記得還是去年,剛到據說是高手云集的威威公司上班的時候,一個新到的同事給我講他花了半天的時間寫,并做了很長時間的實踐,寫了個關于攻擊.jsp頁面的程序。下面我把具體的實現過程和大家分享一下。測試平臺是Tomcat,當然,版本有點低,他的目的只是想證實一下他的某些想法。首先,他在Tomcat的WEB目錄下建立了一個Hello.jsp文件,內容是:
<%out.print(hello);%>
通過IE的正常請求地址為:http://localhost:8080/examples/jsp/hello.jsp,顯示結果為:hello。然后開始具體的攻擊測試。測試時,發出的請求地址為:http://localhost:8080/examples/jsp/////////hello.jsp?,瀏覽器上顯示編譯錯誤,錯誤的原因是500?java.lang.NullPointerException。這個應該是比較常見的錯誤了。現在,恢復正常的請求http://localhost:8080/examples/jsp/hello.jsp,問題就出現了,即出錯,而且所報的錯誤和剛才造成它錯誤的請求是一樣的:“500?java.lang.NullPointerException”。難道是緩存在瀏覽器里了嗎?換臺機器訪問http://192.168.10.188/examples/jsp/hello.jsp。問題依然如故,哎!可憐的Hello.jsp呀!
雖然這個問題有些弱智,不過,他的目的也達到了,即找出“.jsp”流程中存在的一些問題。所以,JSP程序同ASP一樣,還是存在著很多安全上的問題的。因此,對于一心研究論壇或者其他安全信息的朋友來說,要想發現JSP的BUG,了解一些JSP的工作原理是十分重要的。
需要指出的是,雖然是一門網絡編程語言,JSP和PHP、ASP的工作機制還存在很大的區別,首次調用JSP文件時,JSP頁面在執行時是編譯式,而不是解釋式的。首次調用JSP文件其實是執行一個編譯為Servlet的過程。當瀏覽器向服務器請求這一個JSP文件的時候,服務器將檢查自上次編譯后JSP文件是否有改變,如果沒有改變,就直接執行Servlet,而不用再重新編譯,這樣,工作效率得到了明顯提高。這也是目前JSP論壇開始逐漸風靡的一個重要原因。
小提示:Servlet是用Java編寫的Server端程序,它與協議和平臺無關;Servlet運行于Java-enabled?WEB?Server中;Java?Servlet可以動態地擴展Server的能力,并采用請求-響應模式提供WEB服務;最早支持Servlet技術的是JavaSoft的Java?WEB?Server;Servlet的主要功能在于交互式地瀏覽和修改數據,生成動態WEB內容。
說到這里,我們自然就會關心一些JSP的安全問題。一般來說,常見的JSP安全問題有源代碼暴露(包括程序源代碼以明文的方式返回給訪問者,如添加特殊后綴引起jsp源代碼暴露;插入特殊字符串引起Jsp源代碼暴露;路徑權限引起的文件Jsp源代碼暴露;文件不存在引起的絕對路徑暴露問題等)、遠程程序執行類、數據庫如SQL?Server、Oracle?、DB2等的漏洞,操作系統漏洞等。不過,為了突出Jsp的安全問題,本文將結合目前的一些比較流行的Jsp論壇分類闡述和提出解決的建議。為了講解方便,本文還采用一些公開了原代碼的論壇實例代碼,至于安裝軟件版本、操作系統等,可以查看安裝提示。
論壇用戶管理缺陷
為了加強實戰效果,我們可以到http://down.chinaz.com/S/5819.asp這個地址下載一個典型的論壇代碼,根據提示,數據源名稱為yyForum,用戶名為xyworker,密碼:999。到baidu、Google等網站搜索一下,我們可以看到,安裝這個代碼的論壇不少。仔細分析后,可以發現,用戶管理的頁面是user_manager.jsp文件。首先,我們看看這個系統是如何加強它的代碼安全性的。其中,在代碼的開始部分有一個if限制條件,代碼的第三行到第十行具體如下:
<%
if?((session.getValue(UserName)==null)||(session.getValue(UserClass)==null)||(!session.getValue(UserClass).equals(系統管理員)))
%>
其中,Session.getValue表示檢索出Session的值;sendRedirect()執行后,地址欄鏈接會改變,相當于客戶端又重新發了一個get請求,要服務器傳輸另一個文件過來。
下面,我們再來看看修改用戶信息的文件modifyuser_manager.jsp。典型代碼如下:
<%@page?contentType=text/html;?charset=gb2312?language=java?import=java.sql.*,java.util.*??%>
<jsp:useBean?id=yy?scope=page?class=yy.jdbc/>
<%!String?User_Name,User_Password,sql,?User_Sign;%>
<%
User_Name=request.getParameter(name);
//out.println(User_Name);
User_Password=request.getParameter(password);
User_Password=yy.ex_chinese(User_Password);
……
User_Sign=request.getParameter(sign);
User_Sign=yy.ex_chinese(User_Sign);
Connection?con=yy.getConn();
Statement??stmt=con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
sql=update?用戶表?set?用戶密碼='+User_Password+',用戶性別='+User_Sex+',用戶郵箱='+User_Email+',居住地址='+User_Address+',手機號碼='+User_Mobile+',Oicq='+User_Oicq+',出生日期='+User_Birthay+',用戶等級='+User_Class+',簽名='+User_Sign+'?where?用戶名='+User_Name+';
//out.println(sql);
stmt.executeUpdate(sql);
out.println(<font?size=2?color=blue>正在處理你的用戶信息,請稍后...</font><meta?http-equiv='refresh'?content='2;url=user_manager.jsp'>);
%>
<jsp:include?page=inc/online.jsp?flush=true/>
看看這個文件,我們就好像看到了一個簡單的教學文件?,F在,假設管理員提交如下地址,即http://www.51dz.net/bbs/modifyuser_manager.jsp?modifyid=51,需要查看、修改ID為51的用戶的資料(管理員默認的用戶ID為51)。問題就出來了。同樣的,我們可以通過搜索引擎得到如下地址
很明顯,這個用戶管理文件缺乏認證,即使是普通的用戶,甚至包括我們這些搭不上邊的“游客”,也可以直接提交上述請求,從而將其資料一覽無余,更讓人動心的是,密碼也是明文存儲的。
http://www.51dz.net/bbs/modifyuser_manager.jsp同樣是大開山門,直到惡意用戶把數據更新的操作執行完畢,重定向到user_manager.jsp的時候,管理員才會看見那個顯示錯誤的頁面,但這個時候為時已晚,更談不上“亡羊補牢”了。類似的錯誤存在于很多JSP的站點上,面對這樣的論壇,我們能夠放心的說“安全”嗎?解決之道有很多,不過,最基本的要求是為每個需要加身份認證的地方加上身份認證,如果借用別人的代碼,一定要對涉及到用戶管理、密碼認證等重要文件修改一下,照搬雖然省事,但代碼毫無安全性可言。
再就是SQL注入的問題。比如,這個典型的問題:“昨天公司的數據庫被人SQL注入,9萬條記錄都被update了,同事寫了個JSP程序來把他改回來,可是這JSP沒有一點信息返回,看不到進度,在運行些什么都不知道?!辈贿^,這和JSP程序沒有什么必然的聯系,根據國情,國內的網站用ASP+Access或SQLServer的占70%以上,PHP+MySQL占20%,其它的不足10%。因此,ASP的SQL注入比較常見也不足為怪。不過,SQL注入漏洞可謂是“千里之堤,潰于蟻穴”,這種漏洞在網上極為普遍,即使是JSP程序也不能幸免。歸根結底,通常是由于程序員對注入不了解,或者程序過濾不嚴格,或者某個參數忘記檢查導致??纯催@個教材式的JSP程序就可以窺見一般:
Statement?stmt?=?conn.createStatement();?
String?checkUser?=?select?*?from?login?where?username?=?'?+?userName?+?'?and?userpassword?=?'?+?userPassword?+?';?
ResultSet?rs?=?stmt.executeQuery(checkUser);?
if(rs.next())?
response.sendRedirect(SuccessLogin.jsp);?
else?
response.sendRedirect(FailureLogin.jsp);
針對這種情況,如果數據庫里存在一個名叫“Tom”的用戶,那么在不知道密碼的情況下至少有下面幾種方法可以登錄:?
用戶名:Tom????????????密碼:'?or?'a'='a
用戶名:Tom????????????密碼:'?or?1=1/*
用戶名:Tom'?or?1=1/*?????密碼:(任意)