Open Security WWHF CTF Writeup

One Coin to Rule them All!



Colors Challenge

Looking at the source for this challenge, we can see a few elements that are not displayed within the website itself. We can see various strings of Base64, and colors displayed inline. Considering the name of the challenge, we will be focusing on the colors first.

Regexr – Extracting Color Codes

Pulling these colored strings from the source code, using regular expressions (regex), we can retrieve the listed colors. In HTML, color values that are prepended with “#” are hex triplets or quadruplets.

Hex triplets are formed with three pairs of characters from the Hex numeral system.

More information on this can be found here: Web colors – Wikipedia.

CyberChef – Decoding the Hex Encodings

Decoding the color codes from Hex in CyberChef, reveals the flag.

Alternatively, we could use CyberChef to completely solve this challenge, with the following recipes. This tool is extremely powerful and can be used as an alternative to command line expressions.

Solving the Challenge with CyberChef

This challenge is not very real-world. But it teaches the importance of knowing what tools are in your toolset. A sizable portion of Cybersecurity is data management, data parsing, and automation. Knowing how to optimize the steps to perform this challenge via command line, CyberChef, or another tool will set you up for success in the future.

Lessons Learned:

Data can be hidden in plain sight in many ways. Steganography is the act of hiding text or a file within an innocuous medium (file, text, ect). By learning to look for these trends it teaches a form of problem solving. In this case, we used colors, which were initially overlooked to show the dangers of trusting something as basic as a color palette.

This challenge teaches another core ideology of steganography that in most cases steganography is not detectable, and if this were not a CTF Challenge, most people would overlook the message, and that is what makes it a great method to send secret messages.

Broken Authentication

Problem Analysis:

This challenge simulates a misconfigured MySQL database. As you can see, both “user authentication” and “secret data” are stored in separate data objects. In SQL, this separation of data would be accessible in separate tables.

Challenge Data Structure

The authentication logic has an issue in validating stored data. This part of the application permits multiple accounts with the same username; however, the back end assumes that the secret data stored for each user is associated with a unique username.

This lack of uniformity results in an access violation, which is the most common issue with Web Applications according to OWASP’s Top 10.


In short, multiple accounts can exist with the same username but varying passwords. These accounts are then authorized to retrieve “secrets” that are accessible from that username.

You can retrieve sensitive information from any account, by creating a new account with a username that was already used. Once this new account is created, the adversary can authenticate and retrieve classified information for that user.

Lessons Learned:

This issue is seen repeatedly. Whether it is different teams working on distinct parts of an application, or new features added after release, all it takes is a single developer making an incorrect assumption about how another part of the application should function, and broken access control vulnerabilities will appear.

These mistakes can result in devastating bugs, and are listed as the most common vulnerability in the OWASP Top 10, see Broken Access Control.

Strong source code auditing and regular code reviews can prevent some of these issues from occurring; however, as an application matures the best way to mitigate these issues over time is to perform regular security audits and third-party code analyses.


Surfing the World Wide Web

Problem Analysis:

The DNS (Domain Name System) standard does not strictly define what characters are permitted in subdomains. As a result, abnormalities can result in differing implementations of what is allowed and what is not.

The current consensus is that subdomains can contain hyphens, periods, and sometimes underscores.

However, some DNS providers will permit you to use special characters for Application-Layer protocols that require it. This results in oddities between the implementation of protocols on the internet, and the loose standards that accompany them.

Let us break the internet!


DNS Resolution:

This challenge requires us to retrieve data from the following domain names. This would be an exceptionally easy task if modern web browsers could resolve these domain names. Unfortunately, that is not the case. We will have to do it manually.


What are the IP Addresses associated with these hostnames? To force DNS resolution, we will need to manually identify the server addresses using nslookup.

We will also need a reliable way to type the hostnames in without the BASH Prompt performing events, or other processing errors.

To reduce the characters that we need to escape in BASH, we are going to save the complete list of hosts to a file. Using BASH substitution to retrieve each line as a variable from the xargs command drops the (), therefore we must add it back.

Instead of utilizing ${}, we will use \(\)${} to represent each URL from the challenge.

cat hosts.txt | xargs -I{} bash -c 'nslookup \(\)${}' | grep "Address: " | uniq

Command Output

This command performs a DNS Lookup for each hostname, then filters unique results. We can see that we get one result, meaning that all the host names are currently pointing to the same IPv4 address, and are using VHOSTS.

Instead of using the command line we could use the following service to do a DNS lookup on the respective IP addresses,


Solution Continued – Pathway 1  (/etc/hosts):

/etc/hosts is a file on Linux that forces local DNS Resolution. By forcing host names to map to these IP addresses, we do not have to rely on external name server to resolve these hosts for us.

This file uses lines formatted in the format of: [IP address] [host name], as shown below. localhost

To write to the /etc/hosts file, we will create a temporary file that contains all the pairs that we will need, and then read the file into the /etc/hosts file.

Create a file at /tmp/tmp that contains all the IP address, host name pairs.

 cat hosts.txt | xargs -I{} bash -c 'echo\ \(\)${} >> /tmp/tmp'

