We are given a website:
https://web-hah-got-em-20ac16c4b909.2024.ductf.dev/
(Note: It might have been taken down by the time you’re reading this since the CTF is over)
Investigation
I opened it. All I see is “Not Found”. Is it broken?
โ you (maybe)
I thought the same until I downloaded the provided files
(you can find a copy here)
I found a Dockerfile:
|
|
We now know that the flag is at /etc/flag.txt
and that the service is probably “gotenberg”
What’s gotenberg?
โ you (maybe)
A quick internet search leads us to:
https://gotenberg.dev/
It seems to be a pdf generation API.
How’s this going to help us?
โ you (maybe)
Let’s check their Github.
The latest release seems to be 8.8.0
, while the version the challenge uses (as seen in the Dockerfile) is 8.0.3
, which is quite old.
Finding the vulnerability
Since we’re on an old version we may be able to find a known vulnerability.
โ you (maybe)
Exactly, I like the way you think Mr. Reader. Checking the versions, the release notes for version 8.1.0
(which proceeds 8.0.3
) say:
โ ๏ธ Security Update
This update addresses a critical security flaw which previously enabled unauthorized read access to the system files of a Gotenberg container. It is strongly advised to upgrade to this version, especially for those utilizing the Chromium module
to process untrusted content.A special thanks to @filipochnik!
โ 8.1.0 release notes
Good, but we need more detail.
โ you (maybe)
My first idea was to use the file://
URI scheme but it seems to be filtered out,
So I checked the commits between the two releases and the commit ad152e62e5124b673099a9103eb6e7f933771794
caught my eye,
Its commit message says:
fix(chromium): better default deny list regexp
It’s not hard to notice the most glaring change:
|
|
From the variable name, it’s easy to deduce that it is used to filter out malicious attempts at accessing local files, this must mean 8.0.3
’s approach was somehow vulnerable.
I don’t quite get this regex.
โ you (maybe)
Don’t worry, I’ll explain:
Before that, I should note: There might be more details on how the API is checking the URIs we give it or how or where itโs applying this Regex. I havenโt read the full code, but my current understanding was enough to solve this challenge.
The old regex (8.0.3
):
It can be broken into three parts:
^file:///
which means “starts withfile:///
”.[^tmp]
which matches any single letter that isn’tt
orm
orp
..*
which is basically a wildcard: it matches anything.
So, to sum up: any URI that starts withfile:///
that isn’t immediately followed by a string that starts witht
,m
, orp
.
The new regex (8.1.0
):
It’s a bit more complex:
^file:
: this is obvious. It matches URIs starting withfile:
(?!//\/tmp/)
: this uses a regex feature called “negative lookahead”. While it might seem scary, it can be explained with a simple example:q(?!t)
matches any letter q not followed by the letter t.
Therefore ^file:(?!//\/tmp/)
matches URIs that start with file:
but don’t have ///tmp/
immediately after.
So, they do the same thing. The second one seems to just add useless requirements.
โ you (maybe)
Not at all actually.
I thought long and hard about how I could exploit this until I found myself on the Wikipedia page for the file URI scheme and found this example:
Unix
Here are two Unix examples pointing to the same /etc/fstab file:
โ Wikipedia - Creative Commons Attribution-ShareAlike License 4.0
1 2
file://localhost/etc/fstab file:///etc/fstab
It appears that we can specify a host.
How would that help?
โ you (maybe)
Well, with the way the old regex is written, if we were to usefile://localhost/etc/flag.txt
the regex won’t match it since it doesn’t start with file:///
therefore it should work.
It’s about time you explained how to use this service. How can I send the URI to the server?
โ you (maybe)
Ah, right.
The Gotenberg website has an example for us:
|
|
Let’s craft our command and see what happens!
|
|
Would you look at that? We have a file flag.pdf
with the flag.
|
|
Hope you enjoyed this writeup and learned something new.