1. 程式人生 > >SQL注入攻擊例項

SQL注入攻擊例項

一位客戶讓我們針對只有他們企業員工和顧客能使用的企業內網進行滲透測試。這是安全評估的一個部分,所以儘管我們之前沒有使用過SQL注入來滲透網路,但對其概念也相當熟悉了。最後我們在這項任務中大獲成功,現在來回顧一下這個過程的每一步,將它記錄為一個案例。

我們記錄下了在多次錯誤的轉折後經歷的曲折過程,而一個更有經驗的人會有這不同的 — 甚至更好的 — 方法。但事實上我們成功以後才明白,我們並沒有完全被誤導。

其他的SQL文章包含了更多的細節,但是這篇文章不僅展示了漏洞利用的過程,還講述了發現漏洞的原理。

目標內網

展現在我們眼前的是一個完整定製網站,我們之前沒見過這個網站,也無權檢視它的原始碼:這是一次“黑盒”攻擊。‘刺探’結果顯示這臺伺服器執行在微軟的IIS6上,並且是ASP.NET架構。這就暗示我們資料庫是微軟的SQL server:我們相信我們的技巧可以應用在任何web應用上,無論它使用的是哪種SQL 伺服器。

登陸頁有傳統的使用者-密碼錶單,但多了一個 “把我的密碼郵給我”的連結;後來,這個地方被證實是整個系統陷落的關鍵。

當鍵入郵件地址時,系統假定郵件存在,就會在使用者資料庫裡查詢郵件地址,然後郵寄一些內容給這個地址。但我的郵件地址無法找到,所以它什麼也不會發給我。

對於任何SQL化的表單而言,第一步測試,是輸入一個帶有單引號的資料:目的是看看他們是否對構造SQL的字串進行了過濾。當把單引號作為郵件地址提交以後,我們得到了500錯誤(伺服器錯誤),這意味著“有害”輸入實際上是被直接用於SQL語句了。就是這了!

我猜測SQL程式碼可能是這樣:

  1. SELECT fieldlist  
  2.   FROM
    table
  3.  WHERE field = '$EMAIL';  

$EMAIL 是使用者從表單提交的地址,並且這段查詢在字串末端$EMAIL上提供了引號。我們不知道欄位或表的確切名字,但是我們瞭解他們的本質,這有助於我們做正確的猜測。

當我們鍵入 [email protected]‘ -注意這個末端的引號 – 下面是這個SQL欄位的構成:

  1. SELECT fieldlist  
  2.   FROMtable
  3.  WHERE field = '[email protected]'';  

當這段SQL開始執行,SQL解析器就會發現多餘的引號然後中斷執行,並給出語法錯誤的提示。這個錯誤如何清楚的表述給使用者,基於應用內部的錯誤恢復規程,但一般來說都不會提示“郵件地址不存在”。這個錯誤響應成了死亡之門,它告訴別人使用者輸入沒有被正確的處理,這就為應用破解留下了可乘之機。

這個資料呈現在WHERE的從句中,讓我們以符合SQL規範的方式改變輸入試試,看看會發生什麼。鍵入anything’ OR ‘x’=‘x, 結果如下:

  1. SELECT fieldlist FROMtableWHERE field = 'anything'OR'x'='x';  

因為應用不會思考輸入 – 僅僅構造字串 - 我們使用單引號把WHERE從句的單一組成變成了雙組成,’x'=‘x從句是恆成立的,無論第一個從句是什麼。(有一種更好的方式來確保“始終為真”,我們隨後會接觸到)。

但與每次只返回單一資料的“真實”查詢不同,上面這個構造必須返回這個成員資料庫的所有資料。要想知道在這種情況下應用會做什麼,唯一的方法就是嘗試,嘗試,再嘗試。我們得到了這個:

我們猜測這個地址是查詢到的第一條記錄。這個傢伙真的會在這個郵箱裡收到他忘記的密碼,想必他會很吃驚也會引起他的警覺。

我們現在知道可以根據自己的需要來篡改查詢語句了,儘管對於那些看不到的部分還不夠了解,但是我們注意到了在多次嘗試後得到了三條不同的響應:

  • “你的登入資訊已經被郵寄到了郵箱”
  • “我們不能識別你的郵件地址”
  • 伺服器錯誤

前兩個響應是有效的SQL,最後一個響應是無效的SQL:當猜測查詢語句結構的時候,這種區別非常有用。

模式欄位對映

第一步是猜測欄位名:我們合理的推測了查詢包含“email address”和“password”,可能也會有“US Mail address”或者“userid”或“phone number”這樣的欄位。我們特別想執行 SHOW TABLE語句, 但我們並不知道表名,現在沒有比較明顯的辦法可以拿到表名。

