搜尋此網誌

2010年10月27日 星期三

[技術分享] ReDoS

BlogTH78201031549ReDoS? 可不是再次的阻斷服務攻擊 (Re-DoS),而是正規表示式阻斷服務攻擊 (Regular Expression DoS),是阻斷服務攻擊的一種手法。對於一般的程式開發,雖然不見得常使用到正規表示式的應用,但是正規表示式卻常見於各式各樣的過濾器上。所以像是網站應用程式的輸入檢查、快取伺服器或其他類型閘道器的存取過濾功能,通常都有正規表示式的存在。

正規表示式最基本的應用就是用來尋找資料中是否含有特定樣式的內容,例如我們可以透過正規表示式來判斷一個文字檔內是否含有手機號碼,而判斷條件就是以0為開頭的連續10個數字 (這個條件可能不適合用來找出所有可能的手機號碼,但是無損於說明性)。也因此正規表示式的應用至少含有兩個部分,一個就是用來當做搜尋的樣式 (Pattern,通常這部份也稱為正規表示式),以前述的例子而言就是”以0為開頭的連續10個數字”。另外一部分則是用來被搜尋的輸入目標,以前述的例子而言就是文字檔的內容。而正規表示式阻斷服務攻擊發生在利用某些樣式進行搜尋時將有可能會產生大量消耗運算資源的情況,因而使得系統無法處理其他的請求。這也是它為什麼被稱為阻斷服務攻擊的原因了。

至於”某些樣式”是哪些樣式,在 OWASP 上面的說明文件舉了一些例子,包含

  • (a+)+
  • ([a-zA-Z]+)*
  • (a|aa)+
  • (a|a?)+
  • (.*a){x} | for x > 10

而前些日子 Microsoft 則推出了一個 Regex Fuzzer 的工具,宣稱可以用來驗證所輸入的正規表示式是否存在遭受正規表示式阻斷服務攻擊的隱憂。不過根據我測試的結果,這個工具的執行結果跟 OWASP 上面的例子並沒有完全一致。以目前的情況而言,想要避免正規表示式阻斷服務攻擊最保險的作法似乎就是限制被搜尋目標的內容長度。如果內容長度過長甚至無法加以限制時,自行利用 Fuzzing 的概念 (或利用 Regex Fuzzer,如果你相信它的話) 加以測試將會是比較安全的作法。

 

相關連結:

2010年10月24日 星期日

[新奇玩意] 電話來源 Fingerprinting

bruce-blog2資訊安全專家 Bruce Schneier 日前在他個人的部落格發表了一篇文章,提到了一個可以用來分析電話來源的小工具 - PinDr0p。根據部落格的內容指出,這個工具雖然沒有辦法分辨出電話來源的所在區域或 IP 位址,但是它可以用來判斷不同的電話之間是否來自同一個所在地。也就是說如果該程式知道你日常往來銀行客服電話的特徵,那麼它就可以判斷出日後宣稱來自該銀行的電話是否符合之前的特徵,也就是說它可以避免使用者接到由其他地方發出的詐騙電話而不自知。雖然 Bruce Schneier 宣稱每一個電話來源只要經過 5 次的取樣就可以達到日後 100% 的準確率,但是如何轉換成為可行的系統,而且在實際的環境下又能有多好的成效,就讓我們拭目以待吧。

 

 

相關連結:

 

2010年10月23日 星期六

[技術分享] iBatis/MyBatis 與 SQL Injection

flow前幾天無意間聽到有人提到 iBatis 使用了 PreparedStatement 的機制,所以沒有 SQL Injection 的問題,因此我特地寫了這篇文章來說明 iBatis 與 SQL Injection 的關係。在開始說明之前,我稍微解釋一下 iBatis 這個套件。根據作者的解釋,它是一個 SQL Mapping 的工具,抽象程度高於 JDBC,但是卻低於 ORM (例如 Hibernate)。簡單來說,iBatis 將系統中會使用到的 SQL 指令皆放置在 XML 檔案內,以避免利用程式本身產生與維護 SQL 指令時的困擾。相較於 JDBC,iBatis 也提供了像是 Cache、Transaction Management 等高階的功能,幫助系統開發者簡化資料庫的相關操作。而因為某些原因,iBatis 已經改名為 MyBatis,不過基本上兩者是一樣的。反倒是目前 MyBatis 有 2.x 與 3.x 兩個主要版本,兩者之間卻是不相容的。