Using nano (text editor), we will edit the /etc/hosts file on Linux, scroll to the bottom of the file, and read the data from /tmp/tmp, this will embed the contents of /tmp/tmp into /etc/hosts.

sudo nano /etc/hosts; 
[Ctrl + r] /tmp/tmp [Enter] Read the contents of /tmp/tmp into /etc/hosts
[Ctrl + s] [Ctrl + x] Save and exit the file.


Next, we need to retrieve the data from the web pages, via CURL.

cat hosts | xargs -I{} bash -c 'curl http://\(\)${}/'

At this point you simply parse the output to easily retrieve the flag, or you can parse it through the command line using the following command.

cat hosts | xargs -I{} bash -c 'curl http://\(\)${}/' | jq -r '.key' | tr -d [:space:]

Solution Continued – Pathway 2 (CURL Resolution):

This was the original solution for the challenge, using a feature that very few knew existed in the renowned tool, CURL. CURL has a feature that permits it to manually resolve hosts, (over HTTPS or HTTP).

The following is a bash script to enumerate the endpoints, and force CURL to resolve the host name to the IP address.

Save the following script as

for number in {1..16}
  export url=\(\)[]$number\!
  curl --connect-to `echo "$url:80:"` $url
exit 0

Finally, we need to mark the saved file as executable, run it, and cleanse the output.

chmod +x; bash | jq '.key' | tr -d [:space:] | tr -d '"'

Solution Continued – Pathway 3 (Net Cat):

This solution uses netcat to establish HTTP Connections. It creates multiple files containing the commands to create and tear down sessions, it then executes, and removes all the excess files.

Save the following content to

for number in {1..16}
  export url=\(\)[]$number\!
  echo "nc 80" > url$number
  echo "GET / HTTP/1.0" >> url$number
  echo "Host: "$url >> url$number
  echo "" >> url$number
  echo "" >> url$number
for n in {1..16}
  cat url$n | bash | tail -n 1 | jq '.key' | tr -d [:space:] | tr -d '"'
rm  url?*
exit 0

Then to execute the script and parse the output, the following can be used.

chmod +x; bash

Lessons Learned:

Domain names containing characters they should not contain. This reminds me of a phrase I say much more often than I should. NEVER TRUST USER INPUT! NEVER! User input should always, always, always be sanitized to prevent these types of assumptions. Something as innocuous as a user passing a subdomain, can pass malicious symbols that “shouldn’t exist in a subdomain.”



Doggers is a challenge based around a shady organization passing messages via a photo board. The challenge instructs users to look for hidden messages, and that some doggers may be valued more so than others, with other hints like “browse the source.” Viewing the source reveals a conversation in hidden elements.

Hidden Messages

Reading the hidden messages, we now know that the hidden key is MK70-1. The encoding scheme used is Dots1n, which is an Open-Source project. Searching GitHub directly results in a single package.

GitHub for Dots1n

Of course, it is a “one-way Cryptor,” so the author did not provide a decrypting function. Great!

decode(encode(“plaintext”, key),key) # Should return “plaintext”

To reverse the logic of this library, we need to reverse the order that the functions are actuated.

Order of Operation – Encode vs Decode

We will have to reverse the logic and change each “encode” function to its “decode” counterpart. (i.e., Base64 encode => Base64 decode)

Writing a Decode Function

With these changes, we should now be able to test the functions with the following logic. Since these are inverse functions, passing the encoded text, with the secret into the decode, should output the original PLAINTEXT.

print(decode(encode("PLAINTEXT", "secret"),"secret")) # return “PLAINTEXT”

Previously, we read that “Doge-32 is encrypted…”

Reading the source code, the dog photos are all saved in a single directory. That directory contains an unlinked file “32.jpg”, available here. This file should have the md5sum of 5cf8c2e7f93f7e49cb0218ce80caa3d8.

Using Python3, we can load the file as input, then save the decoded file as output.

Python3 Decoding the Dogger!

Finally, we can verify the type of file that we decoded. Which is a simple JPG. Open the file, and you will find the flag.

file ./32.decoded.jpg
>> 32.decoded.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, comment: "Compressed by jpeg-recompress", progressive, precision 8, 211x59, components 3

Lessons Learned:

Security through obscurity is a valid technique at increasing defense posture if it is not implemented as the sole technique.

Overall, we must remember the reliability of the Open-Source projects does not implicitly translate to increased security. Malicious projects do exist that have built-in backdoors, as we pivot to a world of pip and npm based installers, we must ensure that we are using reputable libraries, actively manage them to ensure that they are up to date to guarantee security.


The Wild West Hackin’ Fest CTF was an overarching success, with a ton of positive feedback from the community.

Do you have any solutions that weren’t listed here, let us know! While building new CTF challenges we would love to hear from the community about how we did, and what we failed to consider in crafting the challenges. More importantly, we can’t wait to host the next one, where there will be even more challenges, prizes, and fun. Join the Discord and follow our Twitter for more information as the event draws close.