Merge pull request #517 from guardicore/feature/515-add-curl-pba

Replaced ping with curl/wget and Invoke-WebRequest
This commit is contained in:
Shay Nehmad 2019-12-25 12:52:56 +02:00 committed by GitHub
commit 52eced33e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 35 deletions

View File

@ -10,10 +10,10 @@ from infection_monkey.post_breach.pba import PBA
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.environment import is_windows_os
PING_TEST_DOMAIN = "google.com" INFECTION_MONKEY_WEBSITE_URL = "https://infectionmonkey.com/"
CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT = "Created process '{}' as user '{}', and successfully pinged." CREATED_PROCESS_AS_USER_SUCCESS_FORMAT = "Created process '{}' as user '{}' and the process succeeded."
CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT = "Created process '{}' as user '{}', but failed to ping (exit status {})." CREATED_PROCESS_AS_USER_FAILED_FORMAT = "Created process '{}' as user '{}', but the process failed (exit status {}:{})."
USERNAME_PREFIX = "somenewuser" USERNAME_PREFIX = "somenewuser"
PASSWORD = "N3WPa55W0rD!1" PASSWORD = "N3WPa55W0rD!1"
@ -23,8 +23,8 @@ logger = logging.getLogger(__name__)
class CommunicateAsNewUser(PBA): 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 This PBA creates a new user, and then creates HTTPS requests as that user. This is used for a Zero Trust test of the
pillar. See the relevant telemetry processing to see what findings are created. People pillar. See the relevant telemetry processing to see what findings are created.
""" """
def __init__(self): def __init__(self):
@ -34,9 +34,9 @@ class CommunicateAsNewUser(PBA):
username = CommunicateAsNewUser.get_random_new_user_name() username = CommunicateAsNewUser.get_random_new_user_name()
try: try:
with create_auto_new_user(username, PASSWORD) as new_user: with create_auto_new_user(username, PASSWORD) as new_user:
ping_commandline = CommunicateAsNewUser.get_commandline_for_ping() http_request_commandline = CommunicateAsNewUser.get_commandline_for_http_request(INFECTION_MONKEY_WEBSITE_URL)
exit_status = new_user.run_as(ping_commandline) exit_status = new_user.run_as(http_request_commandline)
self.send_ping_result_telemetry(exit_status, ping_commandline, username) self.send_result_telemetry(exit_status, http_request_commandline, username)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
PostBreachTelem(self, (e.output.decode(), False)).send() PostBreachTelem(self, (e.output.decode(), False)).send()
except NewUserError as e: except NewUserError as e:
@ -47,21 +47,33 @@ class CommunicateAsNewUser(PBA):
return USERNAME_PREFIX + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) return USERNAME_PREFIX + ''.join(random.choice(string.ascii_lowercase) for _ in range(5))
@staticmethod @staticmethod
def get_commandline_for_ping(domain=PING_TEST_DOMAIN, is_windows=is_windows_os()): def get_commandline_for_http_request(url, is_windows=is_windows_os()):
format_string = "PING.exe {domain} -n 1" if is_windows else "ping -c 1 {domain}" if is_windows:
return format_string.format(domain=domain) format_string = 'powershell.exe -command "Invoke-WebRequest {url}" -UseBasicParsing'
else:
# true || false -> 0. false || true -> 0. false || false -> 1. So:
# if curl works, we're good.
# If curl doesn't exist or fails and wget work, we're good.
# And if both don't exist: we'll call it a win.
format_string = "curl {url} || wget -O/dev/null -q {url}"
return format_string.format(url=url)
def send_ping_result_telemetry(self, exit_status, commandline, username): def send_result_telemetry(self, exit_status, commandline, username):
""" """
Parses the result of ping and sends telemetry accordingly. Parses the result of the command and sends telemetry accordingly.
:param exit_status: In both Windows and Linux, 0 exit code from Ping indicates success. :param exit_status: In both Windows and Linux, 0 exit code indicates success.
:param commandline: Exact commandline which was executed, for reporting back. :param commandline: Exact commandline which was executed, for reporting back.
:param username: Username from which the command was executed, for reporting back. :param username: Username from which the command was executed, for reporting back.
""" """
if exit_status == 0: if exit_status == 0:
PostBreachTelem(self, ( PostBreachTelem(self, (
CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT.format(commandline, username), True)).send() CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True)).send()
else: else:
PostBreachTelem(self, ( PostBreachTelem(self, (
CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT.format(commandline, username, exit_status), False)).send() CREATED_PROCESS_AS_USER_FAILED_FORMAT.format(
commandline, username, exit_status, twos_complement(exit_status)), False)).send()
def twos_complement(exit_status):
return hex(exit_status & (2 ** 32 - 1))