回到 SQL Injection 本身,用了 iBatis/MyBatis 就沒有 SQL Injection 的問題了嗎?聰明的讀者,您一定想到了答案。因為如果答案是肯定的,就沒有這篇文章存在的必要性, 所以答案就是即使使用了 iBatis/MyBatis,並不能保證系統就沒有 SQL Injection 的問題。基本上,iBatis/MyBatis 會盡量以PreparedStatement 的方式加以執行,但是卻不是在所有的情況下都是如此的。主要的問題發生於在當使用 inline 方式宣告 SQL 指令的參數時,iBatis/MyBatis 允許兩種參數傳遞的宣告方式,一種是利用 #,另外一種則是透過 $,而後者正是問題所在。$ 表示將參數的內容直接附加於 SQL 指令之內,而不是使用參數化的設定。因此只要使用了 $ 的方式來傳遞參數,就有可能遭受 SQL Injection 的攻擊。

此外,iBatis/MyBatis 也支援所謂 Dynamic SQL 的功能。我們之前提到 Dynamic SQL 正是 SQL Injection 的元兇,但是使用 iBatis/MyBatis 的 Dynamic SQL 卻並不表示就有 SQL Injection 的問題,還是必須取決於參數傳遞的方式。如果使用 # 的方式傳遞參數,即使使用 iBatis/MyBatis 的 Dynamic SQL 依舊是安全的。結論知道了,接下來還是透過簡單的範例程式來看看問題是如何發生的。

首先我在資料庫內鍵入了兩筆虛擬的使用者資料:

mysql> select * from USER_ACCOUNT;
+--------+----------+----------+-----------+
| USERID | username | password | groupname |
+--------+----------+----------+-----------+
|      1 | LMEADORS | PICKLE   | EMPLOYEE  |
|      2 | JDOE     | TEST     | EMPLOYEE  |
+--------+----------+----------+-----------+
2 rows in set (0.00 sec)


 



我以 iBatis/MyBatis 2.x 為例,在設定檔中宣告了兩個功能相同的 SQL 指令,也就是根據使用者帳號與密碼來查詢使用者的資料,通常會用於使用者的登入認證過程。第一個 SQL 指令 (第 3-4 行) 採用 # 的宣告方式,而第二個 SQL 指令 (第 7-8 行) 則採用 $ 的宣告方式。



  1: <select id="checkCredential-1" parameterClass="QueryCondition"
  2:                                resultClass="hashmap">
  3:     SELECT * FROM USER_ACCOUNT WHERE username = #username# 
  4:         AND password = #password#
  5: </select>
  6: <select id="checkCredential-2" parameterClass="QueryCondition"
  7:                                resultClass="hashmap">
  8:     SELECT * FROM USER_ACCOUNT WHERE username = '$username$' 
  9:         AND password = '$password$'
 10: </select>


 



程式針對兩組 SQL 指令分別嘗試 SQL Injection 的攻擊 (第 12-13, 22-23 行)。



  1: String resource = "SqlMapConfig.xml";
  2: Reader reader = Resources.getResourceAsReader(resource);
  3: SqlMapClient sqlMap = 
  4:     SqlMapClientBuilder.buildSqlMapClient(reader);
  5: 
  6: // it's good, and the user is found
  7: QueryCondition qc = new QueryCondition("JDOE", "TEST", null);
  8: Object o = sqlMap.queryForObject("checkCredential-1", qc);
  9: System.out.println("1. " + o);
 10: 
 11: // it's bad, but the user can *NOT* be found (sanitized by iBatis)
 12: qc = new QueryCondition("JDOE' OR 1=1--'", "1234", null);
 13: o = sqlMap.queryForObject("checkCredential-1", qc);
 14: System.out.println("2. " + o);
 15: 		
 16: // it's good, and the user is found
 17: qc = new QueryCondition("JDOE", "TEST", null);
 18: o = sqlMap.queryForObject("checkCredential-2", qc);
 19: System.out.println("3. " + o);
 20: 		
 21: // it's bad, and the user is found (*SQL Injected*)
 22: qc = new QueryCondition("JDOE' OR 1=1--'", "1234", null);
 23: o = sqlMap.queryForObject("checkCredential-2", qc);
 24: System.out.println("4. " + o);


 



