PHP代碼審計01之in_array()函數缺陷

前言

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

漏洞分析

下面咱們看第一題,代碼以下:mysql

<?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()的理解,這裏寫一段簡單的代碼。sql

<?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題目來加深鞏固一下。shell

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語句。curl

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函數缺陷致使的漏洞進行學習和分析,一塊兒努力吧!
個人博客即將同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=1pxmil6lphjna學習

相關文章
相關標籤/搜索