February 14, 2023

SQL injection challenge

The #21th DOJO CHALLENGE, EvilTwin-Admin aims to exploit a SQL logic vulnerability by registering a new administrator user, resulting in a Broken Access Control.

💡 You want to create your own DOJO and publish it? Send us a message on Twitter!


We are glad to announce the #21 DOJO Challenge winners list.


  • The best write-ups reports were submitted by: Floczii, W00dy and clhack! Congrats 🥳

The swag is on its way! 🎁

Subscribe to our Twitter and/or Linkedin feeds to be notified of the upcoming challenges.

Read on to find the best write-up as well as the challenge author’s recommendations.

The challenge


See the challenge page >

We asked you to produce a qualified write-up report explaining the logic allowing such exploitation. This write-up serves two purposes:

  • Ensure no copy-paste would occur.
  • Determine the contestant ability to properly describe a vulnerability and its vectors inside a professionally redacted report. This capacity gives us invaluable hints on your own, unique, talent as a bug hunter.


We would like to thank everyone who participated. The #21 DOJO challenge provided us with a large amount of high quality reports and everyone did a great job!

We had to make a selection of the best ones. These challenges allow to see that there are almost as many different solutions… as long as there is creativity! 😉

Thanks again for all your submissions and thanks for playing with us!

Floczii‘s Write-Up

————– START OF Floczii‘s REPORT ——————

Improper Access Control by SQL Logic Vulnerability | CVSSv3 8.6 | CWE-284

Description of the vulnerability

An improper access control vulnerability is a type of security vulnerability that occurs when a system or application does not properly enforce access restrictions. This can allow unauthorized individuals to access sensitive information, perform unauthorized actions, or gain elevated privileges. Improper access controls can occur in a variety of ways, such as weak authentication mechanisms, missing access controls, or flawed access control logic. This type of vulnerability can be exploited by attackers to gain unauthorized access to sensitive information or systems, steal sensitive data, perform unauthorized actions, or even take control of the system.


An attacker can become the only administrator of the concerned application by creating a new user that meets the requirements to overwrite the current admin user. He can then log-in with his custom email and password and access admin features.

Steps to reproduce

This is the Sqlite code used in the challenge :

/*Create a new user to our website:*/
INSERT INTO users(id, username, email)
  $id, LOWER(REPLACE(LTRIM('$username', ' '), 'admin', 'Nope')), 'EvilTwin'||'@'||''

UPDATE users
  SET password = cast(x'$passwd' AS TEXT), role = IIF( $id > 1, 'USER', 'ADMIN')
WHERE id=$id;

/*We should only have one admin. Limit the output:*/
SELECT ('===(RESULT)=='=0),* FROM users WHERE ( username='admin' AND role=upper('admin') ) AND password LIKE cast('FLAG{%}' AS TEXT) LIMIT 1;
--_____   _____    __    __   __   __   __    
/*  __ \ /\  __-. /\ `-./  \ /\ \ /\ `-.\ \   
\ \  __ \\ \ \/\ \\ \ \·./\ \\ \ \\ \ \·.  \  
 \ \_\ \_\\ \____, \ \_\ \ \_\\ \_\\ \_\\"\_\ 
  \/_/\/_/ \/____/  \/_/  \/_/ \/_/ \/_/ \/*/

/*You need help? Debug you process*/
-- return
SELECT ('===(DEBUG)=='=0),* FROM users WHERE id = $id;

We also have some input restrictions :

  • The $id allows only numbers
  • The $username and $passwd fields prohibit the single quote ' and backslash \

The first query allows to insert a new user to the database, with user-supplied id and username. Note that the ltrim(), replace() and lower() functions are applied on the username, they respectively remove leading spaces, replaces the “admin” string with “Nope”, and lowercase the input.
Also, we can’t use the id 1, because it is already the admin’s id which is the primary key of the user table.

The second query sets the password and the role for the newly created user. The passwd input is casted from hexadecimal to text, so it must be supplied in this format (hex characters) else an error is thrown. Then the role is set depending of the user’s id : If it is greater than 1, the role is “USER”, else “ADMIN”.

Finally, the third query is supposed to search for the admin user, which has the username “admin” and the role “ADMIN”, in addition to a password looking like “FLAG{%}” with % matching any sequence of zero or more characters. The output of this query is limited to 1, because there should be only one admin 🙂


We can notice several flaws allowing us to become the new administrator of the app.

The first flaw is in the id input. The role of “ADMIN” is given only if this value is greater than 1, with 1 already being the admin’s id. But we can set it to 0 which is lower than 1 since all numbers are allowed, so our user is given the “ADMIN” role.

Moreover, the username is not strictly checked at creation because the lower() function is applied right after the replace() that removes the lowercase string “admin” from the field. It means we can set our username to “admin” by supplying a value with at least one uppercase letter like “ADMIN” to bypass this filter.

Eventually, we set our password to the hexadecimal value of the string “FLAG{AnythingHere}” to match the admin’s password : 464c41477b466c6f637a69697d (Here “FLAG{Floczii}”).

We now fulfill all requirements to be returned by the third query, as a user with username “admin”, role “ADMIN”, and password matching “FLAG{%}”.

Since the output of the last query (checking for the administrator) is limited to 1 row and our new user has the id 0, we are the first (and only) row being returned and we should have access to the application with our custom password.

We successfully added our EvilTwin user with a custom password as the only administrator of the app !


To complete the challenge, we can fill the input fields with the following values :

$id : 0
$username : ADMIN
$passwd : 464c41477b466c6f637a69697d


First of all, the admin’s id can be strictly checked with a condition like “$id = 1” instead of the current one so an attacker could not create a user with the “ADMIN” role. Also, the role “USER” can be set by default at insertion since it is not supposed to have more than 1 administrator, instead of updating the table.

Furthermore, the username check can be more efficient if the replace() function is called after the lower() one.

Last but not least, a password check should not be done with a “LIKE” statement, which is way too permissive. The unique id of the admin should be taken into account when checking for the right user, and not only the username which can be bypassed.


————– END OF Floczii‘s REPORT ——————