我們可以看到第二次的 SQL Injection 攻擊成功 (第 4 行),程式使用的是設定檔中第二個 SQL 指令。針對第一個 SQL 指令的 SQL Injection 並沒有成功 (第 2 行)。



1. {username=JDOE, groupname=EMPLOYEE, password=TEST, USERID=2}
2. null
3. {username=JDOE, groupname=EMPLOYEE, password=TEST, USERID=2}
4. {username=JDOE, groupname=EMPLOYEE, password=TEST, USERID=2}


 



接下來我們來看看 iBatis/MyBatis 的 Dynamic SQL 是如何運作的。第一個 Dynamic SQL 採用 # 的方式加以宣告,而第二個 Dynamic SQL 則採用 $ 的方式加以宣告。



  1: <select id="checkCredentialExtended-1" 
  2:     parameterClass="QueryCondition" resultClass="hashmap">
  3:     SELECT * FROM USER_ACCOUNT
  4:     <dynamic prepend=" WHERE ">
  5:         <isNotEmpty property="username">
  6:             username=#username#
  7:         </isNotEmpty>
  8:     </dynamic>
  9:     <dynamic prepend=" AND ">
 10:         <isNotEmpty property="groupname">
 11:             password=#password#
 12:         </isNotEmpty>
 13:     </dynamic>
 14:     <dynamic prepend=" AND ">
 15:         <isNotEmpty property="groupname">
 16:             groupname=#groupname#
 17:         </isNotEmpty>
 18:     </dynamic>
 19: </select>
 20: <select id="checkCredentialExtended-2" 
 21:     parameterClass="QueryCondition" resultClass="hashmap">
 22:     SELECT * FROM USER_ACCOUNT
 23:     <dynamic prepend=" WHERE ">
 24:         <isNotEmpty property="username">
 25:             username='$username$'
 26:         </isNotEmpty>
 27:     </dynamic>
 28:     <dynamic prepend=" AND ">
 29:         <isNotEmpty property="groupname">
 30:             password='$password$'
 31:         </isNotEmpty>
 32:     </dynamic>
 33:     <dynamic prepend=" AND ">
 34:         <isNotEmpty property="groupname">
 35:             groupname='$groupname$'
 36:         </isNotEmpty>
 37:     </dynamic>
 38: </select>


 



程式同樣針對兩組 SQL 指令分別嘗試 SQL Injection 的攻擊 (第 22-23, 42-43 行)。



  1: String resource = "SqlMapConfig.xml";
  2: Reader reader = Resources.getResourceAsReader(resource);
  3: SqlMapClient sqlMap = 
  4:     SqlMapClientBuilder.buildSqlMapClient(reader);
  5: 
  6: // it's good, and the user is found
  7: QueryCondition qc = new QueryCondition("JDOE", "TEST", null);
  8: Object o = sqlMap.queryForObject("checkCredentialExtended-1", qc);
  9: System.out.println(o);
 10: 
 11: // it's good, and the user is found
 12: qc = new QueryCondition("JDOE", "TEST", "EMPLOYEE");
 13: o = sqlMap.queryForObject("checkCredentialExtended-1", qc);
 14: System.out.println(o);
 15: 
 16: // it's good, but the user is not found (no such user)
 17: qc = new QueryCondition("JDOE", "TEST", "MANAGER");
 18: o = sqlMap.queryForObject("checkCredentialExtended-1", qc);
 19: System.out.println(o);
 20: 
 21: // it's bad, but the user is *NOT* be found (sanitized by iBatis)
 22: qc = new QueryCondition("JDOE' OR 1=1--'", "1234", null);
 23: o = sqlMap.queryForObject("checkCredentialExtended-1", qc);
 24: System.out.println(o);
 25: 
 26: // it's good, and the user is found
 27: qc = new QueryCondition("JDOE", "TEST", null);
 28: o = sqlMap.queryForObject("checkCredentialExtended-2", qc);
 29: System.out.println(o);
 30: 		
 31: // it's good, and the user is found
 32: qc = new QueryCondition("JDOE", "TEST", "EMPLOYEE");
 33: o = sqlMap.queryForObject("checkCredentialExtended-2", qc);
 34: System.out.println(o);
 35: 		
 36: // it's good, but the user is not found (no such user)
 37: qc = new QueryCondition("JDOE", "1234", "MANAGER");
 38: o = sqlMap.queryForObject("checkCredentialExtended-2", qc);
 39: System.out.println(o);
 40: 		
 41: // it's bad, and the user is found (*SQL Injected*)
 42: qc = new QueryCondition("JDOE' OR 1=1--'", "1234", "MANAGER");
 43: o = sqlMap.queryForObject("checkCredentialExtended-2", qc);
 44: System.out.println(o);


 



