Contents

Buggy Doggo CTF — Python memory leak FTW!

Last Saturday I received a message from my acquaintance — James Santiago — about a CTF hosted by BugPoc and NahamSec, sponsored by Amazon. I thought there would be a challenge connected with AWS security, but oh… the task surprised me and really got me hooked!
So if you want to know how I did my reconnaissance on AWS infrastructure and chained cryptographic attack with obfuscated path traversal to obtain a memory leak from the Python application — then get some snacks or something to drink and check it out!

First things first — reconnaissance

At the beginning of the challenge we obtained three pieces of information:

  1. The challenge domain is doggo.buggywebsite.com
  2. The objective is to access Python variable called **SECRET_API_KEY
  3. We have to find the memory leak

First look at the page

Let’s then jump to the domain:

./images/welcome_page.png
Sweet — a lot of dog pictures — it will be useful to soothe the nerves 😄

By looking at the proxy (Burp) it is possible to see these four requests:

./images/first_view.png
First view

We’ve got a second domain — doggo-api — and also a script.js.

Let’s get a look at this script:

./images/script.png
Script - deleted few unnecessary lines here

Again API domain with two endpoints (/get-dogs, /fingerprint) and a few base64-encoded strings.

Decoding base64 does nothing, as it is some encrypted data.

Verify these two endpoints, first /fingerprint:

./images/fingerprint.png
Fingerprint endpoint

Returns base64-encoded ciphertext with a very similar-looking beginning — gAAAAABg — like hardcoded strings in JS file. Nothing else special here, let’s check /get-dogs:

./images/get-dogs_r.png
Get-dogs endpoint

Returns dog pics from S3 Bucket — that’s something. Also two headers here:

  • x-param — one of the hardcoded strings from JS
  • x-fingerprint — the one returned from /fingerprint!

Furthermore, by changing x-param to other hardcoded strings, the only different thing is a response path parameter, as it changes from /dogs?page=1 to page=2, 3 and 4. When trying to input another value, the server returns “ERROR: Unable to Decrypt”. Also if I tried to call /dogs myself, I received “Error, this endpoint is only internally accessible”.

What if we will use our x-fingerprint value as x-param?

./images/useragent.png
x-param content

Our User-Agent header value inside JSON! Nice — that’s the moment when I had some idea about it, but before going into explanation, let’s verify what we have there.

Infrastructure

Let’s dig some information about our domains with some further look using nslookup:

./images/dig.png
Infrastracture reconnaissance

Okay, second S3 Bucket and EC2 instance. I like buckets, especially the open ones 😉 so why not, let’s try some listing:

./images/notthistime.png
S3 Bucket with correct permissions

Oh… Not this time. I also checked if they are writable, but unfortunately no.

Meanwhile, I started a gobuster dir scan using few lists from SecLists (Discovery common.txt, quickhits.txt, py.txt, js.txt) on the buckets (maybe some hidden downloadable file?) and domains. One of them was pretty successful!

./images/gobuster.png
Gobuster results

There was an interesting finding, /heapdump endpoint, which when used, returned:

./images/unathorized.png
Heapdump response

The same response when I tried to call /dogs!

The theory

At this stage, I jumped to two conclusions:

  1. We have to call /heapdump by passing on properly crafted x-param value using /get-dogs API endpoint
  2. We have to break encryption somehow

And also, two possibilities how to do it:

  1. By analyzing the encryption, try to modify this ciphertext (Padding Oracle attack or some CBC modification)
  2. By properly crafted User-Agent value generate ciphertext which will modify the JSON or be interpreted as /heapdump path (Signing Oracle attack)

Encryption

By googling the phrase “gAAAAAB encryption”, I found that this one is symmetric Fernet encryption, and by looking at specification I get to know that generated token is a concatenation of the following fields:

1
Version ‖ Timestamp ‖ IV ‖ Ciphertext ‖ HMAC

And verified this with ciphertexts generated by /fingerprint:

Decryption code from ASecuritySite

Decryption code from ASecuritySite

Wrong idea

I thought that maybe somehow HMAC is not verified and I will be able to tamper with the data (simple explanation about HMAC — it verifies data integrity and authenticity, as it hashes the message — first four fields in our case — using some secret key).

