Challenge 1: SQL Injection
Time to start with the first challenge. For this challenge, the attacker will have to exploit a poorly secured login page and access an administrator's account
What is SQL injection?
A quick refresher in case you forgot / don't know how SQL injection works: SQL injection relies on the fact that the attacker can inject some malicious SQL code as user input and pass it directly into the backend query without being sanitized
Example of a basic SQL injection attack:
SELECT user_data FROM accounts WHERE username = ? AND password = ? -- An SQL query is evaluated from left to right+----+----------+----------+-----------+
| ID | username | password | user_data |
+----+----------+----------+-----------+
| 1 | admin | 4dM1nPwD | ... |
| 2 | john_doe | password | ... |
+----+----------+----------+-----------+When the attacker enters "admin" into the username and password field
SELECT user_data FROM accounts WHERE username = 'admin' AND password = 'admin' -- returns FalseWhen the attacker enters "admin" into the username and " x' OR 1=1 -- " into the password field
SELECT user_data FROM accounts WHERE username = 'admin' AND password = 'x' OR 1=1 --' returns true since 1 is always equal to 12 rows affected
+----+----------+----------+-----------+
| ID | username | password | user_data |
+----+----------+----------+-----------+
| 1 | admin | 4dM1nPwD | ... |
| 2 | john_doe | password | ... |
+----+----------+----------+-----------+This is the basics of how SQL injection is performed. In the later sections, we'll go through the steps to create the vulnerability and some basic processes we'll take to make it a little harder for the attacker to exploit SQL injection
Creating the vulnerable system
We'll first create a simple login form for our user to log in with

We can't forget the backend logic too.
With this, we now have a very simple login form


Implementing the vulnerability
To keep this challenge short and simple, we'll directly print out the flag along with the login confirmation message. If you wish to make a more complex challenge, you can incorporate other parts into this, such as directory traversal to find an admin-only page
Because we were using sqlite3's query parameters, this makes it significantly harder for our participants to perform SQL injection. To reduce the difficulty, we will change our query to use a format string instead
In the updated code, we have replace the original query parameters with a format string. This will remove any sanitization that may be performed before query execution.

It looks like the vulnerability works as expected, but wait! What if we try another staff ID (for example, Jane's staff ID)

If we take a look at our query, we can see what is going on
When we execute this query, the database will try to match these criteria
There is a staffID in accounts with the value "654321", and the password is ''; or
The value of 1 is equal to 1
Since 1 is always equal to 1, therefore the statement will evaluate to true and return the following output
Our code only takes the first item in the list, so it will always return the administrator account. While it looks good on paper, this will not do for our challenge. What if we have the flag in the 3rd entry of the database, then the challenge will be impossible to solve? To fix this, we have to add some parenthesis to our code to establish the order of operations
Now, when we evaluate the query, the program will first check if the provided staff ID exists in the database, then check if the corresponding password is correct OR if 1 is equal to 1. Afterwards, the AND operator is then executed and the SELECT statement gives us the result

Let's up the difficulty now!
Making the attacker's life hell
As we have mentioned previously, using sanitization will help to prevent a large majority of SQL injection techniques. This can range from simple things like removing non-alphanumeric characters, to blacklisting all SQL commands like SELECT, UNION and more.
For our challenge, we will be using two different sanitization methods:
Whitespace stripping (removing all whitespaces)
Simple case-sensitive input stripping (replacing matches with empty strings)
Alright, let's get to work!
Now, when we try our original payload, we will fail the injection. We add a try...except statement here to prevent the attacker from figuring out that their query has failed, and preventing additional data about our code from being leaked. To add additional difficulty, we even stripped away lowercase keywords (e.g. select, union, order by), so the attacker will have to be more creative with their exploits

To exploit the form now, we will have to use some creative means of getting what we need. Lets walk through each sanitization technique and how the attacker can bypass it
Whitespace stripping: The attacker can use multi-line comments (using the /* and */ symbols) to create spaces
Case-sensitive keyword stripping: The attacker can use a mix of upper-case and lower-case characters to call the different methods (e.g. SeLeCt instead of SELECT/select)
Testing these bypasses out, we can see that we are now able to inject the query again

Congratulations! You have made your first web challenge.
As a final touch, we will add some extra data into our login database, and insert the flag

Since we have the "data" column now, we have to modify our code to return the column somehow, or the flag will never be retrievable

Replace the flag with your CTF's flag format
Conclusion
Congratulations, you now have a fully-fledged web challenge! In the next chapter, we will create a challenge that requires the attacker to use directory traversal to access the Foundation's secret list of safehouses, as well as some methods to prevent directory traversal attacks.
The code for this section can be found here.
Last updated