前言
從今天起,結合紅日安全寫的文章,開始學習程式碼審計,題目均來自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函式缺陷導致的漏洞進行學習和分析,一起努力吧!