JSP應用的安全問題_JSP編程
來源:互聯網
上載者:User
一、概述
當網路編程越來越方便,系統功能越來越強大,安全性卻指數倍地下降。這恐怕就是網路編程的不幸和悲哀了。各種動態內容產生環境繁榮了WWW,它們的設計目標就是為了給開發人員更多的力量,給終端使用者更多的方便。正因為如此,系統設計師和開發人員必須明確地把安全問題作為一個考慮因素,事後追悔很難奏效。
從安全的角度來看,伺服器端WWW應用的弱點來源於各種各樣的互動能力和傳輸通道。它們是攻擊者直接可以用來影響系統的工具。在攻擊者尋找和利用系統安全性漏洞時,它們總是給系統安全帶來壓力。對付所有這些攻擊的通用防衛策略就是所謂的輸入驗證。
從同一層面考慮,主要有兩種設計上的錯誤導致了安全方面的問題:
· 拙劣的存取控制,以及
· 對部署環境作隱含的假設。
在有關安全的文獻中,針對存取控制問題有著許多深入的分析。這裡我們要討論的是底層實現(代碼和配置)上的安全管理問題,討論的環境是JSP。或者說,我們將討論惡意的使用者輸入偽裝自身以及改變應用預定行為的各種方法,考慮如何檢驗輸入合法性以及減少對資訊和應用介面的不受歡迎的探測。
二、JSP概述
JSP技術允許把Java代碼邏輯嵌入到HTML和XML文檔之內,為建立和管理動態WWW內容帶來了方便。JSP頁面由JSP引擎預先處理並轉換成Java Servlet,此後如果出現了對JSP頁面的請求,Web伺服器將用相應的Servlet輸出結果作為應答。雖然JSP和Servlet在功能上是等價的,但是,和Servlet相比,JSP的動態內容產生方法恰好相反:JSP是把Java代碼嵌入到文檔之中,而不是把文檔嵌入到Java應用之中。為訪問外部功能和可重用的對象,JSP提供了一些用來和JavaBean組件互動的額外標記,這些標記的文法和HTML標記相似。值得注意的是:HTML文法屬於JSP文法的一個子集(一個純HTML文檔是一個合法的JSP頁面),但反過來不一定正確。特別地,為了便於動態產生內容和格式,JSP允許在標記之內嵌入其他標記。例如,下面是一段合法的JSP代碼:
<A HREF = "<%= request.getRemoteUser() %>">
從本文後面可以看到,這種結構增加了安全問題的複雜性。
與CGI相比,JSP具有更好的效能和會話管理(即工作階段狀態持久化)機制。這主要通過在同一個進程之內運用Java線程處理多個Servlet實現,而CGI一般要求為每一個請求分別建立和拆除一個進程。
三、安全問題
由於完全開放了對伺服器資源的訪問,從JSP頁面轉換得到的不安全Servlet可能給伺服器、伺服器所在的網路、訪問頁面的客戶機之中的任意一個或全體帶來威脅,甚至通過DDoS或蠕蟲分布式攻擊,還可能影響到整個Internet。人們往往假定,Java作為一種型別安全的、具有垃圾收集能力的、具有沙箱(Sandbox)機制的語言,它能夠奇蹟般地保證軟體安全。而且事實上,許多在其他語言中存在的低層次安全問題,比如緩衝或堆溢出,很少給Java程式帶來危害。然而,這並不意味著人們很難寫出不安全的Java程式,特別是對編寫Servlet來說。驗證輸入和控制對資源的訪問是始終必須關注的問題。另外,JSP的體繫結構相當複雜,其中包含許多相互協作的子系統。這些子系統之間的互動常常是安全隱患的根源。除此之外,雖然現在所有的JSP實現都圍繞著Java,但JSP規範允許幾乎所有其他語言扮演這個角色。這樣,這些替代語言的安全問題也必須加以考慮。
簡而言之,在JSP系統中產生安全性漏洞的機會是相當多的。下面我們將討論它們中最常見的一部分。
四、非置信使用者輸入的一般問題
非置信的使用者輸入(Untrusted User Input)實際上包含了所有的使用者輸入。使用者輸入來源於用戶端,可以通過許多不同的途徑到達伺服器端,有時甚至是偽裝的。為JSP伺服器提供的使用者輸入包括(但不限於):
· 請求URL的參數部分,
· HTML表單通過POST或GET請求提交的資料,
· 在用戶端臨時儲存的資料(也就是Cookie),
· 資料庫查詢,
· 其它進程設定的環境變數。
使用者輸入的問題在於,它們由伺服器端的應用程式解釋,所以攻擊者可以通過修改輸入資料達到控制伺服器脆弱部分的目的。伺服器的脆弱部分常常表現為一些資料訪問點,這些資料由使用者提供的限定詞標識,或通過執行外部程式得到。
JSP能夠調用儲存在庫裡面的本地代碼(通過JNI)以及執行外部命令。類Runtime提供了一個exec()方法。exec()方法把它的第一個參數視為一個需要在獨立的進程中執行的命令列。如果這個命令字串的某些部分必須從使用者輸入得到,則使用者輸入必須先進行過濾,確保系統所執行的命令和它們的參數都處於意料之內。即使命令字串和使用者輸入沒有任何關係,執行外部命令時仍舊必須進行必要的檢查。在某些情況下,攻擊者可能修改伺服器的環境變數影響外部命令的執行。例如,修改path環境變數,讓它指向一個惡意的程式,而這個惡意程式偽裝成了exec()所調用程式的名字。為了避免這種危險,在進行任何外部調用之前顯式地設定環境變數是一種較好的習慣。具體的設定方法是:在exec()調用中,把一個環境變數的數組作為第二個參數,數組中的元素必須是name=value格式。
當使用者輸入用來標識程式開啟的任意類型的輸入/輸出流時,類似的問題也會出現。訪問檔案、資料庫或其他網路連接時不應該依賴於未經檢驗的使用者輸入。另外,開啟一個流之後,把使用者輸入直接發送給它是很不安全的。對於SQL查詢來說這一點尤其突出。下面訪問JDBC API的JSP代碼片斷很不安全,因為攻擊者可以在他提交的輸入中嵌入分隔命令的字元,從而達到執行危險命令的目的:
<%@ page import="java.sql.*" %> <!-- 這裡加上一些開啟SQL Server串連的代碼 --> <% Statement stmt = connection.getStatement(); String query = "SELECT * FROM USER_RECORDS WHERE USER = " + request.getParameter("username"); ResultSet result = Statement.executeQuery(query); %>
如果username包含一個分號,例如:
http://server/db.jsp? username=joe;SELECT%20*%20FROM%20SYSTEM_RECORDS
一些版本的SQL Server會忽略整個查詢,但還有一些版本的SQL Server將執行兩個命令。如果是後者,攻擊者就可以訪問原本沒有資格訪問的資料庫資源(假定Web伺服器具有存取權限)。
進行適當的輸入檢驗可以防止這類問題出現。
五、輸入檢驗
從安全的角度來看,輸入檢驗包括對來自外部資料源(非置信資料來源,參見前面說明)的資料進行語法檢查,有時還要進行語義檢查。依賴於應用的關鍵程度和其他因素,作為輸入檢驗結果而採取的動作可能是下面的一種或者多種:
· 忽略文法上不安全的成分,
· 用安全的代碼替換不安全的部分,
· 中止使用受影響的代碼,
· 報告錯誤,
· 啟用一個入侵監測系統。
輸入檢驗可以按照以下兩種模式之一進行:列舉不安全的字元並拒絕它們;定義一組安全的字元,然後排除和拒絕不安全的字元。這兩種模式分別稱為正向和反向輸入過濾。一般地,正向輸入過濾更簡單和安全一些,因為許多時候,要列舉出伺服器端應用、用戶端瀏覽器、Web伺服器和作業系統可能誤解的字元並不是一件容易的事情。
請參見本文下面“通過嵌入標記實現的攻擊”部分中輸入檢驗的例子,這個例子示範了如何避免誤解惡意提交的輸入內容。
六、GET請求和Cookie中的敏感性資料
就象CGI協議所定義的,把請求資料從用戶端傳輸到伺服器端最簡單的方法是GET要求方法。使用GET要求方法時,輸入資料附加到請求URL之後,格式如下:
URL[?name=value[&name=value[&...]]]
顯然,對於傳輸敏感性資料來說,這種編碼方式是不合適的,因為通常情況下,整個URL和請求字串都以明文方式通過通訊通道。所有路由裝置都可以和伺服器一樣記錄這些資訊。如果要在客戶請求中傳輸敏感性資料,我們應該使用POST方法,再加上一種合適的加密機制(例如,通過SSL串連)。從JSP引擎的角度來看,在很大程度上,使用哪種傳輸方法無關緊要,因為兩者的處理方式一樣。
在WWW的發展過程中,Netscape引入了Cookie的概念。Cookie是伺服器儲存到用戶端的少量資訊,伺服器提取這些資訊以維持工作階段狀態或跟蹤用戶端瀏覽器的活動。JSP提供了一個response隱含對象的addCookie()方法,用來在用戶端設定Cookie;提供了一個request()對象的getCookie()方法,用來提取Cookie的內容。Cookie是javax.servlet.http.Cookie類的執行個體。由於兩個原因,如果把敏感性資料儲存到Cookie,安全受到了威脅:第一,Cookie的全部內容對用戶端來說都是可見的;第二,雖然瀏覽器一般不提供偽造Cookie的能力,但沒有任何東西能夠阻止使用者用完全偽造的Cookie應答伺服器。
一般而言,任何用戶端瀏覽器提交的資訊都不可以假定為絕對安全。
七、通過嵌入標記實現的攻擊
CERT Advisory CA-2000-02描述了客戶在請求中嵌入惡意HTML標記的問題。這個問題一般被稱為“cross site scripting”問題,但它的名字有些用詞不當,因為它不僅僅和指令碼有關,同時,它和“跨越網站”(cross site)也沒有什麼特別的關係。不過,這個名字出現時,問題還沒有被人們廣泛瞭解。
這種攻擊通常包含一個由使用者提交的病態指令碼,或者包含惡意的HTML(或XML)標記,JSP引擎會把這些內容引入到動態產生的頁面。這種攻擊可能針對其他使用者進行,也可能針對伺服器,但後者不太常見。“cross site scripting”攻擊的典型例子可以在論壇伺服器上看到,因為這些伺服器允許使用者在自己提交的文章中嵌入格式化標記。通常,被濫用的標記是那些能夠把代碼嵌入到頁面的標記,比如<SCRIPT>、<OBJECT>、<APPLET>和<EMBED>。另外還有一些標記也會帶來危險,特別地,<FORM>可能被用於欺騙瀏覽者暴露敏感資訊。下面是一個包含惡意標記的請求字串的例子:
http://server/jsp_script.jsp?poster=evilhacker& message=<SCRIPT>evil_code</SCRIPT>
要防止出現這種問題當然要靠輸入檢查和輸出過濾。這類檢查必須在伺服器端進行,不應依賴於用戶端指令碼(比如JavaScript),因為沒有任何東西能夠阻止使用者逃避用戶端檢驗過程。
下面的代碼片斷示範了如何在伺服器端檢查嵌入的標記:
<!-- HTML代碼結束 --><% String message = request.getParameter("message"); message = message.replace ('<','_'); message = message.replace ('>','_'); message = message.replace ('"','_'); message = message.replace (''','_'); message = message.replace ('%','_'); message = message.replace (';','_'); message = message.replace ('(','_'); message = message.replace (')','_'); message = message.replace ('&','_'); message = message.replace ('+','_'); %><p>你提交的訊息是:<hr/><tt><%= message %></tt><hr/></p><!-- 下面加上其他HTML代碼 -->
由於要列舉出所有不合法的字元比較困難,所以更安全的方法是進行正向過濾,即除了那些確實允許出現的字元之外(例如[A-Za-z0-9]),丟棄(或者轉換)所有其他字元。
八、關於JavaBean的說明
JSP按照JavaBean規範描述的一系列約定,在JSP頁面中快速、方便地訪問可重用的組件(Java對象)。每個JavaBean組件封裝了一些可以不依賴於調用環境而獨立使用的資料和功能。Bean包含資料成員(屬性),並通過Get和Set方法實現訪問這些屬性的標準API。
為快速初始化指定Bean的所有屬性,JSP提供了一種捷徑,即在查詢字串中提供name=value對,並讓它匹配目標屬性的名字。考慮下面這個使用Bean的例子(以XML格式顯示):
<jsp:useBean id="myBasket" class="BasketBean"> <jsp:setProperty name="myBasket" property="*"/> <jsp:useBean> <html> <head><title>你的購物籃</title></head> <body> <p> 你已經把商品: <jsp::getProperty name="myBasket" property="newItem"/> 加入到購物籃 <br/> 金額是$ <jsp::getProperty name="myBasket" property="balance"/> 準備 <a href="checkout.jsp">付款</a>
注意在setProperty方法調用中使用的萬用字元號“*”。這個符號指示JSP設定查詢字串中指定的所有屬性的值。按照本意,這個指令碼的調用方式如下:
http://server/addToBasket.jsp?newItem=ITEM0105342
正常情況下,HTML表單構造的查詢字串就是這種形式。但問題在於,沒有任何東西能夠防止使用者佈建balance屬性:
http://server/addToBasket.jsp? newItem=ITEM0105342&balance=0
處理頁面的<jsp:setProperty>標記時,JSP容器會把這個參數映射到Bean中具有同樣名字的balance屬性,並嘗試把該屬性設定為0。
為避免出現這種問題,JSP開發人員必須在Bean的Set和Get方法中實現某種安全措施(Bean必須對屬性進行強制的存取控制),同時,在使用<jsp:setProperty>的萬用字元時也應該小心謹慎。
九、實現上的漏洞與原始碼安全
無論是哪一種JSP實現,在一定的階段,它們的某些版本都會出現給系統帶來危險的安全隱患,即使JSP開發人員遵從了安全編程慣例也無濟於事。例如,在Allaire的JRun的一個版本中,如果請求URL包含字串“.jsp%00”作為JSP指令碼副檔名的一部分,伺服器不會忽略null位元組,它會把頁面視為一個靜態非JSP頁面之類的東西。這樣,伺服器會請求作業系統開啟該頁面,而這時null位元組卻被忽略,結果提供給使用者的是JSP頁面的原始碼而不是頁面的執行結果。
類似地,Tomcat的一個版本也有一個安全隱患。只要請求類如下面的格式,它會讓攻擊者看到JSP頁面的原始碼:
http://server/page.js%2570
這裡的騙局在於,%25是URL編碼的“%”,而70是“p”的十六進位值。Web伺服器不會調用JSP處理器(因為URL沒有以“.jsp”結尾),但靜態檔案處理器會設法把URL映射到正確的檔案名稱字(再一次解碼URL)。
另外,許多Web伺服器和JSP實現都帶有示範指令碼,這些示範指令碼常常包含安全隱患。在把伺服器部署到一個不無惡意的環境(即Internet)之前,禁止對所有這些指令碼的訪問有利於安全。
簡而言之,JSP開發人員應該清楚:在自己正在開發的平台上,當前有哪些安全隱患。訂閱BUGTRAQ和所有供應商提供的郵件清單是跟蹤這類資訊的好方法。
結束語
JSP和任何其他強大的技術一樣。如果要保證被部署系統的安全和可靠,應用JSP時必須小心謹慎。在這篇文章中,我們簡要地討論了JSP指令碼中常常出現的代碼和配置級安全問題,提出了降低由此帶來的安全風險的建議。