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語句,就可以暴出資料