00
碎碎念的背景
之前学的是计科专业,高级程序语言学的是 java,工作并没有找开发,但是安全和开发相关的话也可以试试代码审计吧,所以找了一个 CMS,关于 Spring MVC,开发者在 2016 年左右开发的,漏洞其实也蛮多。找这个是因为我看网上也有相关的 pdf ,想着要是自己不会还能跟着别人的步骤走一走,但是自己找到了,那也就写出来。
这个 CMS 有个坑,有时候登录的时候会提示系统繁忙,这时候不用在首页进行登录,在课程或者其他页面进行登录即可。具体原因我也还没去看。
关于 Spring MVC 的相关知识,可以去 b 站搜 java 进行学习。简单来说是将应用程序分为 Controller 、Model 、View 三层,其中 Controller 作为接收客户端请求信息的入口,Model 作为每个业务功能模块的实体,每个业务功能都可抽象为一个实体/对象,(也就是面向对象的思想)Controller 接收客户端请求后,进行业务逻辑处理,这其中会用到 Service 层及 DAO 层(为了将业务逻辑解耦合而出现的),最终会将处理结果也即是业务数据,也就是 Model,返回给前端 View 。最终回显在客户端其实是 View + Model 的结果(所以有时候输入的数据不加过滤输出就会造成 xss 漏洞)。
下载:http://down.admin5.com/jsp/132874.html
配置:下载包中提供安装教程,按照安装教程安装即可。
01
逻辑漏洞
这个项目模块太多了,所以一开始我从用户个人信息那块尝试。(当然一开始我看了他的 springmvc.xml 配置文件,看了他的拦截器的编写,但是我可能太菜了,并没有发现什么不当的配置;而且不知道代码审计从哪审起,所以我想着死磕吧,从头开始 -- 从第一个 controller 文件开始,但是发现有点低效,为了理清项目的大体结构,所以我又去看了他的每个 entity 文件,因为知道了他的实体就知道他有哪些业务功能,以及他们之间的关系。但是我慢慢理发现好多啊,然后我就干脆从业务开始了 orz)
注册后登陆,来到个人资料这,可以修改用户信息,修改并抓取数据包。
url 为 /uc/updateUser ,在 UserController 中找到这一入口。
会发现这个后台代码实现过程中,并没有判断 User 的“合法性”,而此处的 User 为前端传入的参数进行封装的,接下来 userService 就去对数据库进行操作,更改用户数据信息,故此处存在一个越权漏洞,当然,userId 作为 User 的主键,此处更改 userId,即可更改对应 User 的个人信息。
这时候再去登录 userId 为 68 的用户,信息就已经被更改了。当然这里不能更改密码啥的,因为当前业务的 sql 语句中没有添加对应的操作字段。
这里还有一个需要注意的地方,更改某些 id 的返回信息是不一样的,有的会提示系统繁忙,虽然...但是数据库中个人信息还是更改了。
同时如果返回修改成功的消息,那么刷新当前页面,会发现已经登陆到当前修改的 id 的账户了。这是个很奇葩的现象,我们可以来逐步分析一下。以及前面提到的会提示系统繁忙也就知道了。
在上面代码中修改完成之后,有一个缓存用户的过程。正是这个过程,使得其刷新时登录到了修改的 id 的账户。跟进 setLoginInfo 方法。
在设置用户的登录时间戳的时候,是通过 user.getUserId 来从缓存中获取对应的用户信息(用户登录时会缓存当前的登录时间戳,也是将 userId 作为 key 存入缓存),而这时的 user 也正是我们前面通过 post 传输过来的,可由用户控制,后台代码未审查的 user 。接下来 if 条件语句将 uuid 也即是 cookie 中 inxeduweb_user_login_ 字段的值,和 user 绑定起来,这时候 uuid 作为 存入的 element 的 key,value 即为 user 。再去看其他的业务功能代码会发现判断当前登录用户都是通过请求头 cookie 中 inxeduweb_user_login_ 字段值来判断的,而此时其绑定的 user 可以通过 /uc/updateUser 更改个人资料处,在 post 数据中设置 userId 为目标用户 id 来更改。那么也就是说我们可以登录任意用户了。
这里说的登录任意用户其实是不严谨的,嘿嘿,还记得前面有说还有些情况会返回系统繁忙吗?这种情况是不能登录上去的。这种情况会出现在从未登录过当前系统的用户中,因为这种情况下就没有将其的 userId 作为 key 存入缓存,在 ehcache-2.10.1.jar!\net\sf\ehcache\util\concurrent\ConcurrentHashMap.class 的 get 方法中去获取对应的缓存也就会爆出异常,从而不能完成接下来的代码功能。
02
SQL 注入
这个我没有一个个去看他的代码,因为他用的 mybatis 框架,我们可以去搜 ${ 这种形式来找 sql 注入(其实是我懒了)。
在 mybatis 中 #{userId} 这种形式表示采用了类似预编译方式,将 userId 作为一个字符串代入 sql 语句中,不会影响原本的 SQL 语句,而 ${userId} 这种形式则表示将 userId 的值直接拼接到 sql 语句中,可能造成 sql 注入。
那为什么不全都使用 #{} 这种形式呢,因为有时候比如说参数在代入 like、in、order by 等的 sql 语句时,我们使用 #{} 会报错或者与实际业务功能相悖,所以不能使用 #{} 的形式。
回到正题,全局搜索 ${ ,选择 .xml 文件。
这里以 CourseFavoritesMapper.xml 为例(其他的好像都是在 admin 后台目录下的,我找到这个就没看其他的了)。这是在一个删除的 sql 语句中,目标是在where in 子查询里面的 value 值,是删除收藏的业务模块,在 java 文件中全局搜索 deleteCourseFavoritesById 这个 id,可以看到整个从 Controller 到 Service 再到 DAO 的过程。
上图中的 ids 也就是传入到后台 sql 语句中的 value,可以看到是从 url 中 /uc/deleteFaveorite/{ids} 传入的,中间没有做任何过滤,直接传到 sql 语句中进行查询,故此处存在 sql 注入漏洞。如果再往下看会发现还有一个 Controller 文件中也会调用同样的 Service 进行同样的 sql 查询。也就是 AppUserController 文件中,这里传入到后台的 sql 语句的参数是从 request.getParameter("id") 传入进来的。所以两个都可以试一下。好吧,因为我不知道 delete 语句怎么查数据,所以我就交给了 sqlmap 。如果有小伙伴可以教教我的话就更好了。
这里首先用一下第一个入口的 url 来进行注入,但是只能跑出数据库名,表名、列名、字段名都跑不出(其实也不用跑出来,因为 xml 文件里面都有这些信息)。字段里面的数据也跑不出来,是我 sqlmap 哪里用得不对么?而且也不是 dba 权限,这样的话我就想去读文件,读文件也读不出来,有什么别的方法吗?跑出库名的截图:
那再用第二个入口跑一下吧。
把数据跑出来了,看他登陆校验的时候用的 md5 存入到数据库中的,所以去在线解密网站解出他的密码了。读文件还是读不出来,为什么?有什么办法吗?emm 如果有知道的小伙伴请教教我吧。
03
xss 漏洞
这个其实是我自己随便测了一下,试出来了,才去看的源码,这是不是作弊 2333 。
回到正题吧,那这个出现在网站头部的搜索框中。
在 head.jsp 中,可以看到这里是从 queryCourse 中取出 courseName 的。而这个 courseName 也即是我们输入的搜索的课程名,我们可以去看看 /uc/showcoulist 的入口处理代码。
可以看到这里还是通过前台输入的参数来进行封装,到 service 、DAO 都没有进行过滤,再加入到了 Model 中。故此处存在 xss 漏洞。
04
碎碎念的结语
好了,我就找了这三个漏洞,写完这篇文章都有点累,小伙伴们可以自己搭一下。因为没找到其他的 CMS,所以之后可能还会更新这个吧。如果有什么交流的赶紧写在评论区啊,另外欢迎关注我。