我們進行了下一步。在每次測試中,我們會用我們已知的部分加上一些特殊的構造語句。我們已經知道這個SQL的執行結果是email地址的比對,因此我們來猜測email的欄位名:

  1. SELECT fieldlist FROMtableWHERE field = 'x'AND email ISNULL--';

目的是假定的查詢語句的欄位名(email),來試試SQL是不是有效。我不關心匹配的郵件地址是啥(我們用了個偽名’x’), ——’這個符號表示SQL註釋的起始。對於去除應用末尾提供的引號,這是一個很有效的方式,我們不用在乎我們遮蔽掉的是啥。

如果我們得到了伺服器錯誤,意味著SQL有不恰當的地方,並且語法錯誤會被丟擲:更有可能是欄位名有錯。如果我們得到了任何有效的響應,我們就可以猜測這個欄位名是正確的。這就是我們得到“email unknown”或“password was sent”響應的過程。

我們也可以用AND連線詞代替OR:這是有意義的。在SQL的模式對映階段,我們不需要為猜一個特定的郵件地址而煩惱,我們也不想應用隨機的泛濫的給使用者發“這是你的密碼”的郵件 - 這不太好,有可能引起懷疑。而使用AND連線郵件地址,就會變的無效,我們就可以確保查詢語句總是返回0行,永遠不會生成密碼提醒郵件。

提交上面的片段的確給了我們“郵件地址未知”的響應,現在我們知道郵件地址的確是儲存在email欄位名裡。如果沒有生效,我們可以嘗試email_address或mail這樣的欄位名。這個過程需要相當多的猜測。

接下來,我們猜測其他顯而易見的名字:password,user ID, name等等。每次只猜一個欄位,只要響應不是“server failure”,那就意味著我們猜對了。

  1. SELECT fieldlist FROMtableWHERE email = 'x'AND userid ISNULL--';

在這個過程中,我們找到了幾個正確的欄位名:

  • email
  • passwd
  • login_id
  • full_name

無疑還有更多(有一個線索是表單中的欄位名),一陣挖掘後沒有發現更多了。但是我們依然不知道這些欄位名的表名,它們在哪找到的?

尋找資料庫表名

應用的內建查詢指令已經建立了表名,但是我們不知道是啥:有幾個方法可以找到表名。其中一個是依靠subselect(字查詢)。

一個獨立的查詢

  1. SELECTCOUNT(*) FROM tabname  

返回表裡記錄的數量,如果表名無效,查詢就會失敗。我們可以建立自己的字串來探測表名:

  1. SELECT email, passwd, login_id, full_name  
  2.   FROMtable
  3.  WHERE email = 'x'AND 1=(SELECTCOUNT(*) FROM tabname); --';

我們不關心到底有多少條記錄,只關心表名是不是正確。重複多次猜測以後,我們終於發現members是這個資料庫裡的有效表名。但它是用在這個查詢裡的麼?所以我們需要另一個測試,使用table.field:實際查詢的部分只工作在這個表中,而不是隻要表存在就執行。

  1. SELECT email, passwd, login_id, full_name FROM members WHERE email = 'x'
  2.                     AND members.email ISNULL--';

當返回“Email unknown”時,就意味著我們的SQL注入成功了,並且我們正確的猜測出了表名。這對後面的工作很重要,但是我們暫時先試試其他的方法。

找使用者賬號

我們對members表的結構有了一個區域性的概念,但是我們僅知道一個使用者名稱:任意使用者都可能得到“Here is your password”的郵件。回想起來,我們從未得到過資訊本身,只有它傳送的地址。我們得再弄幾個使用者名稱,這樣就能得到更多的資料。

首先,我們從公司網站開始找幾個人:“About us”或者“Contact”頁通常提供了公司成員列表。通常都包含郵件地址,即使它們沒有提供這個列表也沒關係,我們可以根據某些線索用我們的工具找到它們。

LIKE從句可以進行使用者查詢,允許我們在資料庫裡區域性匹配使用者名稱或郵件地址,每次提交如果顯示“We sent your password”的資訊並且郵件也真發了,就證明生效了。

警告:這麼做拿到了郵件地址,但也真的發了郵件給對方,這有可能引起懷疑,小心使用。

我們可以查詢email name或者full name(或者推測出來的其他資訊),每次放入%萬用字元進行如下查詢:

  1. SELECT email, passwd, login_id, full_name  
  2.   FROM members  
  3.  WHERE email = 'x'OR full_name LIKE'%Bob%';  