我們可以看到第二次的 SQL Injection 攻擊成功 (第 8 行),程式使用的是設定檔中第二個 SQL 指令。針對第一個 SQL 指令的 SQL Injection 並沒有成功 (第 4 行)。



  1: {username=JDOE, groupname=EMPLOYEE, password=TEST, USERID=2}
  2: {username=JDOE, groupname=EMPLOYEE, password=TEST, USERID=2}
  3: null
  4: null
  5: {username=JDOE, groupname=EMPLOYEE, password=TEST, USERID=2}
  6: {username=JDOE, groupname=EMPLOYEE, password=TEST, USERID=2}
  7: null
  8: {username=JDOE, groupname=EMPLOYEE, password=TEST, USERID=2}


 



前述是 iBatis/MyBatis 2.x 的例子,而 MyBatis 3.x 雖然與 iBatis/MyBatis 2.x 不相容,但是問題的本質卻是完全一樣的。也就是當透過設定檔指定 SQL 指令時,MyBatis 3.x 一樣有可能遭遇 SQL Injection 的攻擊。除了利用設定檔的方式外,MyBatis 3.x 還可以透過所謂 Mapper class 的機制來指定 SQL 指令,而此一機制一樣有可能遭受 SQL Injection 的攻擊。至於利用 MyBatis 3.x 提供的 SelectBuilder 與 SqlBuilder 所產生的 SQL 指令,因為並不支援以 $ 的方式宣告參數,所以並不會有上述的問題。



因此在使用 iBatis/MyBasit 的 inline 參數宣告方式時,請記得盡量採用 # 的方式加以宣告。如果確有使用 $ 的需求,則需務必做好參數的嚴格檢查,以免產生 SQL Injection 的危害。而 iBatis/MyBatis 除了使用 SQL 指令之外,也可以呼叫預儲程序 (Stored Procedure)。在這種情況下,如何避免 Stored Procedure 遭受 SQL Injection 的攻擊,就成了預儲程序自己的責任了。



2010年10月16日 星期六

[資安觀念] SaaS 與 DoS/DDoS

iStock_000005760185XSmallDoS/DDoS (阻斷服務攻擊) 對許多人來說,早已經是耳熟能詳的名詞。以 DoS 的定義來說,只要能夠讓服務中斷,不管用什麼方法都可以算是 DoS 的攻擊。所以,拿榔頭把連結伺服器的路由器給敲爛,絕對也是 DoS 攻擊的一種。這樣的攻擊方式除了被逮捕的風險高了許多,而且在效率上也不夠好。所以除非你跟某家企業有深仇大恨,而且報著必死的決心,不然請勿輕易嘗試此一作法。也因此以網路為主的遠端 DoS/DDoS 才是主要的攻擊手法,駭客想盡一切辦法讓使用者無法連結到某個企業的網路服務,像是把網路頻寬或伺服器的運算資源消耗殆盡。

而這一兩年極為熱門的話題 - SaaS (或大家比較常聽到的雲端運算),除了它本身產生的資安考量外,SaaS 與 DoS/DDoS 還有其他的關聯。首先,SaaS 的架構讓駭客擁有了更多且更便宜 (甚至免費) 的資源來對攻擊目標的資源進行消耗的行為。嚴格來說,甚至可以說是以 Botnet 為主的地下經濟本身就是一種 SaaS 的服務,只是買方甚至連軟體都不用操作就可以收到結果。另外之前提到利用 SaaS 架構提供破解 WPA 的服務,雖然不是用來進行 DoS/DDoS 的攻擊,卻也是利用 SaaS 來增強破壞力的應用。

