Difficulty: Hard

This Active Directory box was especially challenging due to a firewall that blocked many connections, adding an extra layer of difficulty.
The journey began with decrypting a password from Jenkins, which granted WinRM access as a user.
From there, abusing ACLs became crucial for escalating privileges.
Each step deepened my understanding of AD exploitation, making it a rewarding and insightful challenge.


Nmap

The nmap scan revealed three open ports:

obraz

Port 80 - Website

Here’s what the website looks like:

obraz

Right from the initial enumeration, an email address and domain name are revealed.
Let’s make a note of the email for later and add the domain object.htb to /etc/hosts.
At this point, we can try directory busting, but it didn’t yield any results.

Lastly there is a link that points to http://object.htb:8080/.

Port 8080 - Jenkins

This website hosts a Jenkins instance.
We can try common default credentials such as admin:password, jenkins:jenkins, and similar combinations, but they didn’t work.

obraz

After creating an account, we gain access to the dashboard and notice it’s running version 2.317 of Jenkins.
We could potentially abuse the Script Console using a Groovy-based reverse shell, but access to it requires administrative privileges.
My second thought was to create a Jenkins job that, when built, executes a Windows command, eventually leading to shell access.
This is a popular way to execute commands in Jenkins, let’s try that:

Click on “New Item” -> “Freestyle Project”, and scroll a bit down, and select “Build Periodically”:

obraz

We want it to make a build every minute, meaning our command will be executed every minute also.
Scroll a bit down and “Add a build step” -> “Execute Windows Batch Command”

obraz
Then hit save and apply.

Now we wait for one minute and select: last build -> console output -> check if we have command execution

obraz

Now it’s time to upload a reverse shell to the target machine, but here we discovered that it won’t be possible likely to the firewall settings that block outbound connection.

obraz

It’s time to change approach, we’re left with possibility to enumerate the target system.
First we want to check jenkins home directory.
We’ll exeucte:

obraz

Output:

obraz

We’ve got two intresting directories: secrets, and users.
First we’ll check Users directory.

obraz

There is a user that we created previously and admin.
Let’s check admin directory:

obraz

We have a config file, lets see what’s in it:

obraz

File Contents:

<?xml version='1.1' encoding='UTF-8'?>
<user>
  <version>10</version>
  <id>admin</id>
  <fullName>admin</fullName>
  <properties>
    <com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty plugin="credentials@2.6.1">
      <domainCredentialsMap class="hudson.util.CopyOnWriteMap$Hash">
        <entry>
          <com.cloudbees.plugins.credentials.domains.Domain>
            <specifications/>
          </com.cloudbees.plugins.credentials.domains.Domain>
          <java.util.concurrent.CopyOnWriteArrayList>
            <com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
              <id>320a60b9-1e5c-4399-8afe-44466c9cde9e</id>
              <description></description>
              <username>oliver</username>
              <password>{AQAAABAAAAAQqU+m+mC6ZnLa0+yaanj2eBSbTk+h4P5omjKdwV17vcA=}</password>
              <usernameSecret>false</usernameSecret>
            </com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
          </java.util.concurrent.CopyOnWriteArrayList>
        </entry>
      </domainCredentialsMap>
    </com.cloudbees.plugins.credentials.UserCredentialsProvider_-UserCredentialsProperty>
    <hudson.plugins.emailext.watching.EmailExtWatchAction_-UserProperty plugin="email-ext@2.84">
      <triggers/>
    </hudson.plugins.emailext.watching.EmailExtWatchAction_-UserProperty>
    <hudson.model.MyViewsProperty>
      <views>
        <hudson.model.AllView>
          <owner class="hudson.model.MyViewsProperty" reference="../../.."/>
          <name>all</name>
          <filterExecutors>false</filterExecutors>
          <filterQueue>false</filterQueue>
          <properties class="hudson.model.View$PropertyList"/>
        </hudson.model.AllView>
      </views>
    </hudson.model.MyViewsProperty>
    <org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty plugin="display-url-api@2.3.5">
      <providerId>default</providerId>
    </org.jenkinsci.plugins.displayurlapi.user.PreferredProviderUserProperty>
    <hudson.model.PaneStatusProperties>
      <collapsed/>
    </hudson.model.PaneStatusProperties>
    <jenkins.security.seed.UserSeedProperty>
      <seed>ea75b5bd80e4763e</seed>
    </jenkins.security.seed.UserSeedProperty>
    <hudson.search.UserSearchProperty>
      <insensitiveSearch>true</insensitiveSearch>
    </hudson.search.UserSearchProperty>
    <hudson.model.TimeZoneProperty/>
    <hudson.security.HudsonPrivateSecurityRealm_-Details>
      <passwordHash>#jbcrypt:$2a$10$q17aCNxgciQt8S246U4ZauOccOY7wlkDih9b/0j4IVjZsdjUNAPoW</passwordHash>
    </hudson.security.HudsonPrivateSecurityRealm_-Details>
    <hudson.tasks.Mailer_-UserProperty plugin="mailer@1.34">
      <emailAddress>admin@object.local</emailAddress>
    </hudson.tasks.Mailer_-UserProperty>
    <jenkins.security.ApiTokenProperty>
      <tokenStore>
        <tokenList/>
      </tokenStore>
    </jenkins.security.ApiTokenProperty>
    <jenkins.security.LastGrantedAuthoritiesProperty>
      <roles>
        <string>authenticated</string>
      </roles>
      <timestamp>1634793332195</timestamp>
    </jenkins.security.LastGrantedAuthoritiesProperty>
  </properties>
