關於SQL注入,你應該知道的那些事

戴上你的黑帽,如今咱們來學習一些關於SQL注入真正有趣的東西。請記住,大家都好好地用這些將要看到的東西,好嗎?html

SQL注入攻擊因以下幾點而是一種特別有趣的冒險:web

  • 1.由於能自動規範輸入的框架出現,寫出易受攻擊的代碼變得愈來愈難——但咱們仍然會寫差勁的代碼。
  • 2.由於你使用了存儲過程或者ORM框架,你不必定很清楚的是(雖然你意識到SQL注入可能穿透他們,對嗎) 咱們在這些保護措施之下編寫的代碼依然是易受攻擊的。
  • 3.經過精心設計的爬取web搜尋易受攻擊站點的自動化工具使這類站點更易遠程檢測出來。而咱們依舊在發佈它們(譯註:指站點)。

SQL注入攻擊因一個很是恰當的緣由而被保留在OWASP(Open Web Application Security Project  開放Web應用安全項目) 的十大隱患列表中第一位——它特別常見,很是容易利用,並且影響十分劇烈。一個很微小的注入風險常常就能使整個系統中的全部數據都被泄漏——而我將要展現給你如何運用大量不一樣的技術本身來這樣作。sql

我幾年前寫《the OWASP Top 10 for .NET developers》時展現過如何防範SQL注入攻擊,因此我不會專一在這些,這都是漏洞利用。受夠了那些無聊的防護工具,讓咱們來攻擊別的東西。shell

若是咱們能攻破查詢內容,大家的數據就都是咱們的了

讓咱們對讓SQL注入攻擊成爲可能的緣由作一個快速歸納。簡而言之,這就是輸入查詢並解密數據。讓我把所說的可視化給你:好比說你有一個包含有相似於「id=1」之類的字符串參數的URL,容納後那個參數經過以下方式構造了一個SQL查詢。數據庫

這整個URL可能和這個東西看起來很像:瀏覽器

這是挺基礎的東西,而當你能掌控連接中的信息並改變傳遞給查詢的值時會變得有趣。好了,把1變成2會給你另外一個你期待的東西,可是若是你這樣作呢?安全

http://widgetshop.com/widget/?id=1 or 1=1服務器

那可能在數據庫服務器中存留成這樣的:網絡

1
SELECT * FROM Widget WHERE ID = 1 OR 1=1

這告訴咱們的是數據沒有被淨化——在上例中ID應該只是一個整數但「1 OR 1=1」的值也被接受。更重要的是,由於數據只是簡單地被添加到查詢中,它可以改變語句的功能。這個查詢將可以選擇全部的記錄而不是單個記錄,由於」1=1″語句是恆成立的。架構

或者,咱們能夠經過把「or 1=1」改爲「and 1=2」來強制頁面不返回任何記錄,由於它一直都不成立因此沒有結果返回。在這兩個可選的方案中咱們能方便地肯定程序是否受注入攻擊威脅。

這是SQL注入攻擊的本質——經過不被信任的數據巧妙地操縱查詢的執行——而在開發者作這樣子事時發生。

1
2
3
query = "SELECT * FROM Widget WHERE ID = " + Request.QueryString[ "ID" ];
// Execute the query...
//執行查詢...

固然他們作的是將不被信任的數據參數化,但本文中我不會過多敘述(若是想要了解防範措施,轉回part one of my OWASP series),而將更多談論如何發動攻擊。

好了,因而背景部分介紹瞭如何展現SQL注入風險存在,但你能拿它怎麼辦?讓咱們開始探尋一些廣泛的注入模式。

抽絲剝繭:合併基於查詢的注入

讓咱們舉個例子,表示咱們想要返回一堆記錄的頁面,在這裏是一個有一堆帶有「TypeId」1的小東西的URL。像這樣:

http://widgetshop.com/Widgets/?TypeId=1

頁面上的結果會像這樣:

咱們會期待這個查詢進入到數據庫時變成像這樣的東西:

1
SELECT Name FROM Widget WHERE TypeId = 1

可是若是咱們能應用我上述描繪的,也就是說咱們可能可以給查詢字符串中的數據添加SQL,咱們可能會作出這樣的東西:

http://widgetshop.com/Widgets/?TypeId=1 union all select name from sysobjects where xtype=’u’

而後它將產生一個以下的SQL查詢:

1
SELECT Name FROM Widget WHERE TypeId = 1 union all select name from sysobjects where xtype= 'u'

如今記好了系統對象表列舉數據庫中全部對象,而在這個例子中咱們用 xtype 「u」 來篩選這個表,換言之,用戶表。

當一個注入風險存在的時候將會有以下的輸出:

這就是叫作合併基於查詢的注入攻擊,就像咱們剛纔簡單地像原始結果添加一項,它直接到了HTML輸出中——簡單吧!既然咱們已經知道有一個數據表叫「User」,咱們能夠作這樣的事:

http://widgetshop.com/Widgets/?TypeId=1 union all select password from [user]

若是數據表中「user」不被中括號括起來,考慮到「user」這個詞在數據庫看來有其餘含義,SQL服務器會變得不易控制。無論怎樣,這是它返回的:

固然,UNION ALL語句只在第一個SELECT語句和第二個有相同的字段時起做用。這很容易被發現,你只需試試一些「union all select ‘a’」,若是它查詢失敗就試試「union all select ‘a’, ‘b’」之類的,以此類推。根本上你是在不斷猜想列數直到你構造的查詢發揮做用。

咱們能夠繼續研究這個方面並揪出各類數據,但仍是學習下一種攻擊方式吧。有時一個基於合併查詢的注入不會發揮做用,與輸入格式、查詢中添加的數據甚至結果如何顯示都有關。爲了繞開它咱們須要變得更有創造性一些。

讓程序本身泄密:基於錯誤信息的注入

http://widgetshop.com/widget/?id=1 or x=1

等一下,這不是一個合法的SQL語句,那個「x=1」不會被處理,至少在沒有一個叫作x的列時不會被處理。那麼它不會拋出一個異常嗎?嚴格地說,事實上你將會看到像這樣的異常:

這是一個ASP.NET的錯誤,而其餘的框架也有相似的樣式。可是重要的是這些錯誤信息暴露了內部的實現方式,換言之,這告訴咱們數據庫中沒有叫作「x」的字段。爲何這很重要?從根本上說,這是由於你一旦確立了一個應用程序在泄漏SQL異常,你就能夠作這樣的事:

http://widgetshop.com/widget/?id=convert(int,(select top 1 name from sysobjects where id=(select top 1 id from (select top 1 id from sysobjects where xtype=’u’ order by id) sq order by id DESC)))

這有好多須要吸取理解,我等會將回來詳細解釋。更重要的是經過那條語句你可以在瀏覽器中獲得這樣的結果: 

如今咱們獲得了,咱們已經發現那數據庫裏有一個表單叫作「Widget」。你將常常能看到這中注入攻擊因依賴於數據庫內部的錯誤而被稱做「基於錯誤信息的注入」。讓咱們解構URL中的這個查詢:

1
2
3
4
5
6
7
8
convert ( int , (
     select top 1 name from sysobjects where id=(
       select top 1 id from (
         select top 1 id from sysobjects where xtype= 'u' order by id
       ) sq order by id DESC
     )
   )
)

從最深層的開始理解,咱們先按照ID的順序從sysobjects表獲取第一個有記錄的ID。在那裏,咱們獲取最後一個ID(這就是爲何它是按降序排列),並把它傳遞到第一個select語句。那個語句接下來只會將那個表單名稱轉換成一個整數。這個轉換將大多數狀況下失敗(各位,不要用「1」或「2」或其餘整數來命名數據表就是這個緣由!),而這個異常暴露了UI中的表單名稱。

爲何是三個select語句?由於這意味着咱們能夠進入最深層的那個並把「top1」改成「top2」,獲得以下結果:

如今咱們知道了這個數據庫有一個數據表叫作「User」。利用這種方法咱們能夠發現各個表單的字段名稱(只需向syscolumns表應用一樣的思路)。咱們能夠更進一步擴展這個思路

在上一個截圖中,我已經發現了叫作User的表單和名爲Password的列,如今我須要作的就是把那個表單選出來(固然,你能夠用嵌套的select語句來一個一個枚舉全部的記錄),並經過將字符串轉換成整數來構造異常(你老是可以在數據後面經過加一個英文字符來看它究竟是不是一個整數,以後嘗試將整個字符串轉換爲整數時就會產生一個異常)。若是你想要進一步理解這能夠有多簡單,我去年錄製了一個我教3歲兒子用Havij來自動注入的視頻,那裏運用了這個技術。

