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:
- The challenge domain is doggo.buggywebsite.com
- The objective is to access Python variable called **SECRET_API_KEY
- We have to find the memory leak
First look at the page
Let’s then jump to the domain:
By looking at the proxy (Burp) it is possible to see these four requests:
We’ve got a second domain — doggo-api — and also a script.js.
Let’s get a look at this script:
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:
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:
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?
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:
Okay, second S3 Bucket and EC2 instance. I like buckets, especially the open ones 😉 so why not, let’s try some listing:
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!
There was an interesting finding, /heapdump endpoint, which when used, returned:
The same response when I tried to call /dogs!
The theory
At this stage, I jumped to two conclusions:
- We have to call /heapdump by passing on properly crafted x-param value using /get-dogs API endpoint
- We have to break encryption somehow
And also, two possibilities how to do it:
- By analyzing the encryption, try to modify this ciphertext (Padding Oracle attack or some CBC modification)
- 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:
|
|
And verified this with ciphertexts generated by /fingerprint:
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:
“/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:
|
|
Status 404 — the same as before — that’s correct.
We need to create something like this:
|
|
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:
|
|
Of course, we need to obtain /heapdump:
|
|
Now add some traversing:
|
|
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 😉
|
|
401 Unauthorized — we’ve got this! Let’s try encrypting it and using as x-param:
Oh, the ../../ part was deleted… Let’s try to URL encode it:
|
|
Here we are, our beautiful heapdump response!
By scrolling down I was able to find the 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.
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!