To be honest — I lost a few hours here trying to tamper it and it was awful, better don’t ask my friends about my comments. I won’t waste your time, so let’s jump to the right answer.

Correct idea

I got some sleep and went with my second thought — Signing Oracle attack — this one is possible if the same signing method and secret are used in two or more places. And this is our case — we can create proper ciphertexts using /fingerprint endpoint.

By fuzzing it a little, I discovered that almost all special characters are accessible (only “ and \ were escaped). There weren’t any SSTI or any other weird behaviour.

Let’s analyze it from the beginning as I did it. I started with value “test” as User-Agent and send the returned value as x-param to the /get-dogs endpoint:

./images/test.png
Changed User-Agent value

/dogs{\”UA\”: \”test\”}” is our generated path. What is worth to notice, is that statusCode is 404 Not Found — that means the backend formats path value properly, as characters {, } and without encoding would return 400 Bad Request.

So my thought — why lose time generating these ciphertexts, if I can try to request it directly — if I would achieve a response with an error about the internal endpoint, I would win.

Path Traversal — obfuscation and security evasion

So I encoded this path and tried to request it:

1
/dogs%7b\%22UA\%22:+\%22test\%22%7d

./images/direct.png
Direct request

Status 404 — the same as before — that’s correct.

We need to create something like this:

1
/dogs/../heapdump

This would result in requesting /heapdump.

The first thing, I added ?test= at the end of my payload, then the last characters of JSON — \”} — would be excluded from path:

1
/dogs%7b\%22UA\%22:+\%22test?test=\%22%7d

Of course, we need to obtain /heapdump:

1
/dogs%7b\%22UA\%22:+\%22test/heapdump?test=\%22%7d

Now add some traversing:

1
/dogs%7b\%22UA\%22:+\%22test/../../heapdump?test=\%22%7d

./images/albblock.png
Blocked by ALB

Whoops —a little different than usual 400 Bad Request response here. Why that? Because probably Load Balancer which was used, tried to block it — but there is a simple trick for evasion 😉

1
/dogs%7b\%22UA\%22:+\%22test/////////../../heapdump?test=\%22%7d

./images/traversing.png
ALB evasion

401 Unauthorized — we’ve got this! Let’s try encrypting it and using as x-param:

./images/nonencoded.png
Filtered path traversal

Oh, the ../../ part was deleted… Let’s try to URL encode it:

1
/dogs%7b\%22UA\%22:+\%22test/////////%2e%2e%2f%2e%2e%2fheapdump?test=\%22%7d

./images/encoded.png
Filter evasion by URL encoding

Here we are, our beautiful heapdump response!

By scrolling down I was able to find the flag:

./images/flag.png
Flag

I win!

BugPoc Proof-of-Concept

As it was required, I created an automatic PoC for this using the BugPoc platform. I decided to go with Python PoC — created a script that requests new ciphertext, uses it on /get-dogs endpoint and by using regex extracts the flag.

./images/PoC.png
PoC

Source code here: https://bugpoc.com/poc#bp-uNE91jDI
ID: bp-uNE91jDI
Password: AfrAIDllaMA91

Summary

I submitted my report using the HackerOne platform and got confirmation about the correct solution:

Great job! Thanks for participating in our CTF Challenge! Your solution is correct, however unfortunately the 1st place prize has already been claimed.

Because I started this challenge too late on Sunday, I wasn’t the first one to solve it.

Still, I have to say that was some great task — nice chaining of not so popular vulnerabilities. Also, I had a chance to train and verify my cryptography skills. And don’t forget about reconnaissance — without a proper one, there would be probably a sad ending.

I’m just a little sad there weren’t more AWS-specific things, but couldn’t have everything 😄.

I included the part with my wrong idea, to show to everyone, especially beginners, that we aren’t flawless robots and not everything goes so smoothly like in writeups. Me, you, we’re all just human, we make mistakes — that’s normal. We have to accept it, learn from them and keep going! Try harder!

Kudos to everyone who solved it!

Remember to follow me on Twitter if You want to be up to date with my work!