可是這裏有一個問題——它惟一能成功的多是由於那個app有些淘氣並將內部的錯誤信息展現給公衆。事實上那個app差很少直接告訴了咱們表單和列表的名字並當咱們作出恰當詢問時返回數據,那麼若是那個app不這樣作又會怎樣呢?個人意思是,若是那個app設定恰當而沒有泄漏內部的錯誤信息呢?

這就是咱們運用「blind」SQL(多譯爲盲注)注入的地方,那真的是一個有趣的東西。

盲目地嘗試注入

在上一個例子中(事實上也在不少成功的注入攻擊先例中),攻擊依賴於受攻擊的app明確地將內部的細節,要麼是合併表單,要麼是將數據返回,要麼將錯誤信息傳回瀏覽器。泄漏內部的實現方法一直都是一鍵很差的事,由於正如你以前看到的那樣,像這樣不安全的錯誤處理能夠促使不只僅是應用程序的架構泄漏,更會使你極易從中獲取數據。

一個恰當設定的app應當可以在獲得一個未經處理的異常時返回一個和下面這個類似的錯誤信息:

這是新ASP.NET的app在處理自定義錯誤時的默認錯誤頁面,可是相似的樣式也在別的technology stacks中出現。如今這個頁面已經和以前那個顯示內部SQL異常的頁面如出一轍了,只不過是用一個有好的錯誤信息代替直接展現出來的異常。假如咱們同時也不能實現一個基於合併查詢的攻擊,SQl注入風險就徹底不存在了嗎?不必定……

盲目地SQl注入攻擊依賴於咱們變得可以獲得不言而喻的信息,換言之,咱們可以經過觀察app並無直接告訴咱們的表單名稱或者在瀏覽器中直接顯示的列表數據來下結論。固然問題來了——咱們如何讓app按照一個能夠觀察到的格式來揭示咱們以前有的信息,而並不顯式地告訴咱們?

咱們將去欣賞兩種嘗試:基於布爾值的和基於時間的。

去詢問(APP),而後你將被回答:基於布爾值的注入

這隻有你詢問app正確的問題時成立。以前,咱們可以明確地詢問這樣的問題,好比「你有什麼表單?」或「每一個表單中你有什麼數據列?」,而後數據庫會明確地告訴咱們。如今咱們須要稍微變換一線詢問的方式,好比像這樣:

1
http://widgetshop.com/widget/?id=1 and 1=2 Clearly this equivalency test can never be true – one will never be equal to two. How an app at risk of injection responds to this request is the cornerstone of blind SQLi and it can happen in one of two different ways.

顯然這個相等測試永遠不會成立——1永遠都不等於2。那麼一個app如何處理這樣的查詢決定了它的SQL注入風險,可能會有兩種方式。

第一種,若是沒有記錄返回,它可能只拋回一個異常。一般開發者會假設那裏存在一個與查詢的字符串有關的記錄,由於常常會是app本身產生那個連接並在另外一個頁面中獲取數據。而當那裏沒有數據能夠返回時,事情就不同了。或者第二種,那個app可能拋出一個異常並同時不會展現記錄,由於那個相等永遠都是錯的。無論怎樣,那個app都會隱含地告訴咱們數據庫中沒有記錄被返回。

如今咱們試試這個:

1
2
3
4
5
6
7
1 and (
  select top 1 substring ( name , 1, 1) from sysobjects where id=( 
   select top 1 id from (
       select top 1 id from sysobjects where xtype= 'u' order by id   
) sq order by id desc
  )
) = 'a'

要記住用這整個語句塊來替換剛纔那個查詢串的「?id=1」,這其實是一個在前一個詢問上作出的小變化,試圖獲取表單名稱。事實上主要的區別在於如今不是試圖經過將字符串轉換爲整數來構造異常,而是運用相等測試來檢查是否有一個表單首字母爲「a」(假設這裏對大小寫不敏感)。若是這個查詢和「?id=1」給咱們的信息同樣,那麼它就至關於向咱們證明相等測試成立了,sysobjects裏確實有一個首字母開頭爲「a」的表單。若是它給咱們以前咱們提到過的兩種情景之一,那麼咱們就知道表單並無以「a」開頭,由於沒有信息被返回。

如今咱們獲得的只有sysobjects中表單的第一個字母,當你想要獲得第二個字母是substring語句須要變成如今這樣:

1
select top 1 substring ( name , 2, 1) from sysobjects where id=(

你能看到它如今從2開始而不是1.固然,這很費力:你在枚舉sysobjects中全部表單後枚舉了全部字母表中可能組成的詞,直到你最後獲得告終果,而後你又要表單名稱的每個字符重複這個過程。可是,有一種像這樣的快捷方式:

1
2
3
4
5
6
7
8
9
and
(  
select  top  1 ascii( lower ( substring ( name , 1, 1)))  from  sysobjects  where
  id=(    
select  top  1 id  from 
      select  top  1 id  from  sysobjects  where  xtype= 'u'  order  by  id    
) sq  order  by  id  desc
   )
) > 109

這裏有一個微妙但很重要的區別,它沒有檢查單個字符匹配,而是查找字符在ASCII表中的位置。事實上,它先將表單名稱轉換爲小寫字母,這樣咱們只須要處理26個字符(固然,假設命名中只有字母),而後它獲取那個字母的ASCII值。在上一個例子中,它接着檢查表單中是否有以在「m」(ASCII值爲109)以後的字母開頭的,而後相同的潛力成功描述了以前應用的(要麼一個記錄被返回要麼沒有)。主要的區別在於,沒有進行26次嘗試猜想字母(並連續進行26次HTTP請求),它如今將會在5次嘗試中窮盡全部可能——你只須要不斷將可能的ASCII值區間減半直到最後只有一種可能剩餘。

好比,若是一個字符ASCII值比109大,那麼它必定在「n」和「z」之間因此你分割(大體地)這個區間爲一半,而後嘗試大於115那個。若是那是錯誤的那麼正確的字符就必定在「n」和「s」之間,因此你再將區間減半,而後嘗試大於112的那個。那時正確的因此如今只有三個字符剩下了,因此你能夠在至多兩次嘗試中將區間減少至長度爲1。一句話就是至多26次猜想(平均起來13次),如今只須要5次,若是你只是簡單地每次將答案區間減半。

經過構造恰當的詢問app將依舊告訴你以前它經過明確的錯誤信息告訴你的東西,只不過它如今有些怕羞,你須要哄它纔會獲得答案。這常常被叫作「基於布爾值」的SQL注入,而它在以前演示過的「基於合併查詢」的和「基於錯誤信息」的方案很差用時可以發揮做用。但這並不是萬無一失,讓咱們看看另外一個途徑,這回咱們將要有一些耐心。

耐心等待泄漏:基於時間的盲目注入

全部實時的方案成功發揮做用都是基於一個假設:app會經過HTML輸出來泄漏信息。在以前的例子中基於合併查詢的和基於錯誤信息的嘗試是在瀏覽器中給咱們數據來明確地告訴咱們對象名稱和泄漏的內部數據。在盲目的基於布爾值的例子中,咱們被隱含地告知同一份信息藉助於HTML和基於真假相等測試獲得的結果不一樣。那麼當這份信息不能經過HTML泄漏時,不管是明確地仍是隱含地,怎麼辦?

讓咱們想像有另外一個攻擊媒介是這個URl:

1
http://widgetshop.com/Widgets/?OrderBy=Name

在這個例子中很正常假設查詢會被翻譯成像這樣的東西:

1
SELECT * FROM Widget ORDER BY Name

顯然咱們不能直接開始向ORDER BY語句直接加東西(儘管那裏已經有其餘角度你能夠掛載一個基於布爾值的攻擊),因此咱們須要嘗試另外一種途徑。一個很常見的SQL注入技巧是終止一個語句並隨後附加一個語句,好比像這樣:

1
http://widgetshop.com/Widgets/?OrderBy= Name ; SELECT DB_NAME()

這是一個無害的語句(儘管在查找數據庫的名字是可能會有用),一個更有害的途徑可能會是相似於「DROP TABLE Widget」的東西。固然web app鏈接數據庫所調用的賬號須要有這樣的權限,問題在於一旦你開始將連接鏈接起來,它的潛力就開始發揮。

回到那個盲目的SQL注入攻擊,如今咱們須要作的是找到一個在附加語句中運用以前討論到的基於布爾值的測試。要作到這點咱們須要用WAITFOR DELAY語句來產生延時。試試這個,看看尺寸:

這和以前的例子只有一個微小的變化,以前是經過操縱WHERE語句改變返回的記錄的書目,而如今只是用一個新的語句來查找sysobjects中是否存在一個表單以一個比「m」大的字母開頭,而且若是存在,查詢將稍微等待5秒鐘。咱們仍舊須要縮小表單名稱的範圍並且須要嘗試表單名中的每個字符而咱們仍舊須要查詢sysobjects中的其餘表單(固然還要看看syscolumns並將數據提取出來),但全部這一切徹底能夠用一點時間。5秒鐘可能比須要的有些長了或者它可能不夠長,這一切都歸結於應用程序的響應時間如何保持一致,由於最終這都被設計來操做一個能被觀察到的行爲——從開始查詢到最後獲得結果要通過多長時間。

這個攻擊——還有以前那些——固然被能夠徹底地自動化,由於除了簡單枚舉和條件邏輯以外不剩別的了。固然它可能會佔用一些時間,但那是一個相對的概念:若是一個正常的查詢須要1秒鐘,而5次嘗試只有一半須要完成的話,你應該期待每17.5秒獲得一個字符,好比有數據庫中平均有10個字符的話,就是須要大概3分鐘獲得一個表單,而可能一個數據庫中有20個表單,咱們就認爲大概一小時你就能獲得系統中的每個表單名稱。而這是你用單線程方式作這些的狀況。

到這裏沒有結束……

這是那些有一堆不一樣角度觀點的話題,不僅由於有太多的數據庫、app框架、服務器的組成,更不要說一整個防護體系好比網絡應用的防火牆。一個事情變得棘手的例子是若是你須要求助於基於時間的攻擊而數據庫尚未支持延遲功能,好比一個Access數據庫(是的,遊戲而事實上在網站中用這些!)這裏的一個途徑是用叫作 heavy queries的方案,查詢因爲自己的性質會致使響應是緩慢的。

另外一件關於SQL注入攻擊值得一提的是攻擊是否成功有兩個關鍵因素:第一個是app在輸入方面的規範,這決定了app最終會接收到什麼字符並傳給數據庫。一般咱們會看到很零零碎碎的途徑,好比尖括號和引號被剝離,但其餘一切是容許的。當這種狀況出現時,攻擊者須要變得有創意,考慮如何構造恰當的查詢使得「路障」被避免。而這正是第二點——攻擊者的SQl實力是相當重要的。這不是指你運用TSQL的SELECT FROM的能力,那些優秀的SQl注入者掌握大量可以繞過輸入檢測的竅門並從系統中選擇數據而使它們能經過網頁來檢索。好比說,搜尋一個列的類型能夠經過像這樣的小技巧:

1
http://widgetshop.com/Widget/?id=1 union select sum (instock) from widget

在這個例子中,基於錯誤的注入攻擊將在錯誤信息返回到UI時(固然,若是沒有報錯就是指它是整型的)會告訴你「InStock」列是什麼類型的

或者一旦你徹底厭倦了那個該死的易受攻擊的站點仍然在網絡上留存,試試這個:

1
http://widgetshop.com/Widget/?id=1;shutdown

可是注入攻擊能夠經過從HTTP中獲取信息而更進一步,好比那裏有能給攻擊者機器腳本的載體或者試試另外一個離題的——爲何不試試直接經過HTML獲取那該死的東西?你就建立一個本地的SQL服務器並經過1433端口遠程鏈接到SQL Server Management Studio!等一下,你會須要那個網頁app用來連接數據庫來創造用戶的賬號,是嗎?是的,並且大部分人都須要,事實上你只需經過Google就能找到它們(譯註:用度娘會告訴你找不到)(固然這種狀況下SQL注入攻擊就沒有必要了,由於數據庫此時已經能公開獲取)

最後,若是關於SQl注入攻擊及漏洞的流行和在當今軟件行業的影響還有什麼疑問,就在上週就有一篇 關於能夠說是迄今爲止最大的黑客方案之一的新聞,據稱它造就了3億損失

這起訴書也暗示那些黑客,在大多數狀況下,沒有部署很複雜的方案來進入企業網絡。這篇報道也展現了在大多數狀況下缺口是經過SQL注入漏洞的道德——這一威脅已經被完全證實並領悟遠超過十年了。

可能SQL注入攻擊沒有像某些人相信的那樣被人理解。

轉自伯樂在線

相關文章
相關標籤/搜索