除此之外,隨著 SaaS 架構的風行,有人預計利用 DoS/DDoS 攻擊手法來對企業進行威脅的事件將會持續增加。原因之一是很多 SaaS 架構採用用多少算多少的計費方式,這樣的方式也就表示用的越多,帳單的金額就越高。在此情況下,DoS/DDoS 攻擊就算無法癱瘓企業的網路服務,也將使得帳單金額大增。因此,駭客可以利用這樣的預期損失而對企業進行事前的勒索。我個人覺得這雖然有其道理,但是 SaaS 的服務通常可以設定帳單金額的上限,所以在最差的情況下,被攻擊企業的網路服務也”只是”完全停擺,並不會有金額大爆炸的情況發生。倒是 SaaS 的模式,讓企業遭受 DoS/DDoS 攻擊時的損失容易量化,因此方便駭客對勒索金額進行”合理的”喊價。不論如何,當 SaaS 讓資源的應用變成價格化後,DoS/DDoS 攻擊已經不再只是單純的”給他死”,而有更多的操作空間可以讓駭客獲取利益。這年頭,當駭客不但要有技術能力,還得有商業頭腦才行。

 

相關連結:

2010年10月12日 星期二

[新聞時事] 運用繪圖處理器 (GPU) 可大幅提昇解密資料的速度

images雖然大多數的人跟我一樣,都不是密碼學的專家,但是密碼學的應用卻充斥在我們的日常生活之中。從你買東西時使用信用卡付賬,到在網路上進行購物,甚至是你每天登入到作業系統,都跟密碼學有關。密碼學的中文說法,其實很容易讓人產生誤解,因為密碼學講的並不是我們平常登入各式系統所使用的密碼,而是加解密的演算法。雖然比較嚴謹的系統都會將密碼做加密的保護,但是兩者之間卻沒有必然的關係,而密碼學就是一門充滿了各式各樣演算法的學問。這些複雜的演算法,往往都不是由資訊專家所提出,而是數學家所苦心研究出來地成果,並經過外界公開的審視。儘管演算法在被公開接受並採用前往往已經經過了仔細的評估,但是卻沒有一個演算法本身是絕對安全的。所有的演算法至少都會遭遇一種手法的攻擊,那就是所謂的暴力攻擊法。簡單的說,暴力攻擊法就是利用強大的運算能力,將加密或解密的各種可能性逐一計算,最後獲得所需資訊的手法。既然所有的演算法都會遭受暴力攻擊法的攻擊,那麼為什麼密碼學的應用還會被大量採用在我們日常生活當中呢?原因很簡單,就是因為暴力破解法所需的強大運算往往在現今的技術下是無法有效地加以實現,也因此我們的資料”暫時”是安全的。

除非是很特殊的組織或個人,否則一般駭客能夠用來破解加密資料的工具還是最常見的個人電腦設備。傳統上我們常用的電腦設備主要透過中央處理器  (CPU) 處理各式各樣的運算需求,而中央處理器本身是一個通用的處理器,所以對於處理大量的數學運算並不在行。儘管隨著技術的進步,中央處理器的處理速度已經大為提昇,但是先天的定位註定中央處理器一旦遇到複雜的數學運算就是只能吃憋。

而電腦內部除了中央處理器之外,往往還有另外一個處理器,稱之為繪圖處理器 (GPU)。繪圖處理器顧名思義就是用來處理圖型顯示的運算,而圖型顯示正需要大量的數學運算,所以繪圖處理器天生就是數學運算的高手。其實繪圖處理器的存在由來已久,但是因為市場需求的關係,之前繪圖處理器的發展速度並不如中央處理器那般快速。而近年來遊戲與網路多媒體的蓬勃應用,讓繪圖能力的重要性越趨重要。再加上技術的成熟,因此繪圖處理器的處理速度大幅增進,尤其是這樣的技術應用已經相當普及,而不像過去繪圖處理器是有錢人的專屬玩具。也因為這樣的發展,所以繪圖處理器的強大數學運算能力不但可以用在繪圖功能上,更有人開始發揮創意並開發新的應用,而其中一個應用就是用來破解加密的資料。一旦這樣的運用普及化,我們對於演算法的安全性就必須重新加以思考,因為所謂的”暫時”安全可能已經不再是”暫時”,而僅僅只是一瞬間的時間。至於在實際上會有多大的影響層面,就讓我們持續觀察吧。

 