View File

@ -13,3 +13,4 @@ wmi
pywin32 ; sys_platform == 'win32' pywin32 ; sys_platform == 'win32'
pymssql<3.0 pymssql<3.0
pyftpdlib pyftpdlib
WinSys-3.x

View File

@ -5,7 +5,7 @@ from infection_monkey.utils.auto_new_user import AutoNewUser
from infection_monkey.utils.new_user_error import NewUserError from infection_monkey.utils.new_user_error import NewUserError
ACTIVE_NO_NET_USER = '/ACTIVE:NO' ACTIVE_NO_NET_USER = '/ACTIVE:NO'
WAIT_TIMEOUT_IN_MILLISECONDS = 20 * 1000 WAIT_TIMEOUT_IN_MILLISECONDS = 60 * 1000
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -65,8 +65,7 @@ class AutoNewWindowsUser(AutoNewUser):
self.username, self.username,
".", # Use current domain. ".", # Use current domain.
self.password, self.password,
win32con.LOGON32_LOGON_INTERACTIVE, # Logon type - interactive (normal user). Need this to open ping win32con.LOGON32_LOGON_INTERACTIVE, # Logon type - interactive (normal user), since we're using a shell.
# using a shell.
win32con.LOGON32_PROVIDER_DEFAULT) # Which logon provider to use - whatever Windows offers. win32con.LOGON32_PROVIDER_DEFAULT) # Which logon provider to use - whatever Windows offers.
except Exception as err: except Exception as err:
raise NewUserError("Can't logon as {}. Error: {}".format(self.username, str(err))) raise NewUserError("Can't logon as {}. Error: {}".format(self.username, str(err)))
@ -78,33 +77,28 @@ class AutoNewWindowsUser(AutoNewUser):
import win32process import win32process
import win32api import win32api
import win32event import win32event
from winsys import _advapi32
exit_code = -1 exit_code = -1
process_handle = None process_handle = None
thread_handle = None thread_handle = None
try: try:
# Open process as that user: # Open process as that user
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera # https://github.com/tjguk/winsys/blob/master/winsys/_advapi32.py
process_handle, thread_handle, _, _ = win32process.CreateProcessAsUser( proc_info = _advapi32.CreateProcessWithLogonW(
self.get_logon_handle(), # A handle to the primary token that represents a user. username=self.username,
None, # The name of the module to be executed. domain=".",
command, # The command line to be executed. password=self.password,
None, # Process attributes command_line=command
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
) )
process_handle = proc_info.hProcess
thread_handle = proc_info.hThread
logger.debug( logger.debug(
"Waiting for process to finish. Timeout: {}ms".format(WAIT_TIMEOUT_IN_MILLISECONDS)) "Waiting for process to finish. Timeout: {}ms".format(WAIT_TIMEOUT_IN_MILLISECONDS))
# https://social.msdn.microsoft.com/Forums/vstudio/en-US/b6d6a7ae-71e9-4edb-ac8f-408d2a41750d/what-events-on-a-process-handle-signal-satisify-waitforsingleobject?forum=vcgeneral
# Ignoring return code, as we'll use `GetExitCode` to determine the state of the process later. # Ignoring return code, as we'll use `GetExitCode` to determine the state of the process later.
_ = win32event.WaitForSingleObject( # Waits until the specified object is signaled, or time-out. _ = win32event.WaitForSingleObject( # Waits until the specified object is signaled, or time-out.
process_handle, # Ping process handle process_handle, # Ping process handle