Datadog RSA CTF 2021

Ari Kalfus | May 24, 2021

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!

Completed Challenges

Best Doggos

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.

100 points

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.

Login Page

I registered a new user (meow / meow) and then logged in. After logging in, I could view my user settings page, where I found the flag.

Best Doggos 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 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.

100 points

(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.

Datadog Infrastructure Menu

Alrighty then. Wonder what I’ll have to do on this page to locate… Oh.

Watching The Dog Flag

Easy peasy. Selecting the process brings up more information and also makes it easier to copy the flag from the page.

Watching The Dog Flag Selected

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.

100 points

All right, this time we’re looking through Nginx and ModSecurity logs. I navigated to the Logs dashboard and filtered by Source:nginx. I arrived at

There were a number of HTTP Status Codes to filter the search by:

HTTP Status Codes Filter

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:

HTTP 418 Filtered Logs

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.

100 points

I ran the common.txt wordlist from seclists with gobuster against the Docker container and didn’t find anything. I tried again with directory-list-2.3-medium.txt, a personal favorite.

Gobuster Brute Force

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.

Walking The Dog Flag

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.

100 points

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:

SQL Select Error

I received an error about the number of columns in my query, which is great news.

SQL Select Error Query

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:

SQLi 4 Columns

This returned a different error, which I read as related to processing the NULL results. Due to this, I believed I found the correct number of columns.

SQLi 4 Columns Response

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.

SQLi Version Command

I view source on the page and see that a string in the first column will insert into href inside the list:

SQLi Version Output

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 blog. The query was (note the space after the comment --):

SQLi Database Command

I then needed to understand what tables and columns were available inside the blog database. 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_schema. 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 blog database:

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 blog database.

SQLi Extract Tables and Columns

I constructed the following schema of the blog database:

- version_num

- id
- title
- age
- content
- author_id

- id
- username
- pw_hash
- is_admin
- av_path

- id
- smtp_user
- smtp_pass

- 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 -- 
SQLi Users Passwords

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:

SQLi Comments

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 smtp_user and smtp_pass columns:

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:

SQLi SMTP Config

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.

100 points

I return to my window of Logs in the Datadog platform filtered by Source:nginx. I can filter the results to only the modsecurity service:

Logs Service Filter

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.

Logs Modsecurity Filter

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_user and smtp_pass columns in the config table triggered alert REQUEST-942-APPLICATION-ATTACK-SQLI.conf. This was the flag.

Chasing The Dog 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.

comments powered by Disqus