The Dojo challenge, Hacker forum, asked participants to perform a second-order SQL injection, inserting a new value into the database which later exposes the flag in the result of a SQL SELECT statement.
💡 Want to create your own monthly Dojo challenge? Send us a message on Twitter!
The winners
Congrats to snurkeburk, kto94 and Tofu for the best write-ups 🥳
The swag is on its way! 🎁
Subscribe to our Twitter and/or LinkedIn feeds to be notified of upcoming challenges.
Read on to find out how one of the winners managed to solve the challenge.
TALKIE PWNII #2: VIDEO CHALLENGE WRITE-UP
OVERALL BEST WRITE-UP
We want to thank everyone who participated and reported to the Dojo challenge. Below is the best write-up overall:
————– START OF snurkeburk‘s REPORT —————
Description
A critical vulnerability was identified within the post comment functionality. More specifically, SQL injection is possible via the author field when posting a comment, giving the attacker the ability to modify and extract sensitive information such as user passwords.
Code Review
During the initial reconnaissance phase, a thorough code inspection was conducted in which the underlying tech-stack was revealed. The application uses PHP that connects to an SQLite3 database, as shown in the first three lines of the source-code:
<?php
chdir('/tmp');
$db = new SQLite3('forum.db');
This immediately sparked concern for potential PHP code– or SQL injection.
Next, the application defines three PHP functions: makePost()
, addComment()
, and get()
, which are used in the context of rendering posts with their respective comments.
Further down, on line 53, we encounter user-supplied input for the first time. Two variables are defined: $input_author
and $input_comment
.
Next, immediately after the user-inputs are defined we find the code in which the user-comments are inserted into the SQL database.
$stmt = $db->prepare(
"INSERT INTO comments (post_id, author, comment, image) VALUES (1, :author, :comment, 'https://static.vecteezy.com/system/resources/previews/035/672/488/non_2x/ai-generated-orange-cat-with-sunglasses-giving-thumbs-up-on-transparent-background-free-png.png')"
);
$stmt->bindValue(":author", $input_author, SQLITE3_TEXT);
$stmt->bindValue(":comment", $input_comment, SQLITE3_TEXT);
$stmt->execute();
A note was taken that the application makes proper use of prepared statements in the SQL query – A remediation technique used to prevent SQL injections. At first, this seemed to contradict my initial belief of finding a SQL injection.
Continuing to read the code, we are presented with a while-loop which fetches all of the posts from the database and sends them to the makePost()
function defined on line 9.
while ($p = $posts->fetchArray(SQLITE3_ASSOC)) {
$post = new Post();
$post->makePost($p['author'], $p['banner'], $p['title'], $p['post']);
Within this while-loop, a query is made to the SQL database to fetch all of the comments associated with the post being processed in the current iteration. What stands out in regards to this specific query is the lack of using prepared statements, as the aforementioned query demonstrated on line 57. In fact, it is making use of the PHP sprintf()
function with the %s
flag, effectively embedding the $p["id"]
value directly into the query.
$comments = $db->query(
sprintf("SELECT author, comment, image FROM comments WHERE post_id = '%d'", $p["id"])
);
Let us play with the thought of id
's value being under our control, and we set our id
to be equal to 50. The sprintf()
function would have created the following query:
SELECT author, comment, image FROM comments WHERE post_id = '50'
Theoretically, this would have been susceptible to SQL injection. Assume we enter an id
value of 50' OR post_id = '1' --
, the following SQL query would have been constructed by sprintf()
:
SELECT author, comment, image FROM comments WHERE post_id = '50' OR post_id = '1' --'
By including a single quote (') after our id, we deliberately close the string variable within the query, allowing us to append additional SQL commands ourselves. Here, an attacker would have had the opportunity to edit other peoples posts through the SQL injection by specifying someone else's post_id
. However, since there is no way to control that specific parameter, we are forced to let it go and move on.
Moving along down the code, in order to hinder negativity and haters from hating, a second nested while-loop is constructed to check for any "bad words" within any of the comments. These "bad words" are found using a regex-matching technique that look for words such as "bad", or "terrible". In case a comment is identified as negative, or rather, in case a "hater is found hating", the application imposes a ban on the user and censors the comment. It does this by setting banned = 1
as a property of the user in the SQL database.
// Ban haters (haters gonna hate)
while ($comment = $comments->fetchArray(SQLITE3_ASSOC)) {
if ( preg_match("/(bad|terrible|worst|skid)/", $comment['comment']) ) {
$db->exec(
sprintf("UPDATE users SET banned = 1 WHERE username = '%s'", $comment['author'])
);
$post->addComment($comment["image"], $comment["author"], "*****", true);
} else {
$post->addComment($comment["image"], $comment["author"], $comment["comment"]);
}
Once again, the SQL query is constructed using sprintf()
. This time, it embeds the author value directly into the query for the users table. Remember, author is a field defined on line 54 as $input_author
, meaning we have full control of the value. Due to the similarity of our previous theoretical SQL injection with this one, setting $input_author
to be a single quote (') should generate an SQL query as shown below, consequently breaking the original query. Notably, in order to reach the vulnerable code, we must enter one of the "bad words" as the $input_comment
.
UPDATE users SET banned = 1 WHERE username = '''
Indeed, after sending a request with $input_author='
and $input_comment=bad
, an error is shown when viewing the HTML source code:
PHP Warning: SQLite3::exec(): unrecognized token: "'''" in Standard input code on line 377
Thus, this confirms the presence of a SQL injection.
Exploitation
A payload was constructed that extracts a user password in order to demonstrate the risks of having such a vulnerability present in an application.
In order to construct such a payload, two conditions must be met and as such the payload achieves the following:
- Extraction of the user password.
- The ability to access the extracted password.
The following steps were taken when constructing the payload:
- Deliberately close the string variable in order to include our own SQL query.
input_author='
- When posting a comment, the comment field must include a "bad word" such as bad in order to reach the vulnerable part of the code.
input_comment=bad
- Create a query that extracts the password from the user "Brumens" using a
SELECT
statement:SELECT password FROM users WHERE username = "brumens"
- In order to access the password, it has to be reflected in the response. As shown earlier, comments are created and then stored within the database. This means that we can simply inject a new comment directly into the SQL database with one of the reflected values to be that of Brumens password!
INSERT INTO comments (post_id, author, comment, image) VALUES (1, (SELECT password FROM users WHERE username = "brumens"), "", "")
- Finally, we end our SQL query with a double-dash (--). A double-dash is a comment-marker in this version of SQL, and anything that might come after it which could break the payload will be commented out and ignored.
Combining the steps above:
input_author='; INSERT INTO comments (post_id, author, comment, image) VALUES (1, (SELECT password FROM users WHERE username = "brumens"), "", "") --
input_comment=bad
The resulting comments are created on the Hacker Forum:
Giving us the password FLAG{Vuln3r4b1li7y_Exp0s3d!!} for the user Brumens.
Risk
Having a SQL injection vulnerability present in the application poses a serious threat. Data theft and manipulation in combination with authentication bypass has been demonstrated, however, there are many more possible ways for an attacker to leverage this vulnerability. It is a threat not only to the application itself, but for the users whose data may be exposed or altered, and to the company's reputation, which is at great risk.
As demonstrated, this vulnerability can be leveraged for an account takeover. However, the root cause of the account takeover is rather manipulation of data stored in the database. SQL injections can be leveraged in other ways, such as denying service for users by shutting down the database or deleting content (DoS).
Remediation
In this case, there are multiple remediation techniques that are recommended.
- Firstly, the SQL queries on lines 72 & 78 should make use of prepared statements, as is done on line 58. These have been proven to fix many issues regarding SQL injections, more information can be read here: https://www.php.net/manual/en/mysqli.quickstart.prepared-statements.php
- Secondly, the WAF should feature protection against input-based attacks. This may include but is not limited to input sanitizing, which is a technique used to remove any dangerous characters such as
'
,;
,()
etc. The WAF should include an allow list – a list containing characters that are allowed to pass the WAF. Furthermore, these input-checks should be replicated on the client-side. - Lastly, the application should implement client-side password hashing techniques. The account takeover would still have been possible, albeit a little bit more time consuming. But had the SQL user not been able to write to the users password field, and the password was hashed, the account takeover likely would not have been possible.
————– END OF snurkeburk‘s REPORT —————