</user>

This is a file that contain credentials and can be decrypted with a python script, but in order to do that we will need master.key and hudson.util.Secret which both can be found in Secrets directory.

obraz

Output:

obraz

We will copy this key to our kali linux.
With getting hudson.util.Secret I had a problem but finally it worked when converted to base64 with powershell:

obraz

Output:

obraz

Now let’s decode that and put in a file:

obraz

Now we can put everything together and get plain text password with this script:

https://github.com/gquere/pwn_jenkins/blob/master/offline_decryption/jenkins_offline_decrypt.py

obraz

From nmap scan we know that port 5985 is open, meaning that we can try connect with evil-winrm:

obraz

Let’s retrieve a flag:

obraz

I like to check privileges first when enumerating windows:

obraz

At this point I’ll run bloodhound.
Normally I would run bloodhound-python to collect information for bloodhound but there is a firewall that blocks connection.
We can use evil-winrm’s upload function to upload Sharphound, and run it with collection method “all”:

obraz

Now we can move .zip file with evil-winrm’s download function, run bloodhound and ingest data.

Bloodhound Analysis

First we can mark oliver as “owned”.
If we look at node info and scroll a bit down we notice that oliver has one Object control.

obraz

If we check that we can see that we have “ForceChangePassword” over user smith.
Meaning we can change smith’s password, normally I would do it remotely from kali linux, but once again this machine blocks connections with firewall.

obraz

Let’s change his password locally on the target machine, in order to do that we will use PowerView.ps1 script. We can upload it with evil-winrm’s upload function and load it into memory.

obraz

Now to change his password we need to create password object in powershell which will contain new password for user smith.
Then use this object to change the password.

obraz

Shell as Smith

Now we can login with evil-winrm.

obraz

Pivoting to Maria

Back to bloodhound we can mark smith as owned.
Lucky for us smith has object control over another user too.

obraz

Bloodhound help says that we can perform targeted Kerberoast attack with “GenericWrite” privilege.
To achieve it we need to create SPN for the target user, and then retrieve it’s hash.

obraz

Unfortunately it didnt’t work.

obraz

It’s time for different approach, let’s open google and do more research.
After researching this topic I found that we can change logonscript for a user with “GenericWrite” privilege.

https://notes.morph3.blog/abusing-active-directory-acls/genericwrite

This article explains it.
Logon script runs everytime a user logs in.
It’s automated by HTB but normally we would wait for a user to log in.

Due to firewall we can’t connect back to our machine, let’s stick with enumeration.

obraz

On this screenshot we can see that user Maria has Engines.xls file on the desktop.
We can copy this file to programdata directory with the same method.

obraz

Let’s check this file:

obraz

It contain some passwords, now I’ll put those passwords in a file and spray them with nxc:

obraz

Elevating Privileges as Maria

Now we can check bloodhound once again.
We have WriteOwner over “Domain Admins” group, meaning we can easily elevate privileges.

First we will take ownership of this group:

Set-DomainObjectOwner -Identity 'Domain Admins' -OwnerIdentity 'maria'

Then grant Maria full rights:

Add-DomainObjectAcl -Rights 'All' -TargetIdentity "Domain Admins" -PrincipalIdentity "maria"

Lastly add Maria to the group:

Add-ADGroupMember -Identity 'Domain Admins' -Members 'maria'

obraz

For it to take changes we have to exit evil-winrm and connect again.
Lastly we will retrieve the root flag.

obraz

Thank you for reading!!


<
Blog Archive
Archive of all previous blog posts
>
Next Post
HTB Falafel (Hard) - Writeup