相關連結:

2010年10月8日 星期五

[技術分享] 用了參數化查詢就可以對 SQL Injection 高枕無憂? NO!

sql-injection在上個月的一篇文章中,我們看到 WhiteHat Security 對於避免 SQL Injection 的危害提出了 10 個手法。其中第 7 個手法,使用”參數化的查詢 (在 Java 的程式語言中主要是透過 PreparedStatement 來提供) 並避免使用動態式的查詢”對許多人來說應該算是老生常談了。事實上,程式內未採用參數化查詢大多不是因為技術上的考量,因為除了極少數的情況 (通常與效能有關) 外,參數化查詢都是可以順利運作的。在無法採用參數化查詢的原因中,最常見的情況就是使用了預儲程序 (Stored Procedure)。預儲程序如果小心使用,對於 SQL Injection 其實也有一定的防護能力。但是預儲程序畢竟不像參數化查詢那般嚴謹,所以提供這些功能的程式碼無法事先做太多的防範措施,大多數防護的責任還是落在系統開發者自行開發的程式上。而參數化查詢因為會嚴格限制每個參數的型別,所以提供此一功能的程式就可以做好萬全的準備,讓系統開發者在撰寫自己的程式時不需太過擔心。所以除非被限制必須使用預儲程序來存取資料,否則我們就應該採用參數化查詢來存取資料。

參數化查詢與預儲程序之間的取捨,往往還有許多其他非技術的因素,但是這並不是我這篇文章的重點,所以這個問題就在此打住。當我們確實採用了參數化的查詢後,是否就可以對 SQL Injection 高枕無憂了呢?很可惜的是,答案依舊是否定的。如果我們仔細審視第一段的藍色文字,可以發現後半段是避免使用動態式的查詢。而前、後段文字使用”並”加以連結,所以一旦使用者違反了後半段文字的限制,即使使用參數化查詢依舊會遭受 SQL Injection 的攻擊。因為這點對於部分的系統開發者來說或許並不是那麼熟悉,所以接下來我將以實際的例子 (Java 語法) 來加以說明。我假設我們擁有一個稱為 users 的資料表,內含 name 與 password 兩個欄位,分別表示使用者的姓名與密碼。而在此資料表中有一個使用者的姓名與密碼分別為 ‘cyril wang’ 與 ’1234’。

1. 這是 PreparedStatement 的標準用法,也就是利用 setXXX 的方式 (程式碼第 9, 10 行) 來明確指定資料的型別。因為使用者輸入 (參見備註1) 的帳號與密碼相對應,所以此程式執行完後可以取得該使用者的ID。

備註1:雖然在所有的程式範例中, userInputForUsername 與 userInputForPassword 兩個變數的值是固定的,但是在一般程式中此兩變數的值通常來自於一般使用者 (或駭客) 的輸入。

  1: Class.forName("com.mysql.jdbc.Driver");
  2: Connection c =
  3:     DriverManager.getConnection(url, account, password);
  4: 
  5: String userInputForUsername = "cyril wang";
  6: String userInputForPassword = "1234";
  7: String sql = "SELECT * FROM users WHERE name=? AND password=?";
  8: PreparedStatement stmt = c.prepareStatement(sql);
  9: stmt.setString(1, userInputForUsername);
 10: stmt.setString(2, userInputForPassword);
 11: ResultSet rs = stmt.executeQuery();
 12: if (rs.next()) {
 13: 	System.out.println("Your ID is " + rs.getInt(1));
 14: } else {
 15: 	System.out.println("Get out!");
 16: }


 



