Added new user communication PBA and ZT test, not working yet WIP!
This commit is contained in:
parent
30b74675a5
commit
52a95935c8
|
@ -1,2 +1,3 @@
|
|||
POST_BREACH_COMMUNICATE_AS_NEW_USER = "Communicate as new user"
|
||||
POST_BREACH_BACKDOOR_USER = "Backdoor user"
|
||||
POST_BREACH_FILE_EXECUTION = "File execution"
|
||||
|
|
|
@ -30,6 +30,7 @@ TEST_SCHEDULED_EXECUTION = u"scheduled_execution"
|
|||
TEST_MALICIOUS_ACTIVITY_TIMELINE = u"malicious_activity_timeline"
|
||||
TEST_SEGMENTATION = u"segmentation"
|
||||
TEST_TUNNELING = u"tunneling"
|
||||
TEST_COMMUNICATE_AS_NEW_USER = u"communicate_as_new_user"
|
||||
TESTS = (
|
||||
TEST_SEGMENTATION,
|
||||
TEST_MALICIOUS_ACTIVITY_TIMELINE,
|
||||
|
@ -38,7 +39,8 @@ TESTS = (
|
|||
TEST_MACHINE_EXPLOITED,
|
||||
TEST_DATA_ENDPOINT_HTTP,
|
||||
TEST_DATA_ENDPOINT_ELASTIC,
|
||||
TEST_TUNNELING
|
||||
TEST_TUNNELING,
|
||||
TEST_COMMUNICATE_AS_NEW_USER
|
||||
)
|
||||
|
||||
RECOMMENDATION_DATA_TRANSIT = u"data_transit"
|
||||
|
@ -47,13 +49,15 @@ RECOMMENDATION_USER_BEHAVIOUR = u"user_behaviour"
|
|||
RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic"
|
||||
RECOMMENDATION_SEGMENTATION = u"segmentation"
|
||||
RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES = u"network_policies"
|
||||
RECOMMENDATION_USERS_MAC_POLICIES = u"users_mac_policies"
|
||||
RECOMMENDATIONS = {
|
||||
RECOMMENDATION_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.",
|
||||
RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.",
|
||||
RECOMMENDATION_USER_BEHAVIOUR: u"Adopt security user behavior analytics.",
|
||||
RECOMMENDATION_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.",
|
||||
RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it.",
|
||||
RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible."
|
||||
RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.",
|
||||
RECOMMENDATION_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC only.",
|
||||
}
|
||||
|
||||
POSSIBLE_STATUSES_KEY = u"possible_statuses"
|
||||
|
@ -140,6 +144,16 @@ TESTS_MAP = {
|
|||
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
|
||||
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED]
|
||||
},
|
||||
TEST_COMMUNICATE_AS_NEW_USER: {
|
||||
TEST_EXPLANATION_KEY: u"The Monkey tried create a new user and communicate with the internet from it.",
|
||||
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||
STATUS_FAILED: "Monkey was able to cause a new user to access the network. Your network policies are too permissive - restrict them to MAC only.",
|
||||
STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network."
|
||||
},
|
||||
RECOMMENDATION_KEY: RECOMMENDATION_USERS_MAC_POLICIES,
|
||||
PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS],
|
||||
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
|
||||
},
|
||||
}
|
||||
|
||||
EVENT_TYPE_ISLAND = "island"
|
||||
|
|
|
@ -17,6 +17,40 @@ WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add,
|
|||
|
||||
class BackdoorUser(PBA):
|
||||
def __init__(self):
|
||||
super(BackdoorUser, self).__init__(POST_BREACH_BACKDOOR_USER,
|
||||
linux_cmd=' '.join(LINUX_COMMANDS),
|
||||
windows_cmd=WINDOWS_COMMANDS)
|
||||
linux_cmds, windows_cmds = BackdoorUser.get_commands_to_add_user(
|
||||
WormConfiguration.user_to_add, WormConfiguration.remote_user_pass)
|
||||
super(BackdoorUser, self).__init__(
|
||||
POST_BREACH_BACKDOOR_USER,
|
||||
linux_cmd=' '.join(linux_cmds),
|
||||
windows_cmd=windows_cmds)
|
||||
|
||||
@staticmethod
|
||||
def get_commands_to_add_user(username, password):
|
||||
linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username)
|
||||
windows_cmds = BackdoorUser.get_windows_commands_to_add_user(password, username)
|
||||
return linux_cmds, windows_cmds
|
||||
|
||||
@staticmethod
|
||||
def get_linux_commands_to_add_user(username):
|
||||
linux_cmds = [
|
||||
'useradd',
|
||||
'-M',
|
||||
'--expiredate',
|
||||
datetime.datetime.today().strftime('%Y-%m-%d'),
|
||||
'--inactive',
|
||||
'0',
|
||||
'-c',
|
||||
'MONKEY_USER',
|
||||
username]
|
||||
return linux_cmds
|
||||
|
||||
@staticmethod
|
||||
def get_windows_commands_to_add_user(password, username):
|
||||
windows_cmds = [
|
||||
'net',
|
||||
'user',
|
||||
username,
|
||||
password,
|
||||
'/add',
|
||||
'/ACTIVE:NO']
|
||||
return windows_cmds
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
import os
|
||||
import random
|
||||
import string
|
||||
import subprocess
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32process
|
||||
import win32security
|
||||
|
||||
from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER
|
||||
from infection_monkey.post_breach.actions.add_user import BackdoorUser
|
||||
from infection_monkey.post_breach.pba import PBA
|
||||
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
|
||||
from infection_monkey.utils import is_windows_os
|
||||
|
||||
USERNAME = "somenewuser"
|
||||
PASSWORD = "N3WPa55W0rD!@12"
|
||||
|
||||
|
||||
class CommunicateAsNewUser(PBA):
|
||||
"""
|
||||
This PBA creates a new user, and then pings google as that user. This is used for a Zero Trust test of the People
|
||||
pillar. See the relevant telemetry processing to see what findings are created.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(CommunicateAsNewUser, self).__init__(name=POST_BREACH_COMMUNICATE_AS_NEW_USER)
|
||||
|
||||
def run(self):
|
||||
username = USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5))
|
||||
if is_windows_os():
|
||||
if not self.try_to_create_user_windows(username, PASSWORD):
|
||||
return # no point to continue if failed creating the user.
|
||||
|
||||
# Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera
|
||||
new_user_logon_token_handle = win32security.LogonUser(
|
||||
username,
|
||||
".", # current domain
|
||||
PASSWORD,
|
||||
win32con.LOGON32_LOGON_BATCH, # logon type
|
||||
win32con.LOGON32_PROVIDER_DEFAULT) # logon provider
|
||||
|
||||
if new_user_logon_token_handle == 0:
|
||||
PostBreachTelem(
|
||||
self,
|
||||
("Can't logon as {} Last error: {}".format(username, win32api.GetLastError()), False)
|
||||
).send()
|
||||
return # no point to continue if can't log on.
|
||||
|
||||
# Using os.path is OK, as this is on windows for sure
|
||||
ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe")
|
||||
if not os.path.exists(ping_app_path):
|
||||
PostBreachTelem(self, ("{} not found".format(ping_app_path), False)).send()
|
||||
return # Can't continue without ping.
|
||||
|
||||
# Open process as that user:
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera
|
||||
return_value_create_process = win32process.CreateProcessAsUser(
|
||||
new_user_logon_token_handle, # A handle to the primary token that represents a user.
|
||||
# If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module
|
||||
# to execute, and *lpCommandLine specifies the command line.
|
||||
ping_app_path, # The name of the module to be executed.
|
||||
"google.com", # The command line to be executed.
|
||||
None, # Process attributes
|
||||
None, # Thread attributes
|
||||
True, # Should inherit handles
|
||||
win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process.
|
||||
None, # An environment block for the new process. If this parameter is NULL, the new process
|
||||
# uses the environment of the calling process.
|
||||
None, # CWD. If this parameter is NULL, the new process will have the same current drive and
|
||||
# directory as the calling process.
|
||||
win32process.STARTUPINFO() # STARTUPINFO structure.
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa
|
||||
)
|
||||
|
||||
if return_value_create_process == 0:
|
||||
PostBreachTelem(self, (
|
||||
"Failed to open process as user. Last error: {}".format(win32api.GetLastError()), False)).send()
|
||||
return
|
||||
else:
|
||||
try:
|
||||
linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username)
|
||||
linux_cmds.extend([";", "sudo", "-", username, "-c", "'ping -c 2 google.com'"])
|
||||
subprocess.check_output(linux_cmds, stderr=subprocess.STDOUT, shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
PostBreachTelem(self, (e.output, False)).send()
|
||||
return
|
||||
|
||||
def try_to_create_user_windows(self, username, password):
|
||||
try:
|
||||
windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password)
|
||||
subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True)
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
PostBreachTelem(self, (
|
||||
"Couldn't create the user '{}'. Error output is: '{}'".format(username, e.output), False)).send()
|
||||
return False
|
|
@ -19,7 +19,8 @@ class PBA(object):
|
|||
def __init__(self, name="unknown", linux_cmd="", windows_cmd=""):
|
||||
"""
|
||||
:param name: Name of post breach action.
|
||||
:param command: Command that will be executed on breached machine
|
||||
:param linux_cmd: Command that will be executed on breached machine
|
||||
:param windows_cmd: Command that will be executed on breached machine
|
||||
"""
|
||||
self.command = PBA.choose_command(linux_cmd, windows_cmd)
|
||||
self.name = name
|
||||
|
|
|
@ -25,8 +25,9 @@ class PostBreach(object):
|
|||
Executes all post breach actions.
|
||||
"""
|
||||
for pba in self.pba_list:
|
||||
LOG.debug("Executing PBA: '{}'".format(pba.name))
|
||||
pba.run()
|
||||
LOG.info("Post breach actions executed")
|
||||
LOG.info("All PBAs executed. Total {} executed.".format(len(self.pba_list)))
|
||||
|
||||
@staticmethod
|
||||
def config_to_pba_list():
|
||||
|
|
|
@ -119,6 +119,14 @@ SCHEMA = {
|
|||
"title": "Back door user",
|
||||
"attack_techniques": []
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CommunicateAsNewUser"
|
||||
],
|
||||
"title": "Communicate as new user",
|
||||
"attack_techniques": []
|
||||
},
|
||||
],
|
||||
},
|
||||
"finger_classes": {
|
||||
|
|
|
@ -1,7 +1,18 @@
|
|||
from monkey_island.cc.database import mongo
|
||||
from common.data.post_breach_consts import *
|
||||
from monkey_island.cc.models import Monkey
|
||||
from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_user import test_new_user_communication
|
||||
|
||||
|
||||
def process_communicate_as_new_user_telemetry(telemetry_json):
|
||||
current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
|
||||
success = telemetry_json['data']['result'][1]
|
||||
message = telemetry_json['data']['result'][0]
|
||||
test_new_user_communication(current_monkey, success, message)
|
||||
|
||||
|
||||
POST_BREACH_TELEMETRY_PROCESSING_FUNCS = {
|
||||
POST_BREACH_COMMUNICATE_AS_NEW_USER: process_communicate_as_new_user_telemetry,
|
||||
# `lambda *args, **kwargs: None` is a no-op.
|
||||
POST_BREACH_BACKDOOR_USER: lambda *args, **kwargs: None,
|
||||
POST_BREACH_FILE_EXECUTION: lambda *args, **kwargs: None,
|
||||
|
@ -13,5 +24,7 @@ def process_post_breach_telemetry(telemetry_json):
|
|||
{'guid': telemetry_json['monkey_guid']},
|
||||
{'$push': {'pba_results': telemetry_json['data']}})
|
||||
|
||||
if telemetry_json["name"] in POST_BREACH_TELEMETRY_PROCESSING_FUNCS:
|
||||
POST_BREACH_TELEMETRY_PROCESSING_FUNCS[telemetry_json["name"]](telemetry_json)
|
||||
post_breach_action_name = telemetry_json["data"]["name"]
|
||||
if post_breach_action_name in POST_BREACH_TELEMETRY_PROCESSING_FUNCS:
|
||||
POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name](telemetry_json)
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_NETWORK, STATUS_FAILED, TEST_COMMUNICATE_AS_NEW_USER, \
|
||||
STATUS_PASSED
|
||||
from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding
|
||||
from monkey_island.cc.models.zero_trust.event import Event
|
||||
|
||||
|
||||
def test_new_user_communication(current_monkey, success, message):
|
||||
tried_to_communicate_event = Event.create_event(
|
||||
title="Communicate as new user",
|
||||
message="Monkey on {} tried to create a new user and communicate from it.".format(current_monkey.hostname),
|
||||
event_type=EVENT_TYPE_MONKEY_NETWORK)
|
||||
events = [tried_to_communicate_event]
|
||||
|
||||
if success:
|
||||
events.append(
|
||||
Event.create_event(
|
||||
title="Communicate as new user",
|
||||
message="New user created by Monkey on {} successfully tried to communicate with the internet. "
|
||||
"Details: {}".format(current_monkey.hostname, message),
|
||||
event_type=EVENT_TYPE_MONKEY_NETWORK)
|
||||
)
|
||||
test_status = STATUS_FAILED
|
||||
else:
|
||||
events.append(
|
||||
Event.create_event(
|
||||
title="Communicate as new user",
|
||||
message="Monkey on {} couldn't communicate as new user. Details: {}".format(
|
||||
current_monkey.hostname, message),
|
||||
event_type=EVENT_TYPE_MONKEY_NETWORK)
|
||||
)
|
||||
test_status = STATUS_PASSED
|
||||
|
||||
AggregateFinding.create_or_add_to_existing(
|
||||
test=TEST_COMMUNICATE_AS_NEW_USER, status=test_status, events=events
|
||||
)
|
Loading…
Reference in New Issue