戴上你的黑帽,現在我們來學習一些關于SQL注入真正有趣的東西。請記住,你們都好好地用這些將要看到的東西,好嗎?
SQL注入攻擊因如下幾點而是一種特別有趣的冒險:
SQL注入攻擊因一個非常恰當的原因而被保留在OWasp(Open Web application Security PRoject 開放Web應用安全項目)的十大隱患列表中第一位——它特別常見,非常容易利用,而且影響十分劇烈。一個很微小的注入風險經常就能使整個系統中的所有數據都被泄漏——而我將要展示給你如何運用大量不同的技術自己來這樣做。
我幾年前寫《the OWASP Top 10 for .NET developers》時展示過如何防范SQL注入攻擊,所以我不會專注在這些,這都是漏洞利用。受夠了那些無聊的防御工具,讓我們來攻擊別的東西。
如果我們能攻破查詢內容,你們的數據就都是我們的了讓我們對讓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注入攻擊的本質——通過不被信任的數據巧妙地操縱查詢的執行——而在開發者做這樣子事時發生。
123 | 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中的這個查詢: