Web


Nmap discovered a web server on the target port 80 The running service is Apache httpd 2.4.29

Webroot I am re-directed to a login page at login.php

Wappalyzer identified that the web application is written in PHP

the footer shows that its created by m4lwhere and points to an external web server

I tried some default/weak credentials without any luck Opting out to fuzzing

Fuzzing


┌──(kali㉿kali)-[~/archive/htb/labs/previse]
└─$ ffuf -c -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://$IP/FUZZ -ic -e .php,.txt,.html
________________________________________________
 :: Method           : GET
 :: URL              : http://10.10.11.104/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
 :: Extensions       : .php .txt .html 
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________
download.php            [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 1431ms]
login.php               [Status: 200, Size: 2224, Words: 486, Lines: 54, Duration: 95ms]
files.php               [Status: 302, Size: 4914, Words: 1531, Lines: 113, Duration: 114ms]
index.php               [Status: 302, Size: 2801, Words: 737, Lines: 72, Duration: 3442ms]
header.php              [Status: 200, Size: 980, Words: 183, Lines: 21, Duration: 95ms]
nav.php                 [Status: 200, Size: 1248, Words: 462, Lines: 32, Duration: 94ms]
footer.php              [Status: 200, Size: 217, Words: 10, Lines: 6, Duration: 95ms]
css                     [Status: 301, Size: 310, Words: 20, Lines: 10, Duration: 94ms]
status.php              [Status: 302, Size: 2966, Words: 749, Lines: 75, Duration: 97ms]
js                      [Status: 301, Size: 309, Words: 20, Lines: 10, Duration: 95ms]
logout.php              [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 95ms]
accounts.php            [Status: 302, Size: 3994, Words: 1096, Lines: 94, Duration: 94ms]
config.php              [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 94ms]
logs.php                [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 97ms]
server-status           [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 91ms]

While ffuf return a lot of files, most of them are locked away from the login page The only files that returned without code 302 are header.php, footer.php, and nav.php

header.php and footer.php


Both header.php and footer.php are pretty much empty


The nav.php file is rather interesting as it contains buttons pointing to the other files

All of these files are locked away from the authentication and returns 302 to the login.php file

Vulnerability


I pretty much tried all the injections techniques that I could think of

However, the login.php file doesn’t seem to be vulnerable to any of them

But then I found something interesting

execution after redirect


If I send a GET request to a resource that is locked away from the authentication; index.php

The web server responses with the code 302 and re-directs me to the login.php file This seems pretty usually as it locks resources behind the authentication

But What if I manually modified the intercepted response to the code 200?

I am able to access the index.php file without getting re-direct to the login page.

This vulnerability is know as Execution After Redirect (EAR) Execution After Redirect (EAR) is an attack where an attacker ignores redirects and retrieves sensitive content intended for authenticated users. To prevent this type of attack, it is important to ensure that the server-side authentication and access control mechanisms are properly implemented and validated. Additionally, server-side code should always return a redirect status code when redirecting users to protected resources, and should ensure that the redirect URL is properly validated and sanitized to prevent any malicious input from being used in the redirect.

I will be continuing the numeration

The website claims to be Precise File Hosting Service Management Is that a file server of sort? It also suggests to create an account

The header here must be the nav.php file that I enumerated earlier Each button is pointing to other files; including accounts.php, file.php, status.php, and file_logs.php

accounts.php


Using the same EAR technique, I am able to access the accounts.php file It seems that I am able to create an account here. It also notes that only admins should be able to access this page

I will create one.

Using the same EAR technique, I created a testing account

Authentication


I will now attempt to authenticate to the web application with the created testing account

Successfully authenticated as the tester user Now I don’t need to keep using the EAR technique to get things done

files.php


While the files.php file supports a file upload feature, there is an existing file, SITEBACKUP.ZIP, uploaded by newguy

Hovering the cursor over the file, it points to another resource

assuming that it’s using php include and the file attribute is set to 32, there must have been 31 other files prior Nevertheless, I will download the website backup archive

siteBackup.zip


┌──(kali㉿kali)-[~/…/htb/labs/previse/siteBackup]
└─$ unzip siteBackup.zip   
Archive:  siteBackup.zip
  inflating: accounts.php            
  inflating: config.php              
  inflating: download.php            
  inflating: file_logs.php           
  inflating: files.php               
  inflating: footer.php              
  inflating: header.php              
  inflating: index.php               
  inflating: login.php               
  inflating: logout.php              
  inflating: logs.php                
  inflating: nav.php                 
  inflating: status.php              

The archive contains 13 PHP files These must be the source codes

config.php

┌──(kali㉿kali)-[~/…/htb/labs/previse/siteBackup]
└─$ cat config.php 
<?php
 
function connectDB(){
    $host = 'localhost';
    $user = 'root';
    $passwd = 'mysql_p@ssw0rd!:)';
    $db = 'previse';
    $mycon = new mysqli($host, $user, $passwd, $db);
    return $mycon;
}
 
?>

the config.php file contains a db credential; root:mySQL_p@ssw0rd!:)

status.php


The status.php file appears to be checking the backend DB as well as some other info

Source Code


┌──(kali㉿kali)-[~/…/htb/labs/previse/siteBackup]
└─$ cat status.php  
<?php
session_start();
if (!isset($_SESSION['user'])) {
    header('location: login.php');
}
?>
 
<?php include( 'header.php' ); ?>
 
<title>Previse Status</title>
</head>
<body>
 
<?php include( 'nav.php' ); ?>
 