記住儘管可能不只有一個“Bob”,但我們只能看到一條資訊:建議精煉LIKE從句。

密碼暴力破解

可以肯定的是,我們能在登陸頁進行密碼的暴力破解,但是許多系統都針對此做了監測甚至防禦。可能有的手段有操作日誌,帳號鎖定,或者其他能阻礙我們行動的方式,但是因為存在未過濾的輸入,我們就能繞過更多的保護措施。

我們在構造的字串裡包含進郵箱名和密碼來進行密碼測試。在我們的例子中,我們用了受害者 並嘗試了多組密碼。

  1. SELECT email, passwd, login_id, full_name FROM members WHERE email = '<a  
  2.                         href="mailto:[email protected]">[email protected]</a>' AND passwd  
  3.                         = 'hello123';  

這是一條很好使的SQL語句,我們不會得到伺服器錯誤的提示,只要我們得到“your password has been mailed to you”的提示資訊,就證明我們已經得到密碼了。這時候受害人可能會警覺起來,但誰關心他呢,我們已經得到密碼了。

這個過程可以使用perl指令碼自動完成,然而,我們在寫指令碼的過程中,發現了另一種方法來破解系統。

資料庫不是隻讀的

迄今為止,我們沒做查詢資料庫之外的事,儘管SELECT是隻讀的,但不代表SQL只能這樣。SQL使用分號表示結束,如果輸入沒有正確過濾,就沒有什麼能阻止我們在字串後構造與查詢無關的指令。

The most drastic example is:

這劑猛藥是這樣的:

  1. SELECT email, passwd, login_id, full_name  
  2.   FROM members  
  3.  WHERE email = 'x'DROPTABLE members; --';  -- Boom!

第一部分我們準備了一個偽造的email地址——‘X’——我們不關心查詢結果返回什麼:我們想要得到的只是我們自己構造的SQL指令。這次攻擊刪除了整個members表,這就不太好玩了。

這表明我們不僅僅可以切分SQL指令,而且也可以修改資料庫。這是被允許的。

新增新使用者

我們已經瞭解了members表的區域性結構,新增一條新紀錄到表裡視乎是一個可行的方法:如果這成功了,我們就能簡單的用我們新插入的身份登陸到系統了。

不要太驚訝,這條SQL有點長,我們把它分行顯示以便於理解,但它依然是一條語句:

  1. SELECT email, passwd, login_id, full_name  
  2.   FROM members  
  3.  WHERE email = 'x';  
  4.         INSERTINTO members ('email','passwd','login_id','full_name')   
  5.         VALUES ('[email protected]','hello','steve','Steve Friedl');--';

即使我們得到了正確的欄位名和表名,但在成功攻擊之前我們還有幾件事需要了解:

  1. 在web表單裡,我們可能沒有足夠的空間鍵入這麼多文字(儘管可以用指令碼解決,但並不容易)。
  2. web應用可能沒有members表的INSERT許可權。
  3. 母庸置疑,members表裡肯定還有其他欄位,有一些可能需要初始值,否則會引起INSERT失敗。
  4. 即使我們插入了一條新紀錄,應用也可能不正常執行,因為我們無法提供值的欄位名會自動插入NULL。
  5. 一個正確的“member”可能額不僅僅只需要members表裡的一條紀錄,而還要結合其他表的資訊(如,訪問許可權),因此只新增一個表可能不夠。

在這個案例裡,我們遇到了問題#4或#5,我們無法確定到底是哪個—— 因為用構造好的使用者名稱登陸進去的時候,返回了伺服器錯誤的提示。儘管這就暗示了我們那些沒有構造的欄位是必須的,但我們沒有辦法正確處理。

一個可行的辦法是猜測其他欄位,但這是一個勞力費神的過程:儘管我們可以猜測其他“顯而易見”的欄位,但要想得到整個應用的組織結構圖太難了。

我們最後嘗試了其他方式。

把密碼郵給我

我們意識到雖然我們無法新增新紀錄到members資料庫裡,但我們可以修改已經存在的,這被證明是可行的。

從上一步得知  賬戶在這個系統裡,我們用SQL注入把資料庫中的這條記錄改成我們自己的email地址:

  1. SELECT email, passwd, login_id, full_name FROM members WHERE email = 'x';  
  2.                             UPDATE members SET email = <a href="mailto:'[email protected]">'[email protected]</a>'  
  3.                             WHERE email = <a href="mailto:'[email protected]">'[email protected]</a>';  

執行之後,我們自然得到了“we didn’t know your email address”的提示,但這在預料之中,畢竟我們用了假的email地址。UPDATE操作不會通知應用,因此它悄然執行了。

