PHP程式碼審計01之in_array()函式缺陷

雪痕*發表於2020-09-25

前言

從今天起,結合紅日安全寫的文章,開始學習程式碼審計,題目均來自PHP SECURITY CALENDAR 2017,講完這個題目,會再用一道有相同問題的CTF題來進行鞏固。下面開始分析。

漏洞分析

下面我們看第一題,程式碼如下:

<?php
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()
    {
        // TODO: Implement __destruct() method.
            //這裡要特別注意!!!
        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()函式的用法。先看一下PHP手冊對這個函式的解釋,是檢查陣列中存在某個值,重點是我圈起來的,如果沒有設定第三個引數,那麼就使用寬鬆的檢查,問題就出現在這裡。

現在看上方程式碼第12行,這裡使用了in_array()函式來檢查檔名,但是沒有設定第三個引數!,只會進行弱型別比較,不會檢查資料型別。比如上面白名單規定,只能上傳1~24的檔名,我們上傳3shell.php,因為3在白名單中,所以它會將3shell轉換成3從而繞過了白名單,達到了任意檔案上傳的目的。
為了加深對in_array()的理解,這裡寫一段簡單的程式碼。

<?php
$id =3 and 1=1;
$whitelist = range(1, 5);
if (!in_array($id, $whitelist)) {
    echo "你想搞事";
} else {
    echo "你通過了";
}
?>

這裡in_array()也是沒有設定第三個引數,會進行弱型別比較,會將3 and 1=1轉化為3從而繞過了白名單,輸出你通過了。當我設定第三個引數為true時,此時會進行強型別檢查。所以我們將上文第三行程式碼修改為:if (!in_array($id, $whitelist,true)),再執行就會輸出:“你想搞事”。

現在是不是對in_array()函式有了一個大概的瞭解呢?那讓我們做一道同型別CTF題目來加深鞏固一下。

CTF練習

這道題目也是in_array()函式沒有設定第三個引數,導致白名單被繞過,然後被SQL隱碼攻擊。下面我們具體看一下相關程式碼。
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的相關程式碼。
config.php

<?php
$servername = "localhost";
$username = "root";
$password = "XFAICL1314";
$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;
}
?>

然後是搭建CTF使用的sql語句。

create database day1;
use day1;
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('HRCTF{1n0rrY_i3_Vu1n3rab13}');

題目解析

用上面程式碼我們在本地將環境搭建好。然後開始分析,先看index.php檔案程式碼。再第16行通過$_GET方法接收使用者的輸入,並用stop_hack()來過濾使用者的輸入,然後下方直接拼接到sql語句中進行查詢。然後再向下看,這裡這裡用in_array()來進行一個簡單的檢查,我們發現它沒有設定第三個引數,進行弱型別檢查。現在我們來驗證一下,眼見為實。根據我們上方白名單規則,我們現在id只能輸入1~5。現在我們輸入3來看一下,發現查詢到了資訊。

現在我們輸入8,它不在白名單中,看看返回什麼。

上面是正常的輸入,所以白名單是有效的,下面我們構造payload,比如我們輸入:1',發現程式報錯,繞過了白名單的檢查。可以直接報錯注入。

而關於報錯注入的函式,大約有四個,分別是:floor()、extractvalue()、updatexml()、exp()
雖然繞過了白名單,但是還有過濾函式stop_hack()現在我們定位到這個函式看看:

發現過濾了一些危險函式,我們檢視後發現,這裡沒有過濾updatexml()函式,可以用它,但是concat函式被過濾了,我們需要找到可以替換得函式了。這裡我們使用make_set()函式,它的用法是make_set()函式是先將x轉化成二進位制,例如: 11的二進位制為1011,將二進位制順序顛倒變成1101,每一位數再與後面的字串相對應,為1的擷取,為0的丟棄。如下圖:

所以我們構造payload,獲取資料,為了避免佔用篇幅這裡直接是獲取到flag的payload。

and updatexml(1,make_set(3,'~',(select flag from flag limit 1)),1)

小結

通過這篇文章的講解,是不是對in_array()理解更深了一些呢?下一篇文章會對filter_var函式缺陷導致的漏洞進行學習和分析,一起努力吧!

相關文章