JSP安全編程實(shí)例淺析(中級(jí))2
作者:Stone_Star
 

四、時(shí)刻牢記SQL注入

一般的編程書籍在教初學(xué)者的時(shí)候都不注意讓他們從入門時(shí)就培養(yǎng)安全編程的習(xí)慣。著名的《JSP編程思想與實(shí)踐》就是這樣向初學(xué)者示范編寫帶數(shù)據(jù)庫(kù)的登錄系統(tǒng)的(數(shù)據(jù)庫(kù)為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");


這樣使得盡信書的人長(zhǎng)期使用這樣先天“帶洞”的登錄代碼。如果數(shù)據(jù)庫(kù)里存在一個(gè)名叫“jack”的用戶,那么在不知道密碼的情況下至少有下面幾種方法可以登錄:
用戶名:jack
密碼:' or 'a'='a
用戶名:jack
密碼:' or 1=1/*
用戶名:jack' or 1=1/*
密碼:(任意)

lybbs(凌云論壇)ver 2.9.Server在LogInOut.java中是這樣對(duì)登錄提交的數(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("用戶名或密碼中不能用空格。");


但是我不清楚為什么他只對(duì)密碼而不對(duì)用戶名過(guò)濾星號(hào)。另外,正斜杠似乎也應(yīng)該被列到“黑名單”中。我還是認(rèn)為用正則表達(dá)式只允許輸入指定范圍內(nèi)的字符來(lái)得干脆。

這里要提醒一句:不要以為可以憑借某些數(shù)據(jù)庫(kù)系統(tǒng)天生的“安全性”就可以有效地抵御所有的攻擊。pinkeyes的那篇《PHP注入實(shí)例》就給那些依賴PHP的配置文件中的“magic_quotes_gpc = On”的人上了一課。

五、String對(duì)象帶來(lái)的隱患

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

六、線程安全初探

“JAVA能做的,JSP就能做”。與ASP、PHP等腳本語(yǔ)言不一樣,JSP默認(rèn)是以多線程方式執(zhí)行的。以多線程方式執(zhí)行可大大降低對(duì)系統(tǒng)的資源需求,提高系統(tǒng)的并發(fā)量及響應(yīng)時(shí)間。線程在程序中是獨(dú)立的、并發(fā)的執(zhí)行路徑,每個(gè)線程有它自己的堆棧、自己的程序計(jì)數(shù)器和自己的局部變量。雖然多線程應(yīng)用程序中的大多數(shù)操作都可以并行進(jìn)行,但也有某些操作(如更新全局標(biāo)志或處理共享文件)不能并行進(jìn)行。如果沒(méi)做好線程的同步,在大并發(fā)量訪問(wèn)時(shí),不需要惡意用戶的“熱心參與”,問(wèn)題也會(huì)出現(xiàn)。最簡(jiǎn)單的解決方案就是在相關(guān)的JSP文件中加上: <%@ page isThreadSafe="false" %>指令,使它以單線程方式執(zhí)行,這時(shí),所有客戶端的請(qǐng)求以串行方式執(zhí)行。這樣會(huì)嚴(yán)重降低系統(tǒng)的性能。我們可以仍讓JSP文件以多線程方式執(zhí)行,通過(guò)對(duì)函數(shù)上鎖來(lái)對(duì)線程進(jìn)行同步。一個(gè)函數(shù)加上synchronized 關(guān)鍵字就獲得了一個(gè)鎖。看下面的示例:


                                    public class MyClass{
                                    int a;
                                    public Init() {//此方法可以多個(gè)線程同時(shí)調(diào)用
                                    a = 0;
                                    }
                                    public synchronized void Set() {//兩個(gè)線程不能同時(shí)調(diào)用此方法
                                    if(a>5) {
                                    a= a-5;
                                    }
                                    }
                                    }


但是這樣仍然會(huì)對(duì)系統(tǒng)的性能有一定影響。一個(gè)更好的方案是采用局部變量代替實(shí)例變量。因?yàn)閷?shí)例變量是在堆中分配的,被屬于該實(shí)例的所有線程共享,不是線程安全的,而局部變量在堆棧中分配,因?yàn)槊總€(gè)線程都有它自己的堆棧空間,所以這樣線程就是安全的了。比如凌云論壇中添加好友的代碼:


                                    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傳遞了某個(gè)參數(shù)后他的線程轉(zhuǎn)為睡眠狀態(tài),而參數(shù)被用戶B無(wú)意間修改,造成好友錯(cuò)配的現(xiàn)象。

參考文獻(xiàn):

Jordan Dimov:《JSP Security》
developerWorks:《Java安全性》
徐春金:編寫線程安全的JSP程序