之後,我們使用了“I lost my password”的功能,用我們剛剛更新的email地址,一分鐘後,我們收到了這封郵件:

  1. From: <a href="mailto:[email protected]">[email protected]</a>  
  2.                                 To: <a href="mailto:[email protected]">[email protected]</a>  
  3.                                 Subject: Intranet login This email isin response to your request for your  
  4.                                 Intranet log in information. Your User ID is: bob Your passwordis: hello  

現在,我們要做的就是跟隨標準的登入流程進入系統,這是一個高等級職員,有高階許可權,比我們INSERT的使用者要好。

我們發現這個企業內部站點內容特別多,甚至包含了一個全使用者列表,我們可以合理的推出許多內網都有同樣的企業Windows網路帳號,它們可能在所有地方都使用同樣的密碼。我們很容易就能得到任意的內網密碼,並且我們找到了企業防火牆上的一個開放的PPTP協議的VPN埠,這讓登入測試變得更簡單。

我們又挑了幾個帳號測試都沒有成功,我們無法知道是否是“密碼錯誤”或者“企業內部帳號是否與Windows帳號名不同”。但是我們覺得自動化工具會讓這項工作更容易。

其他方法

在這次特定的滲透中,我們得到了足夠的許可權,我們不需要更多了,但是還有其他方法。我們來試試我們現在想到的但不夠普遍的方法。

我們意識到不是所有的方法都與資料庫無關,我們可以來試試。

呼叫xp_cmdshell

微軟的SQLServer支援儲存過程xp_cmdshell有許可權執行任意作業系統指令。如果這項功能允許web使用者使用,那webserver被滲透是無法避免的。

迄今為止,我們做的都被限制在了web應用和資料庫這個環境下,但是如果我們能執行任何作業系統指令,再厲害的伺服器也禁不住滲透。xp_cmdshell通常只有極少數的管理員賬戶才能使用,但它也可能授權給了更低階的使用者。

繪製資料庫結構

在這個登入後提供了豐富功能應用上,已經沒必要做更深的挖掘了,但在其他限制更多的環境下可能還不夠。

能夠系統的繪製出資料庫可見結構,包含表和它們的欄位結構,可能沒有直接幫助。但是這為網站滲透提供了一條林萌大道。

從網站的其他方面收集更多有關資料結構的資訊(例如,“留言板”頁?“幫助論壇”等?)。不過這對應用環境依賴強,而且還得靠你準確的猜測。

減輕危害

我們認為web應用開發者通常沒考慮到“有害輸入”,但安全人員應該考慮到(包括壞傢伙),因此這有3條方法可以使用。

輸入過濾

過濾輸入是非常重要的事,以確保輸入不包含危險程式碼,無論是SQL伺服器或HTM本身。首先想到的是剝掉“惡意字元”,像引號、分號或轉義符號,但這是一種不太好的方式。儘管找到一些危險字元很容易,但要把他們全找出來就難了。

web語言本身就充滿了特殊字元和奇怪的標記(包括那些表達同樣字元的替代字元),所以想要努力識別出所有的“惡意字元”不太可能成功。

換言之,與其“移除已知的惡意資料”,不如移除“良好資料之外的所有資料”:這種區別是很重要的。在我們的例子中,郵件地址僅能包含如下字元:

  1. abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 @.-_+  

允許不正確的字元輸入是沒有任何好處的,應該早點拒絕它們 - 可能會有一些錯誤資訊 – 不僅可以阻止SQL注入,也可以捕獲一些輸入錯誤而不是把它們存入資料庫。

某個特殊的email地址會讓驗證程式陷入麻煩,因為每個人對於“有效”的定義不同。由於email地址中出現了一個你沒有考慮到的字元而被拒絕,那真是糗大了。

真正的權威是 RFC 2822(比RFC822內容還多),它對於”允許使用的內容“做了一個規範的定義。這種更學術的規範希望可以接受&和*(還有更多)作為有效的email地址,但其它人 - 包括作者 – 都樂於用一個合理的子集來包含更多的email地址。

那些採用更限制方法的人應當充分意識到沒有包含這些地址會帶來的後果,特別是限制有了更好的技術(預編譯/執行,儲存過程)來避免這些“奇怪”的字元帶來的安全問題。

意識到“過濾輸入”並不意味著僅僅是“移除引號”,因為即使一個“正規”的字元也會帶來麻煩。在下面這個例子中,一個整型ID值被拿來和使用者的輸入作比較(數字型PIN):

  1. SELECT fieldlist FROMtableWHERE