From 4e5233b42f6383386eb585f40d5570811a9e69a1 Mon Sep 17 00:00:00 2001 From: jmac774 Date: Thu, 4 Sep 2025 09:45:46 -0400 Subject: [PATCH] adding altsecurityidentities handling --- README.md | 2 ++ src/adws.py | 8 ++++++++ src/soa.py | 43 +++++++++++++++++++++++++++++++++++-------- src/soap_templates.py | 4 ++-- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8fc8d3d..794c3ee 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ Enumeration: Writing: --rbcd source Operation to write or remove RBCD. Also used to pass in the source computer account used for the attack. + --altsecid value Operation to write the altSecurityIdentities attribute value, writes by default unless " + --remove" is specified --spn value Operation to write the servicePrincipalName attribute value, writes by default unless " --remove" is specified --asrep Operation to write the DONT_REQ_PREAUTH (0x400000) userAccountControl flag on a target diff --git a/src/adws.py b/src/adws.py index 2b5fe82..8964c02 100644 --- a/src/adws.py +++ b/src/adws.py @@ -1,6 +1,7 @@ import datetime import logging import socket +import re from base64 import b64decode from enum import IntFlag from typing import Self, Type @@ -426,6 +427,13 @@ def _handle_str_to_xml(self, xmlstr: str) -> ElementTree.Element | None: soap message return by the server """ + # handling of unescaped XML special characters in + # responses to ensure smooth parsing + m = re.search(r']*>(X509:<[^>]+>(.*?))', xmlstr) + if m: + unescaped = m.groups()[0] + xmlstr = xmlstr.replace(unescaped, '' % unescaped) + if ":Fault>" and ":Reason>" not in xmlstr: return ElementTree.fromstring(xmlstr) diff --git a/src/soa.py b/src/soa.py index e091db8..2414a6b 100644 --- a/src/soa.py +++ b/src/soa.py @@ -85,8 +85,9 @@ def getAccountDN( return dn -def set_spn( +def set_array_attribute( target: str, + attr: str, value: str, username: str, ip: str, @@ -94,32 +95,34 @@ def set_spn( auth: NTLMAuth, remove: bool = False, ): - """Set a value in servicePrincipalName. Appends value to the - attribute rather than replacing. + """Set a value in AD attribute that has isSingleValued=False + (e.g. servicePrincipalName, altSecurityIdentities). + Appends value to the attribute rather than replacing. Args: target (str): target samAccountName - value (str): value to append to the targets servicePrincipalName + attr (str): name of attribute to modify + value (str): value to append to the target's attribute username (str): user to authenticate as ip (str): the ip of the domain controller auth (NTLMAuth): authentication method remove (bool): Whether to remove the value """ - dn = getAccountDN(target=target,username=username,ip=ip,domain=domain,auth=auth) + dn = getAccountDN(target=target, username=username, ip=ip, domain=domain, auth=auth) put_client = ADWSConnect.put_client(ip, domain, username, auth) put_client.put( object_ref=dn, operation="add" if not remove else "delete", - attribute="addata:servicePrincipalName", + attribute=f"addata:{attr}", data_type="string", value=value, ) print( - f"[+] servicePrincipalName {value} {'removed' if remove else 'written'} successfully on {target}!" + f"[+] {attr} {value} {'removed' if remove else 'written'} successfully on {target}!" ) def set_asrep( @@ -412,6 +415,12 @@ def run_cli(): metavar="source", help="Operation to write or remove RBCD. Also used to pass in the source computer account used for the attack.", ) + writing.add_argument( + "--altsecid", + action="store", + metavar="value", + help='Operation to write the altSecurityIdentities attribute value, writes by default unless "--remove" is specified', + ) writing.add_argument( "--spn", action="store", @@ -503,6 +512,23 @@ def run_cli(): auth=auth, remove=options.remove, ) + elif options.altsecid != None: + if not options.account: + logging.critical( + 'Please specify an account with "--account"' + ) + raise SystemExit() + + set_array_attribute( + ip=remoteName, + domain=domain, + target=options.account, + attr='altSecurityIdentities', + value=options.altsecid, + username=username, + auth=auth, + remove=options.remove + ) elif options.spn != None: if not options.account: logging.critical( @@ -510,10 +536,11 @@ def run_cli(): ) raise SystemExit() - set_spn( + set_array_attribute( ip=remoteName, domain=domain, target=options.account, + attr='servicePrincipalName', value=options.spn, username=username, auth=auth, diff --git a/src/soap_templates.py b/src/soap_templates.py index eda1b0d..52a9681 100644 --- a/src/soap_templates.py +++ b/src/soap_templates.py @@ -100,9 +100,9 @@ {attribute} - {value} + - """ \ No newline at end of file + """