2. 當使用者 (或駭客) 嘗試輸入 SQL Injection 的攻擊字串,PreparedStatement 的實作會將字串內容加以過濾。因為帳號與密碼無法對應,因此無法取得使用者的 ID。



  1: Class.forName("com.mysql.jdbc.Driver");
  2: Connection c =
  3:     DriverManager.getConnection(url, account, password);
  4: 
  5: String userInputForUsername = "cyril wang' OR 1=1--'";
  6: String userInputForPassword = "aaaa";
  7: String sql = "SELECT * FROM users WHERE name=? and password=?";
  8: PreparedStatement stmt = c.prepareStatement(sql);
  9: stmt.setString(1, userInputForUsername);
 10: stmt.setString(2, userInputForPassword);
 11: ResultSet rs = stmt.executeQuery();
 12: if (rs.next()) {
 13: 	System.out.println("Your ID is " + rs.getInt(1));
 14: } else {
 15: 	System.out.println("Get out!");
 16: }


 



3. 仔細看程式碼第 5 行到第 11 行的寫法,這種常見於一般 SQL 查詢的用法對 PreparedStatement 來說也是合法的,而這種用法就稱為動態式查詢。因為使用者輸入的對應的帳號與密碼,因此可以取得 ID。



  1: Class.forName("com.mysql.jdbc.Driver");
  2: Connection c = 
  3:     DriverManager.getConnection(url, account, password);
  4: 
  5: String userInputForUsername = "cyril wang";
  6: String userInputForPassword = "1234";
  7: String sql = "SELECT * FROM users WHERE name = '" +
  8: 	         userInputForUsername +
  9: 	         "' AND password ='" +
 10: 	         userInputForPassword +
 11: 	         "'";
 12: PreparedStatement stmt = c.prepareStatement(sql);
 13: ResultSet rs = stmt.executeQuery();
 14: if (rs.next()) {
 15: 	System.out.println("Your ID is " + rs.getInt(1));
 16: } else {
 17: 	System.out.println("Get out!");
 18: }


 



4. 使用者 (或駭客) 再次嘗試輸入 SQL Injection 攻擊字串,而這將是一次成功的攻擊。儘管沒有輸入對應的密碼,使用者 (或駭客) 依舊取得 ID。



  1: Class.forName("com.mysql.jdbc.Driver");
  2: Connection c = 
  3:     DriverManager.getConnection(url, account, password);
  4: 
  5: String userInputForUsername = "cyril wang' OR 1=1--'";
  6: String userInputForPassword = "aaaa";
  7: String sql = "SELECT * FROM users WHERE name = '" +
  8: 	         userInputForUsername +
  9: 	         "' AND password ='" +
 10: 	         userInputForPassword +
 11: 	         "'";
 12: PreparedStatement stmt = c.prepareStatement(sql);
 13: ResultSet rs = stmt.executeQuery();
 14: if (rs.next()) {
 15: 	System.out.println("Your ID is " + rs.getInt(1));
 16: } else {
 17: 	System.out.println("Get out!");
 18: }


 



從上面這些簡單的範例程式中,我們可以看到如果在參數化查詢指令中使用了動態式的查詢,那麼程式依舊無法避免 SQL Injection 的問題,也因此我們應該盡力避免使用動態式的查詢。如果動態式的查詢確實有其必要性,做好輸入的檢查 (Input Validation & Sanitization) 就是不可避免的手法。其實以安全的角度來看,不管是不是使用參數化查詢,都應該做好輸入的檢查動作才是。



以目前常見的兩大網站安全問題來看,SQL Injection 的問題與解決之道相對比 Cross-Site Scripting 單純多了,因此我們實在沒有理由讓 SQL Injection 一直肆虐下去。對於已經完成或開發中的程式,採用自動化的白箱工具可以有效地找出所有有問題的程式碼,以避免花費大量時間進行人工搜尋。而透過對系統開發人員的教育訓練,則可以讓 SQL Injection 的問題防範於未然,此時白箱工具則可由原先找出問題的角色轉換為確保安全品質與稽核的角色。正確的觀念 (從單位主管到系統開發人員) 再加上合適的工具,SQL Injection 的避免並沒有想像中的那麼遙不可及。



備註2: 在多層次防禦的概念下,除了使用參數化查詢外,其他手法還是有其必要性,而前述 WhiteHat Security 的文章可作為參考。但是千萬切記勿落入”這就是全部可用的手法”的誤解中,以免因為疏忽而產生危害。

About