Datadog held a small 2-hour CTF at the conclusion of RSA 2021 in partnership with HackTheBox. The challenges were a mix of defensive (on Datadog’s new security product) and offensive challenges. I completed 6 of the 11 challenges in 1.5 hours, putting me at #27 out of 78 teams. My teammate had technical issues accessing HackTheBox’s CTF site so I was on my own. However, I won 1st place with the submission of this writeup!
For this CTF, you’re given a server (running in a Docker container) that is hosting a website. Your server is also monitored by DataDog. Your team’s Datadog credentials are being created and will show up on the right side of this page. Then you can start your team’s instance of the server by clicking the toggle on the right side, and it will give you an IP and port for your site. It may take a minute to start. Take a minute to browse the site and interact with it. You’ll find a flag on the site.
This was the introductory challenge. After starting up my Docker container and accessing the site, I found a rudimentary blog with a couple of articles and user registration + login forms.
I registered a new user (
meow) and then logged in.
After logging in, I could view my user settings page, where I found the flag.
Watching the Dog
For this CTF you are given a Docker Container hosting a website that is monitored by DataDog. Once you spin up the docker container and login at https://app.datadoghq.com/account/login with the credentials provided to you, you can begin exploring the platform. The first flag is a running process, which is accessible under the Infrastructure menu.
(Yes, I also noted that this is the only challenge with a lowercase “the” while the others capitalize as “The.”)
Well, I will commend Datadog for these defensive challenges. I have no idea how to navigate their platform but found it very easy to locate the flags. Not sure whether that means they were easy to locate or the platform is intuitive, even for dummies. I’ll take it either way.
The instructions for this challenge told me to look at the running processes under the Infrastructure menu.
Alrighty then. Wonder what I’ll have to do on this page to locate… Oh.
Easy peasy. Selecting the process brings up more information and also makes it easier to copy the flag from the page.
Dog Old Days
Logs are also available to search through, for this event we will focus on NGINX and ModSecurity Logs. There is a request with a unusual HTTP Status Code. The flag will be on the page visited.
All right, this time we’re looking through Nginx and ModSecurity logs.
I navigated to the Logs dashboard and filtered by
I arrived at
There were a number of HTTP Status Codes to filter the search by:
HTTP 418 immediately caught my eye. I’m a teapot! Unlikely to be legitimate traffic. Filtering for HTTP 418 status code, I saw the location to travel to on the docker container:
Unfortunately, it seems I did not take a screenshot of this flag. It’s, uh, left as an exercise to the reader.
Walking The Dog
Do a Directory Bruteforce across the web page (ex: GoBuster, Ffuf, DirBuster), when you find the hidden path it will provide you with a flag. The flag starts with the letter d and is all lower-case letters.
It took several minutes to get 5% of the way through the list, but that is ok, because I found the lowercase path beginning with a “d.” I’m more of a cat person, personally.
Doggy Drop Tables
There is an SQL Injection Vulnerability within this web application, dogs are known for destroying play toys when left unattended. Your automated tools may fail you with this one, but it is relatively easy to exploit by hand with union injection. The flag is in the database.
The search functionality on the website was the first thing I thought of in regards to this challenge. Sure enough, it appeared vulnerable to SQL injection:
I received an error about the number of columns in my query, which is great news.
I needed to figure out how many columns existed in the table being queried by this search form.
One process for doing this is to iteratively add
NULLs until the errors go away.
With this process, I determined there were five columns in this table:
This returned a different error, which I read as related to processing the
Due to this, I believed I found the correct number of columns.
The next task was to identify what type of database was running on the system.
In easy cases, you can port scan the server and identify an open port to suggest what backend database is in use -
3306 for mysql,
5432 for postgres, etc.
Without that, another good option is to leverage Portswigger’s SQL Injection Cheat Sheet and use database-specific syntax to determine what you’re working with.
I am a fan of using the database version commands, as the successful response will tell you exactly what flavor of database is running, plus you can check for public exploits depending on the version.
For example, I tried the
version() command, which is specific to Postgres, which didn’t work.
I try the version commands on that cheat sheet in each column of my SQLi payload, as I don’t know which column gets written to the screen somewhere I can read.
I am not sure if a different column would have been easier to read from, but I found the first column injected results in the
href attribute of the
<a> attribute on the
<li> in the page.
And, I determined that I was working with a MariaDB database, a flavor of MySQL.
view source on the page and see that a string in the first column will insert into
href inside the list:
I moved to Burp Suite (community edition in this case) to more easily repeat my queries as I extracted information from the database. Now that I knew what database flavor I was targeting (MySQL), I needed more information about where I was in the database. The challenge said the flag was somewhere “in the database.” This means we need enumeration.
Every time I do this I google everything, so I had Google tell me that
database() is the command I can run for MySQL to tell me what application database I am running within.
I was in the database
The query was (note the space after the comment
a' UNION SELECT database(),NULL,NULL,NULL,NULL --
I then needed to understand what tables and columns were available inside the
But, I only had one text column available to me.
My go-to in this circumstance is the MySQL GROUP_CONCAT() command.
This lets me select arbitrary columns from a table and concatenate them into one string, so the result will fit into my payload.
With MySQL, I can enumerate tables and columns from
Information about what columns are available in
INFORMATION_SCHEMA.COLUMNS is available in the documentation.
My next attack extracts the tables and columns within each table from the
a' UNION SELECT (SELECT group_concat(TABLE_NAME, ":", COLUMN_NAME FROM information_schema.columns WHERE TABLE_SCHEMA = "blog"),NULL,NULL,NULL,NULL --
Do not shame me for my inconsistent SQL capitalization, please.
This query results in a colon-delineated URL-encoded list of tables and columns in the
I constructed the following schema of the
alembic_version - version_num posts - id - title - age - content - author_id users - id - username - pw_hash - is_admin - av_path config - id - smtp_user - smtp_pass comments - id - author_id - post_id - comment
With this information enumerated from the database, I could make informed queries for specific pieces of information. For example, I could extract the usernames and password hashes from the database. Hi, Garrison!
a' UNION SELECT (SELECT group_concat(username, ":", pw_hash) FROM users),null,null,null,null --
I could also dump all comments posted on the blog and correlate to user IDs.
a' UNION SELECT (SELECT group_concat(author_id, ":", comment) FROM comments),null,null,null,null --
URL-decoding the results turns into:
Back to the task at hand.
I needed a flag found somewhere in the database.
Progressing through the tables, I ended up querying the
config table for
a' UNION SELECT (SELECT group_concat(smtp_user, ":", smtp_pass) FROM config),null,null,null,null --
I took the URL-encoded result and decode it to reveal the flag:
Chasing The Dog
Modsecurity has the abiltiy [sic] to examine POST Data and should have detected the SQL Injection. What is the name of the rule that detected your activity. The flag is the rule name only, not the entire path.
I return to my window of Logs in the Datadog platform filtered by
I can filter the results to only the
This returned a long list of modsecurity logs. Not quite sure what the “this phrase is the flag” messages were, perhaps for a later challenge.
There was undoubtedly a slick way of querying these logs via the Security Signals or Rules sections in Datadog, but I just clicked on a few of the alerts and found what I was looking for.
I could see that my SQL injection attack for the
smtp_pass columns in the
config table triggered alert
This was the flag.
The next challenge was called
Where's Doggo, and included the message:
With SQL Injection it is possible to exfiltrate hashed credentials, however cracking is not always the answer. Combine pieces of information within the database in order to perform a Password Spray. The flag is on the page after login.
I dumped the text from the
posts table and all the
comments and created a wordlist from the individual words.
I started a password spray against the login form with all usernames I dumped from the
users table, however I did not find a match before the CTF time ended.
I wonder if the !Summer2020! / !Winter2020! values from the
config table were what I should have used…
I will have to find another write-up and see!
This was a very enjoyable quick CTF. I particularly liked seeing my offensive attempts reflected in the logs in Datadog and querying those for additional findings. No team solved more than 9 of the 11 challenges, perhaps due to the fact that we had to complete each challenge before the next unlocked and it took around 15 minutes for the data for challenge 2 to populate in Datadog. Overall, we had just under 1.5 hours to complete all the challenges. Would love an opportunity to compete in a similar CTF, perhaps with a bit more playable time.