[DownUnderCtf] hah got em writeup

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)

Website Screenshot

I thought the same until I downloaded the provided files
(you can find a copy here)

I found a Dockerfile:

1
2
3
4
FROM gotenberg/gotenberg:8.0.3

COPY flag.txt /etc/flag.txt
CMD ["gotenberg", "--api-port=1337"]

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/

Gotenberg Screenshot

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 ad152e62e5124b673099a9103eb6e7f933771794caught my eye,

Its commit message says:

fix(chromium): better default deny list regexp

It’s not hard to notice the most glaring change:

1
2
-CHROMIUM_DENY_LIST="^file:///[^tmp].*"
+CHROMIUM_DENY_LIST=^file:(?!//\/tmp/).*

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 with file:///”.
  • [^tmp] which matches any single letter that isn’t t or m or p.
  • .* which is basically a wildcard: it matches anything.
    So, to sum up: any URI that starts with file:/// that isn’t immediately followed by a string that starts with t, m, or p.

The new regex (8.1.0):

It’s a bit more complex:

  • ^file:: this is obvious. It matches URIs starting with file:
  • (?!//\/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:

1
2
file://localhost/etc/fstab
file:///etc/fstab

โ€• Wikipedia - Creative Commons Attribution-ShareAlike License 4.0

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 use
file://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:

1
2
3
4
5
6
7
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form landscape=true \
--form marginTop=1 \
--form marginBottom=1 \
-o my.pdf

Let’s craft our command and see what happens!

1
2
3
4
5
6
7
curl \
--request POST https://web-hah-got-em-20ac16c4b909.2024.ductf.dev/forms/chromium/convert/url \
--form url=file://localhost/etc/flag.txt \
--form landscape=true \
--form marginTop=1 \url
--form marginBottom=1 \
-o flag.pdf

Would you look at that? We have a file flag.pdf with the flag.

1
DUCTF{dEeZ_r3GeX_cHeCK5_h4h_g0t_eM}

Hope you enjoyed this writeup and learned something new.

๐Ÿ‡ต๐Ÿ‡ธ
Built with Hugo
Theme Stack designed by Jimmy