<section class="uk-section uk-section-default">
    <div class="uk-container">
        <h2 class="uk-heading-divider">Status</h2>
        <div class="uk-container" uk-grid>
            <div><p>check website status:</p></div>
        </div>
        <div class="uk-container">
            <?php
                $db = connectDB();
                if ($db === false) {
                    echo("<p class='uk-text-danger'>MySQL server is not functioning properly!</p>");
                    die("error: Could not connect. " . $db->connect_error);
                } else {
                    echo("<p class='uk-text-success'>MySQL server is online and connected!</p>");                 
                    $userQuery = "SELECT id FROM accounts;";
                    $fileQuery = "SELECT id FROM files;";
                    $userResult = $db->query($userQuery);
                    $fileResult = $db->query($fileQuery);
                    $userCount = $userResult->num_rows;
                    $fileCount = $fileResult->num_rows;
                    if ($userCount == 1) {
                        echo "<p>There is <b>{$userCount}</b> registered admin</p>";
                    } else {
                        echo "<p>There are <b>{$userCount}</b> registered admins</p>";
                    }
                    if ($fileCount == 1) {
                        echo "<p>There is <b>{$fileCount}</b> uploaded file</p>";
                    } else {
                        echo "<p>There are <b>{$fileCount}</b> uploaded files</p>";
                    }
                }
                $db->close();
            ?>
        </div>
    </div>
</section>
 
<?php include( 'footer.php' ); ?>

The extracted source code also reveals that This file itself is not much of use

file_logs.php


The file_logs.php file seems rather interesting as it features something It seems that I am able to request log data to check who has downloaded files I will first check the feature

There are 3 options (delimiters) to choose from

Upon clicking the SUBMIT button, my browser downloads a file; out.log

┌──(kali㉿kali)-[~/archive/htb/labs/previse]
└─$ cat ~/Downloads/out.log      
time,user,fileID
1622482496,m4lwhere,4
1622485614,m4lwhere,4
1622486215,m4lwhere,4
1622486218,m4lwhere,1
1622486221,m4lwhere,1
1622678056,m4lwhere,5
1622678059,m4lwhere,6
1622679247,m4lwhere,1
1622680894,m4lwhere,5
1622708567,m4lwhere,4
1622708573,m4lwhere,4
1622708579,m4lwhere,5
1622710159,m4lwhere,4
1622712633,m4lwhere,4
1622715674,m4lwhere,24
1622715842,m4lwhere,23
1623197471,m4lwhere,25
1623200269,m4lwhere,25
1623236411,m4lwhere,23
1623236571,m4lwhere,26
1623238675,m4lwhere,23
1623238684,m4lwhere,23
1623978778,m4lwhere,32
1681297763,tester,32

The out.log file appears to be the content of a log file The file contains a list of entries with columns for time, user, and fileID Timestamps are in Unix time format, and the log appears to span over several years, with entries from both m4lwhere and tester users

The tester user is my testing account and the m4lwhere user must be someone else

The last entry shows me downloading the website backup archive

Source Code


┌──(kali㉿kali)-[~/…/htb/labs/previse/siteBackup]
└─$ cat file_logs.php 
<?php
session_start();
if (!isset($_SESSION['user'])) {
    header('location: login.php');
}
?>
 
<?php include( 'header.php' ); ?>
 
<title>Previse File Access Logs</title>
</head>
<body>
 
<?php include( 'nav.php' ); ?>
<section class="uk-section uk-section-default">
    <div class="uk-container">
        <h2 class="uk-heading-divider">Request Log Data</h2>
        <p>We take security very seriously, and keep logs of file access actions. We can set delimters for your needs!</p>
        <p>Find out which users have been downloading files.</p>
        <form action="logs.php" method="post">
            <div class="uk-margin uk-width-1-4@s">
                <label class="uk-form-label" for="delim-log">file delimeter:</label>
                <select class="uk-select" name="delim" id="delim-log">
                    <option value="comma">comma</option>
                    <option value="space">space</option>
                    <option value="tab">tab</option>
                </select>
            </div>
            <button class="uk-button uk-button-default" type="submit" value="submit">Submit</button>
        </form>
    </div>
</section>
    
<?php include( 'footer.php' ); ?>

Checking the source code reveals that it’s sending a POST request with an attribute; delim, to the logs.php file I will check the logs.php file

logs.php

┌──(kali㉿kali)-[~/…/htb/labs/previse/siteBackup]
└─$ cat logs.php
<?php
session_start();
if (!isset($_SESSION['user'])) {
    header('Location: login.php');
    exit;
}
?>
 
<?php
if (!$_SERVER['REQUEST_METHOD'] == 'POST') {
    header('Location: login.php');
    exit;
}
 
/////////////////////////////////////////////////////////////////////////////////////
//I tried really hard to parse the log delims in PHP, but python was SO MUCH EASIER//
/////////////////////////////////////////////////////////////////////////////////////
 
$output = exec("/usr/bin/python /opt/scripts/log_process.py {$_POST['delim']}");
echo $output;
 
$filepath = "/var/www/out.log";
$filename = "out.log";    
 
if(file_exists($filepath)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="'.basename($filepath).'"');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($filepath));
    ob_clean(); // Discard data in the output buffer
    flush(); // Flush system headers
    readfile($filepath);
    die();
} else {
    http_response_code(404);
    die();
} 
?>

The logs.php file is the actual file that is responsible for the feature as it does the execution It uses PHP exec() method to execute the following OS commands; /usr/bin/python /opt/scripts/log_process.py {$_POST['delim']} The {$_POST['delim']} argument is being the user input from the delim attribute

While I have no way of knowing the Python script, /opt/scripts/log_process.py, it seems that the file might be vulnerable to OS command injection as the logs.php file doesn’t do anything to validate the user input for sanatization