lxfweb

前言

根据红日安全写的文章,学习PHP代码审计的第五节内容,题目均来自PHP SECURITY CALENDAR 2017,讲完题目会用一道CTF的题目和实例来加深巩固。这是之前写的,有兴趣可以去看看:
PHP代码审计01之in_array()函数缺陷
PHP代码审计02之filter_var()函数缺陷
PHP代码审计03之实例化任意对象漏洞
PHP代码审计04之strpos函数使用不当

漏洞分析

下面看题目,代码如下:

题目漏洞是正则使用不严谨导致任意文件删除的漏洞,现在来具体分析,引起漏洞的地方在上面代码的21行,这里用到了preg_replace()函数,我们打开PHP手册来看看对这个函数的定义如下:

了解了函数的用法,看上面代码,[^a-z.-_] 表示匹配除了 a 字符到 z 字符和. 字符到 _ 字符之间的所有字符,但是没有考虑到目录路径字符。这就直接可以任意删除文件,例如构造如下参数:

action=delete&data=../../config.php
将删除config.php文件。

CTF练习

通过上面的讲解,来用一道CTF题目来练习一下,也是关于正则的问题,先看代码:

//index.php
<?php
include 'flag.php';
if  ("POST" == $_SERVER['REQUEST_METHOD'])
{
    $password = $_POST['password'];
    if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password))
    {
        echo 'Wrong Format';
        exit;
    }
    while (TRUE)
    {
        $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
        if (6 > preg_match_all($reg, $password, $arr))
            break;
        $c = 0;
        $ps = array('punct', 'digit', 'upper', 'lower');
        foreach ($ps as $pt)
        {
            if (preg_match("/[[:$pt:]]+/", $password))
            $c += 1;
        }
        if ($c < 3) break;
        if ("42" == $password) echo $flag;
        else echo 'Wrong password';
        exit;
    }
}
highlight_file(__FILE__);
?>
//flag.php
<?php $flag = "HRCTF{Pr3g_R3plac3_1s_Int3r3sting}";?>

这道题目考察了是否熟悉PHP正则表达的字符类,大体是下面这个表格:

alnum 字母和数字 header
alpha 字母
ascii 0 - 127的ascii字符
blank 空格和水平制表符
cntrl 控制字符
digit 十进制数(same as \d)
graph 打印字符, 不包括空格
lower 小写字母
print 打印字符,包含空格
punct 打印字符, 不包括字母和数字
space 空白字符 (比\s多垂直制表符)
upper 大写字母
word 单词字符(same as \w)
xdigit 十六进制数字

想要更加详细的了解,建议翻阅PHP手册,了解了字符类,下面来分析代码,上面一共三处正则表达,第一处如下:

if (0 >= preg_match('/[1]{12,}$/', $password))
它表示的含义是匹配到可打印字符12往上包含12,^表示必须某类字符开头,$表示必须某类字符结尾。
第二处正则如下:
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
if (6 > preg_match_all($reg, $password, $arr))
break;
它表示的含义是,把连续的字符,数字,大写,小写作为一段,最少分成六段,比如Test+0He 会分为T est + 0 H e六段。
下面看第三处正则:
$ps = array('punct', 'digit', 'upper', 'lower');
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
if ($c < 3) break;
if ("42" == $password) echo $flag;
这里的含义是输入的字符必须包含字符,数字,大写,小写其中的三种往上。最后与42进行弱类型比较,都符合就输出flag,现在都解读清楚了,让咱们构造payload结果如下:

实例分析

通过例题和CTF题目的讲解,是不是感觉棒棒的,现在咱们来分析实例吧,实例是LvyeCMS3.1,是基于ThinkPHP3.2.3框架。这个实例存在的漏洞也是函数使用不规范被绕过,导致任意文件删除。下面来具体分析:
先查看入口文件index.php

可以看到公共目录,应用目录等一些信息。接下来再看看目录结构:

而漏洞在Application/Template/Controller/StyleController.class.php文件中,具体如下:

看代码第117行,这里是获取目录路径,参数也是我们可以控制的,再向后看,用到了str_replace()函数,它是个字符串替换函数,具体说明如下:

再这里起到的作用就是将'..\', '../', './', '.\'替换为空。但是这里是可以绕过的,如果我们输入.....///呢,会发生什么?是不是正好构造成了../,举个小例子会更清楚,如下:

构造出../我们就可以穿越目录了,现在访问install.php文件会提示已安装,如下图:

然后尝试删除lvyecms/Application/Install/目录下的 install.lock 文件,构造payload如下:

http://www.xxx.com/index.php?g=Template&m=Style&a=delete&dir=.....///Application/Install/&file=install.lock

现在访问install.php,发现确实删除了,如下图:

小结

通过这篇文章的学习与讲解,是不是对PHP的正则了解的更多了呢,下一篇文章会对parse_str函数缺陷进行学习和讲解。一起加油吧!


  1. [:graph:] ↩︎

相关文章: