最近跟着红日安全发在先知社区的PHP-Audit-LabsPHP SECURITY CALENDAR 2017 上的题目做了一遍,也算学习了一波PHP的代码审计。详情请见https://xz.aliyun.com/u/10394

in_array 函数缺陷

先来看看代码

class Challenge {
    const UPLOAD_DIRECTORY = './solutions/';
    private $file;
    private $whitelist;//白名单变量

    public function __construct($file) {
        $this->file = $file;
        $this->whitelist = range(1, 24);
    }

    public function __destruct() {
        if (in_array($this->file['name'], $this->whitelist)) {//此处进行了检测
            move_uploaded_file(
                $this->file['tmp_name'],
                self::UPLOAD_DIRECTORY . $this->file['name']
            );
        }
    }
}

$challenge = new Challenge($_FILES['solution']);

可以看到,这段代码要实现的是文件的上传,in_array对上传的文件名进行了检测,只有数字才允许被上传。

但in_array函数存在弱类型比较的缺陷。

看看in_array函数

 bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )

in_array检查haystack数组中是否存在needle,如果第三个参数 strict 的值为 TRUE 则 in_array() 函数还会检查 needle的类型是否和 haystack 中的相同。 第三个参数的值默认是FALSE。这样,利用in_array函数检查白名单时,如果没有设置第三个参数为TRUE就会引发弱比较问题。

来看看刚刚那段代码,其中并没有设置in_array函数第三个参数,这样可以通过传入例如1shell.php,绕过白名单过滤。

再来看一个例子,红日给出的一个ctf题目

//index.php
<?php
include 'config.php';
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
    die("连接失败: ");
}

$sql = "SELECT COUNT(*) FROM users";
$whitelist = array();
$result = $conn->query($sql);
if($result->num_rows > 0){
    $row = $result->fetch_assoc();
    $whitelist = range(1, $row['COUNT(*)']);
}

$id = stop_hack($_GET['id']);
$sql = "SELECT * FROM users WHERE id=$id";

if (!in_array($id, $whitelist)) {
    die("id $id is not in whitelist.");
}

$result = $conn->query($sql);
if($result->num_rows > 0){
    $row = $result->fetch_assoc();
    echo "<center><table border='1'>";
    foreach ($row as $key => $value) {
        echo "<tr><td><center>$key</center></td><br>";
        echo "<td><center>$value</center></td></tr><br>";
    }
    echo "</table></center>";
}
else{
    die($conn->error);
}

?>
//config.php
<?php  
$servername = "localhost";
$username = "fire";
$password = "fire";
$dbname = "day1";

function stop_hack($value){
    $pattern = "insert|delete|or|concat|concat_ws|group_concat|join|floor|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex|file_put_contents|fwrite|curl|system|eval";
    $back_list = explode("|",$pattern);
    foreach($back_list as $hack){
        if(preg_match("/$hack/i", $value))
            die("$hack detected!");
    }
    return $value;
}
?>

可以看出index.php用来处理用户id,id先通过一层waf过滤,过滤板了一些php函数和sql注入的关键字,然后判断是否在白名单内,白名单只允许用户id为数据库中总用户数内的数字,但是在这里使用in_array来判断,并且没有使用严格模式,便可以利用弱类型比较来绕过,使用字符串拼接传入的id去数据库查询,这里存在sql注入,过滤掉了union,注意到这里输出了错误信息,因此想到可以利用错误回显进行注入,

这里顺便看一下updatexml报错注入。

upatexml()用于更新xml文档,updatexml(目标xml文档,xml路径,更新的内容),要是xml路径中出现无法识别的符号,会报错输出他后面的内容。

这里官方的wp给出的payload:?id=4 and (select updatexml(1,make_set(3,'~',(select flag from flag)),1))这里因为过滤了concat,这里使用make_set进行字符串拼接

如何修复

将in_array函数的第三个参数设置为TRUE。或者将输入强制转换为数字,阻止弱类型比较即可