代碼審計中的SQL注入


0x00 背景

SQL注入是一種常見Web漏洞,所謂SQL注入,就是通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務器執行惡意的SQL命令。本文以代碼審計的形式研究SQL注入原理、挖掘形式、防御方案及缺陷。

0x01 SQL注入產生原理

SQL注入與其他常見Web漏洞一樣,均是由外部可控的參數引起的。由於程序沒有經過任何過濾就將外部可控的參數拼接進入SQL語句,直接放入數據庫執行,達到了欺騙服務器執行黑客惡意SQL命令的目的。在這里我們采用DVWA中low級別的源碼學習SQL注入的產生原理。

 

<?php

if( isset( $_REQUEST[ 'Submit' ] ) ) {
    // Get input
    $id = $_REQUEST[ 'id' ];

    // Check database
    $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

    // Get results
    while( $row = mysqli_fetch_assoc( $result ) ) {
        // Get values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

    mysqli_close($GLOBALS["___mysqli_ston"]);
}

?> 

 

  在這里我們注意到變量id是由用戶進行掌控的變量,用戶所輸入的id值並沒有進行任何的過濾,直接拼接到SQL語句中執行,我們重點關注這條SQL語句:

$query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";

  此處,如果我們所輸入的變量id為1' or 1=1 or '1,那么此條SQL語句就變為了:

$query  = "SELECT first_name, last_name FROM users WHERE user_id = '1' or 1=1 or '1';";

  這樣,數據庫就會根據上面這條語句執行操作,這就是SQL注入漏洞的產生原理。

0x02 SQL注入的挖掘形式

SQL注入往往出現在用戶登陸、信息查詢等頁面,通常在HTTP頭中會出現漏洞,例如Cookie值、用於獲取用戶IP的client-ip中,在代碼審計中我們需要着重關注這幾個模塊。

普通注入

普通形式的SQL注入例如上文中提到的,直接通過聯合查詢就可以對數據庫進行操作,這種注入形式往往容易被掃描器檢測出來,在這里不過多進行講解。在代碼審計中,我們只需要關注一些關鍵字,例如select from、mysql_connect、mysql_query、update、delete、insert等即可。

編碼注入

編碼注入包括寬字節注入、URLdecode注入等,利用程序的編碼規則缺陷,輸入與轉碼函數不兼容的特殊字符,導致輸入的字符拼接成為了惡意的SQL語句。

1.寬字節注入

寬字節注入是利用mysql的一個特性,mysql在使用GBK編碼的時候,會認為兩個字符是一個漢字(前一個ascii碼要大於128,才到漢字的范圍),當PHP連接MySQL的時候,設置了“set_character_set_client=gkb”時,往往就會產生寬字節注入。

例如以下程序:

 

<?php
//連接數據庫部分,注意使用了gbk編碼,把數據庫信息填寫進去
$conn = mysql_connect('localhost', 'root', 'toor!@#$') or die('bad!');
mysql_query("SET NAMES 'gbk'");
mysql_select_db('test', $conn) OR emMsg("連接數據庫失敗,未找到您填寫的數據庫");
//執行sql語句
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$sql = "SELECT * FROM news WHERE tid='{$id}'";
$result = mysql_query($sql, $conn) or die(mysql_error()); //sql出錯會報錯,方便觀察
?>

<html>
<head>
<meta charset="gbk" />
<title>新聞</title>
</head>
<body>
<?php
$row = mysql_fetch_array($result, MYSQL_ASSOC);
echo "<h2>{$row['title']}</h2><p>{$row['content']}<p>\n";
mysql_free_result($result);
?>
</body>
</html> 

 

以上程序中,sql語句是SELECT * FROM news WHERE tid='{$id}。參數id存在寬字節注入漏洞,程序采用addslashes函數,將$id的值轉義。addslashes函數產生的效果就是,讓“ ’ ”變成“ \’ ”,對此我們要想進行注入需要繞過“\”。

我們提交/test.php?id=-1' and 1=1#時,數據庫執行的操作是select * from news where id = '1\' #,顯然這里無法進行注入,但是我們提交/test.php?id=-1 %df ' and 1=1#時,數據庫執行的操作就變成了select * from user where id = '1運' and 1=1 #。這樣我們就可以實現SQL注入了。

 這是因為單引號被addslashes轉義成為\',我們提交的%df與\(url編碼為%5c)組合成為了%df%5c,也就是gbk編碼中的“運”字,組合之后被轉義的“ \' ”中的“ ‘ ”還存在,成功閉合了之前的單引號。

挖掘這類的SQL注入漏洞只需要搜索關鍵詞,確認是否采用了gbk編碼即可,關鍵詞主要有以下三種:

character_set_client=gkb

mysql_set_charset('gbk')

SET NAMES 'gbk'     //這條語句等同於如下代碼:

SET
character_set_connect='gbk',
character_set_results='gbk',
character_set_client=gbk;

 寬字節注入除了上述方法外,有時為了避免亂碼,程序員使用iconv()函數將GBK編碼轉換為utf-8編碼,例如:

mysql_query(“set names UTF-8”) ;  
$bar =iconv(“GBK”,”UTF-8”, addslashes($_GET[‘’bar])) ; 

例如我們提交http://127.0.0.1/test.php?id=1%e5%5c%27,%e5%5c%27經過addslashes()變為%e5%5c%5c%5c%27,再經過iconv()變為%e9%8c%a6%5c%5c%27,這樣我們所提交的請求多了一個%5c,反斜杠本身被轉義,單引號(%27)生效。

2.URLdecode注入

 現在絕大多數基於PHP的Web程序都會使用addslashes()等過濾函數對用戶提交的變量等進行過濾,如果某處采用了urldecode()函數進行了url解碼,那么將會大概率的導致URLdecode注入,例如以下代碼:

<?php
$a=addslashes($_GET['c']);
$b=urldecode($a);
echo '$a='.$a;
echo '$b='.$b; ?>

我們在地址欄提交/test.php?c=1%2527時,得到的結果是$a=1%27  $b=1'

這是因為%25的解碼結果為“ % ”,與后面的27拼接得到%27,也就是單引號,因此產生了注入。

所以在代碼審計中挖掘urldecode注入時,只需要搜過兩個關鍵函數即可:

urldecode

rawurldecode

0x03 防御方案及缺陷

黑名單過濾

黑名單過濾是一種原始的、低效甚至無效的過濾手段,將關鍵字select等只使用replace()函數置換為空,企圖達到阻止SQL注入的發生。

這種防御方案的缺陷非常明顯,我們只需要將關鍵字雙向,例如seleselectct(過濾掉中間完整的select之后剩余內容仍是select),也可以大小寫繞過的方式,例如SeLeCt(數據庫大小寫不敏感),以此繞過  防御實現sql注入。

魔術引號

在PHP4.2.3以上版本,可以開啟魔法引號自動過濾

magic_quotes_gpc開啟后,會在GET、POST、COOKIE中的單引號、雙引號、反斜杠、空字符的前面加上反斜杠( \ ),但在PHP5中對$_SERVER變量停止了過濾,導致client-ip等容易被利用。

magic_qutoes_runtime開啟后,與GPC的過濾方案一致,區別是它對數據庫和數據文件進行過濾,而非GET、POST、COOKIE值。

在PHP5.4中,魔術引號被取消。

過濾函數

1.addslashes

addslashes是當前常見的一種過濾方式,過濾的內容和范圍與GPC一致,不過該函數的參數必須是string型。例如:

<?php
$str=" 1' ";
echo addslashes($str);
?>

得到的結果是1\‘。

2.mysql_escape_string

在PHP4.0.3以上版本中引入了mysql_escape_string函數和mysql_real_escape_string函數,這兩個函數也是對字符串進行過濾,受函數影響的字符包括單引號、雙引號、\、\n、\r等。例如:

<?php
$con = mysql_connect("localhost","root","password");
$id = mysql_real_escape_string($_GET['id'],$con);
echo `select * from table where id = ' ".$id." '`;
?>

當用戶發出請求/test.php?id=1'時,數據庫做以下處理:select * from test where id = '1\"。

PDO預編譯

PDO prepare預編譯類似於Java中的preparestatement,采用預編譯的形式處理數據庫查詢。

<?php
$dbms='mysql';     //數據庫類型
$host='localhost'; //數據庫主機名
$dbName='test';    //使用的數據庫
$user='root';      //數據庫連接用戶名
$pass='pass';          //對應的密碼
$dsn="$dbms:host=$host;dbname=$dbName";
$dsn -> exec("SET NAME 'gbk'");
$sql = "select * from table where user=? and pass=?";
$pstmt = $dbh -> prepare($sql);
$exeres = $pstmt -> excute (array($user,$pass));

  以上代碼雖然采用了PDO進行預編譯,但是再PHP5.3.6之前仍然會存在寬字節注入漏洞,因為這樣查詢是PHP本地模擬prepare,然后再把完整的sql語句發送給數據庫,php與MySQL編碼不一致導致寬字節注入漏洞的產生,所以我們需要禁止本地模擬prepare,在上述代碼中加入一行即可:

$dsn -> setAttribute(PDO::ATTER_EMULATE_PREPARES, flase);

  

iconv


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM