Hidden in Plain Sight- Using Dev Tools to Find Security Flaws

A common misconception is that all security testing is complicated.  While some testing certainly requires learning new skills and understanding things like networks, IP addresses, and domain names, other testing is extremely simple.  Today we’re going to talk about three security flaws you can find in an application by simply using your browser’s developer tools.  These flaws could be exploited by an average user of your application, not just a well-trained black-hat hacker.  

Editing Disabled Buttons: 
When you are on a web page that has a button that is disabled and only enables when certain criteria are met, such as filling out all the fields in a form, it may be possible to enable it.  I’ve included instructions on how to do this in my blog post on testing buttons.  
A user of your application might use this flaw to get around submitting a form where there are required fields that she doesn’t want to fill in.  Or a user might enable an Edit button and submit edits to data that he shouldn’t be able to edit.  
Viewing Hidden Data:
Recently someone showed me a security flaw in an application that was listing contact details for various members of the site.  Depending on the user’s access rules, there were certain fields for the members, such as their personal address, that were not displayed on the page. But when the developer tools were opened, all of the hidden fields were displayed in the Elements section!  
Any user of this application could open up the developer tools and search through them to find personal data for any of the members of the site.
Finding Hidden Pages:
It’s possible to find links that are not displayed on a web page by looking in the Elements section of the developer tools.  Let’s try this out using the OWASP Juice Shop.  I’ll be providing instructions using Chrome, but you can also do this with Firefox and Internet Explorer.  Navigate to the Juice Shop, then click on the three dots in the top right corner of the browser.  Select “More Tools” and then “Developer Tools”. This will open the Dev Tools section.  Click on the Elements tab if it is not already selected by default.  We’re going to take a look through the HTML of the page to see if we can find links that are not displayed in the browser.  Let’s get our bearings by clicking on a displayed element in the page.  On the Juice Shop main page, there is a nav bar on the top of the page, with links such as “About Us” and “Contact Us”.  Right-click on one of the elements in the nav bar, and choose “Inspect”.  Notice that this highlights the corresponding section of the HTML in Dev Tools.  You’ll see a number of items all tagged with the <li> tag; these are the items in the nav bar.  Open up each one by clicking on the carat on the left side, and you’ll find one that looks like this:
The status of this list item is hidden, as you can see by the “ng-hide” attribute.  The ‘ng-show=”isLoggedIn”‘ attribute tells us that this nav bar item should only display when the user is logged in.  Finally, we see the link that the nav bar item would take us to if we were to click on it when it was displayed: “#/recycle”.  Let’s try navigating to this link by changing the page’s URL from https://juice-shop.herokuapp.com/#/search to https://juice-shop.herokuapp.com/#/recycle.  Click Enter, and you will be taken to the recycle page.  You have successfully navigated to a page that you should only have access to if you have logged in!
This is why it is important to do authorization checks when a user navigates to a page.  It’s not enough to simply hide a link, because it’s so easy to find hidden links by looking in Dev Tools.  Any user could find hidden links in your application and navigate to them, which could give them access to an admin page or a page with data on other users.  
As you can see, testing for these types of security flaws is quick and easy!  I recommend checking for these flaws whenever you test a web page.  And here’s a bonus vulnerability; if you create an account and log into the Juice Shop, and look in the Network section of Dev Tools, you will see your username and password in plain text!  We’ll talk more about this next week, when we take on Session Hijacking.  

How to Craft a Union SQL Injection Attack

Last week, we learned how to craft a basic SQL injection attack.  This week, we’ll learn how to do something more elaborate.  We are going to use a simple search request to get a database to show us all of the records in a table of users!

It’s important to know how to craft attacks like these, because it helps you find vulnerabilities in your company’s application.  When you find vulnerabilities, they can be fixed before a malicious user exploits them.

In order to see a Union attack in action, we will be using the awesome OWASP Juice Shop website.  This site was created by Bjorn Kimminich, and it is so helpful in learning how to test for security vulnerabilities.  Bjorn has written a companion ebook for the site, which provides explanations of various security attacks, hints for the security challenges, and answers to the challenges if you get stuck.  The attack I’ll be walking you through today is from his “Retrieve a list of all user credentials via SQL injection” challenge.

Navigate to the main page of the Juice Shop site using Chrome, then click on the three dots in the top right corner of of the browser.  Choose “More Tools”, then “Developer Tools” from the dropdown menu.  This will open up the Chrome Dev tools on the page.  If the Dev Tools load on the side of the page, you may want to move it to the bottom of the page instead, so the console messages will be easier to read.  You can do this by clicking on the three dots on the right side of the toolbar, and choosing the Dock Side that puts the tools on the bottom of the browser. Click on the Console tab so you will see the console messages logged as you make requests.

We will be using the Search field on the main page of the Juice Shop for our attack.  The first thing we will do is try a simple attack to see what kind of information we can get in response.

Enter into the Search field: ‘;  and click the Search button.
You will get an error in the console. When you click on the carats on the left to expand the console entries, you will see that the SQL query that was run was: SELECT * FROM Products WHERE ((name LIKE ‘%’;%’ OR description LIKE ‘%‘;%’) AND deletedAt IS NULL) ORDER BY name

Now that you know what SQL query is being run when you submit a search, you can manipulate the query.
Enter into the Search field: ‘))–
Let’s take a moment to examine why we are entering these characters.
We are using to make the query think that the search value is completed.
We are using the first ) to close out the parentheses that started right after the word WHERE.
We are using the second ) to close out the second pair of parentheses that started right after the first.
We are using the to comment out all of the remaining text in the query.

When you submit this search value, this is the query that is run:
SELECT * FROM Products WHERE ((name LIKE ‘%‘))– OR description LIKE ‘%‘;%’) AND deletedAt IS NULL) ORDER BY name
Note that everything after the is ignored.

You have probably noticed that this query returned all the juices in the Juice Shop!  This is because the search is running only on the % character, and the % character acts as a wild card.

Now let’s try to do a UNION between this table of Products and another table.  We will make a guess that all of the usernames and passwords are in a table called Users.
Enter into the search field:  ‘)) UNION SELECT * FROM Users– 
Here we are closing out the query as we did before, but now before the we are adding in a UNION command that will join the Users table with the Products table.

When you submit this search, this is the query that is run:
SELECT * FROM Products WHERE ((name LIKE ‘%‘)) UNION SELECT * FROM Users– OR description LIKE ‘%‘;%’) AND deletedAt IS NULL) ORDER BY name

Notice that this did not give you a response in the browser.  Take a look in the Console to see what error you got:
SELECTs to the left and right of UNION do not have the same number of result columns
What this error is saying is that the number of columns in the Products table does not match the number of columns we are asking for in the Users table.  We need to get the number of columns to match before the UNION will be executed.

We can see that there are at least five columns in the Products table, so we will start our query with that.
Enter into the search field: ‘)) UNION SELECT ‘1’,’2′,’3′,’4′,’5′ FROM Users–
The numbers indicate the column numbers that we are requesting.

When you submit this search, this is the query that is run:
SELECT * FROM Products WHERE ((name LIKE ‘%‘)) UNION SELECT ‘1,’2,’3′,’4′,’5′ FROM Users– OR description LIKE ‘%‘;%’) AND deletedAt IS NULL) ORDER BY name

Note that you get the same error message as before, which means that we still don’t have the number of columns right.

At this point, you’ll need to keep guessing at the number of columns.  To make things easier, I’ll tell you that the correct number of columns is eight.

So, submit this search:  ‘)) UNION SELECT ‘1’,’2′,’3′,’4′,’5′,’6′,’7′,’8′ FROM Users–
This is the query that will run:  SELECT * FROM Products WHERE ((name LIKE ‘%‘)) UNION SELECT ‘1’,’2′,’3′,’4′,’5′,’6′,’7′,’8′FROM Users– OR description LIKE ‘%‘;%’) AND deletedAt IS NULL) ORDER BY name

And note that at the bottom of your search results, you have a new row of values!  We are getting closer to success with our attack.

The next step is to remove the product results so it will be easier for us to see the results we really want.  We can do this by searching for something that we know doesn’t exist, so we won’t get any results at all.

Submit this search: FOO’)) UNION SELECT ‘1’,’2′,’3′,’4′,’5′,’6′,’7′,’8′ FROM Users–
This is the query that will run: SELECT * FROM Products WHERE ((name LIKE ‘FOO’)) UNION SELECT ‘1’,’2′,’3′,’4′,’5′,’6′,’7′,’8′ FROM Users– OR description LIKE ‘%‘;%’) AND deletedAt IS NULL) ORDER BY name

We know that there aren’t any juices named FOO, so we won’t get any response.  But because we are doing a UNION, we will still get our row of column numbers as a result.

Now we can try to get some results into our table!  When we look at the columns in our UNION, we can see that the first column is an image field.  It’s unlikely that there are any images in the Users table, and our UNION needs to have each column match up by data type, so we won’t try to replace the first column with any of the Users columns.  We’ll start with column 2.  We can guess that the Users table probably has a column for an id, a column for a username, and a column for a password.  Let’s guess that the names of these columns are “Id”, “Username”, and “Password”.  Replace columns 2, 3, and 4 with Id, Username, and Password, like this:

Search on: FOO’)) UNION SELECT ‘1’,’id’,’username’,’password’,‘5’,’6′,’7′,’8′ FROM Users–

Unfortunately, this search doesn’t give us any results, and we don’t get any clues from the console either.  We can assume that one of our column names is wrong.  What would happen if we used ’email’ instead of ‘username’?

Let’s search on: FOO’)) UNION SELECT ‘1’,’id’,’email’,’password’,‘5’,’6′,’7′,’8′ FROM Users–

And now we get a complete list of ids, email addresses, and passwords for every user in the Users table!

I hope that this example has demonstrated the kind of thinking that is needed when searching for SQL Injection vulnerabilities.  Security testing requires more trial and error and more patience than traditional QA testing, but if it means finding and fixing vulnerabilities and protecting your company’s data, it is definitely worth it!

Introduction to SQL Injection

SQL Injection is another type of security attack that can do serious damage to your application.  It’s important to find SQL Injection vulnerabilities before a malicious user does.

In SQL Injection, a malicious user sends in a SQL query through a form field which interacts with the database in an unexpected way.  Here are four different things that a malicious user might do with SQL Injection:

  • drop a table
  • change the records of another user
  • return records that the user shouldn’t have access to
  • log in without appropriate credentials
To understand how a SQL Injection attack is crafted, let’s take a look at an example.  Let’s say we have a form in our application with a username field.  When the username field is populated with a name such as ‘testerguy’ and submitted to the server, the following SQL query is run:
SELECT * from users where username = ‘testerguy’
If this username exists in the database, results for the users table are returned to the application.  
A malicious user will try to trick the database by 
  1. making it think that the entry has terminated, by passing in testerguy’ 
  2. adding an additional clause, such as OR 1=1
  3. adding a terminating statement such as ; to make sure no other SQL statement will be run
In the above example, what the user would add to the username field would be:
testerguy’ OR 1=1;

And what the database will execute is:

SELECT * from users where username = ‘testerguy’ OR 1=1;

Take a moment to think about the 1=1 clause.  1=1 is always true, so the database interprets this as selecting everything in the table!  So this select statement is asking for all values for all the users in the table. 

Let’s see some SQL Injection in action, using the OWASP Juice Shop.  Click the login button in the top left corner of the page.  We are going to use SQL injection to log in without valid credentials.

We’ll make the assumption that when the login request happens, a request like this goes to the database:

SELECT * from users where username = ‘testerguy’ AND password = ‘mysecretpass’

If the request returns results, then it’s assumed that the user is valid, and the user is logged in.

What we will want to do is try to terminate the statement so that all usernames will be returned, and so that the password isn’t looked at at all. 

So we will send in:

  1. any username at all, such as foo
  2. a single quote to make it look like our entry has terminated
  3. the clause OR 1=1 to make the database return every username in the table
  4. a terminating string of to make the database ignore everything after our request
Taken together, the string we will add to the username field is:
foo’ OR 1=1–
You may notice that the submit button is not enabled yet.  This is because we haven’t added a password.  The UI expects both a username and a password in order to submit the login.  You can add any text at all into the password field, because we are ensuring that it will be ignored. Let’s add bar.
Now when you submit the login request, this is what will be executed on the database:
SELECT * from users where username = ‘foo’ OR 1=1–‘ AND password = ‘bar’

The first part of the request is returning all users, because 1=1 is always true.  And the second part of the request will be ignored, because in SQL everything after the dashes is commented out.  So when the code sees that all users have been returned, it logs us in!
If you hover over the person icon at the top left of the screen, you will see that you have actually been logged in as the admin!  The admin’s email address was the first address in the database, so this is the credential that was used.  Because you are logged in as the admin, you now have elevated privileges on the website that you would not have as a regular user.
Obviously, this sort of scenario is one that you would want to avoid in your application!  Next week we’ll take a look at some other SQL Injection patterns we can use to test for this vulnerability.  

Automated Testing For XSS

Last week, we talked about three different ways to test for Cross-Site scripting.  We looked at examples of manual XSS testing, and talked about how to use the code to formulate XSS attacks.  Today we will look at the third way to test, which is to use automation.  For today’s testing, we’ll be using Burp Suite, which is an oddly-named but very helpful tool, and is available for free (there is also a paid version with additional functionality).  We’ll also be using the Juice Shop and Postman

First, let’s take a look at the field we will be testing in the Juice Shop.  Using the Chrome browser, navigate to the Juice Shop’s home page.  You’ll see a search window at the top of the page.  Open up the Chrome Developer Tools, by clicking on the three-dot menu in the upper right corner, then choosing “More Tools”, then “Developer Tools”.  Once the dev tools are open, click on the Network tab. 

Do a search for “apple” in the search field.  You’ll get your search results on the web page, and you should see the network request in the developer tools.  The request name will be “search?q=apple”.  Click on this request.  A window will open up with the full request URL, which should be https://juice-shop.herokuapp.com/rest/product/search?q=apple. 

Next, open up Postman.  Paste this URL into the URL window, and click the Send button.  You should get a 200 response, and you should see your search results.  Now we’ll set Postman to use a proxy.  Click on the wrench icon in the top navigation bar and select “Settings”.  Click on the Proxy tab, then turn the Global Proxy on.  In the first section of the Proxy Server window, type, which is your local IP address.  In the second section, type 8080, which is your local port.  Postman is now set up to send requests to Burp Suite.  You may need to do one more step here, which is to turn SSL verification off.  In the Settings window, click on the General tab, and turn off the “SSL certificate verification” setting. 

Once you have downloaded Burp Suite, start the application and click Next.  Then click Start Burp.  Now Burp Suite is ready to receive requests.  Go back to Postman, and click Send on the search request that you sent earlier.  It will appear that nothing happens; this is because the request has just been sent to Burp!  Go to Burp, and you will see that the Proxy tab is now in an orange font.  Click on the Proxy tab, and then click the Forward button.  Your request is now being sent on to Postman, and if you return to Postman to check, you will see your request results.  It’s a good idea to turn off the Global Proxy in Postman now, because if you forget, the next time you make a Postman request you’ll wonder why you aren’t getting results! 

Return to Burp Suite, and click on the HTTP tab.  (This is a sub-tab of the Proxy tab.  If you don’t see the HTTP tab, make sure the Proxy tab is selected.)  You should see your GET request listed here.  Right-click on your request, and click “Send to Intruder”.  You should see that the Intruder tab is now in an orange font. 

Click on the Intruder tab.  You can see that the attack target has already been set to the Juice Shop URL.  Now click on the Positions sub-tab.  This is where we choose which element of the request we want to replace. Burp Suite has guessed correctly that the element we’d like to replace in our testing is the search field value of “apple”, so we can leave this setting as-is.  Now click on the Payloads sub-tab.  Here is where we will try out a bunch of cross-site scripting payloads! 

Enter in a bunch of XSS attacks into the Payload Options window, using the Add button.  Here are some suggestions:

<script>alert(“XSS here!”)</script>
<IMG SRC=javascript:alert(‘XSS’)>
<IMG SRC=JaVaScRiPt:alert(‘XSS’)>
<a onmouseover=”alert(document.cookie)”>xxs link</a>

You can find many more suggestions in this XSS Filter Bypass List.  It’s worth noting that if you sign up for the paid version of Burp Suite, a whole list of XSS attacks will be available to use, saving you from having to type them in manually. 

Let’s start our attack!  Click the “Start attack” button in the top right corner of the application.  You’ll get a warning that your requests may be throttled because you are using the free version.  Just click OK, and the attack will start.  You’ll see a popup window, and one by one your XSS attacks will be attempted.

Once the attacks are finished, we can look at the attack results. There are six requests in the popup window.  The first request, request 0, is simply a repeat of our original request.  Requests 1-5 are the requests that we added into the Payload Options window.  We can see that requests 1, 2, and 5 returned a code 200, while requests 3 and 4 returned a 500.  This means that requests 1, 2, and 5 were most likely successful!  You can try them yourself by pasting them into the search field of the Juice Shop page. 

A few notes: 

  • It’s also possible, and more common, to use Burp Suite to intercept web browser requests directly, rather than going through Postman.  I chose to use Postman here because it’s so easy to set up the proxy.  
  • If you have set up Burp Suite to intercept browser requests directly, you may be able to replay your XSS attack responses directly in the browser to see them in action.  
  • Another feature of the paid version of Burp Suite is the Scanner tool, which will scan for a number of vulnerabilities, including XSS.

I hope that this blog post and the two previous ones have helped you to have a greater understanding of what Cross-Site Scripting is, why it’s dangerous, and how to test for it.  Next week, we’ll take a look at SQL Injection!

Three Ways to Test for Cross-Site Scripting

Last week, we explained what Cross-Site Scripting (XSS) is and demonstrated a couple of examples.  But knowing what it is isn’t enough- we need to able to verify that our application is not vulnerable to XSS attacks!  Today we’ll discuss three different strategies to test for XSS.

Strategy One:  Manual Black-Box Testing

This is the strategy to use when you don’t have access to an application’s code, and when you want to manually try XSS.  To implement this strategy, you’ll need to think about the places where you could inject a script into an application:

  • an input field
  • a URL
  • the body of an HTTP request
  • a file upload area
You’ll also need to think about what attacks you will try.  You may want to use an existing list, such as this one:  https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet

This cheat sheet includes lots of different ways to get scripts past any validation filters, including:

  • on error and on mouseover alerts
  • URL encoding
  • using upper and lower case letters (to evade a filter that’s just looking for “javascript” in lower case letters)
  • putting a tab or space into a script so it won’t be detected
  • using a character to end an existing script, and then appending your own
If you are testing manually, a systematic approach is best.  Locate all of the places where you could inject a script, choose a list of attacks you’d like to try, and try each attack in each place.  While you are testing, you may also gain some insight of how you could change the attack by what kind of response you get from the attack.  For example, if your script tag is stripped out by validation, you could try to encode it.

Strategy Two:  Look at the Code

This is the strategy to use if you want to test manually, and you have access to your application’s code.  By looking at the code, you can determine the best way to craft an attack script.  This also works well for testing file uploads; for example, if your application’s code lists file types that are not allowed, you may find some types that have not explicitly been blacklisted, and you can see if you can upload a script using one of those types.
Let’s take a look at how you can use an application’s code to craft an attack.  We’ll be using the XSS Game again; we’ll be doing Challenge 3, and in order to access it you need to have solved Challenges 1 and 2; so take a look at last week’s post to see how to do that.  
As you look at the website in Challenge 3, you see that there are three different tabs, each of which displays a different image when clicked on.  Take a look at what happens in the URL each time you click on one of the tabs.  When you click on the Image 2 tab, “#2” is appended to the URL.  When you click on the Image 3 tab, “#3” is appended to the URL.  What happens when instead of clicking on a tab, you type “#2” into the URL?  Unsurprisingly, you are taken to the Image 2 tab.  What happens when you type “#5” into the URL?  There is no Image 5, but you can see that the page displays the words “Image 5”.  What happens when you type “#FOO”?  The page displays “Image NaN” (short for “Not a Number”).  You have probably figured out now that the end of the URL is the place that you are going to inject your malicious script.  
Now, let’s take a look at the code!  Click on the “toggle” link next to the words “Target Code”.  This will display the code used for the webpage.  Look at line 17; it shows how the URL for the image tag is created:
“<img src=’/static/level3/cloud” + num + “.jpg’ />”;
The “num” part of this image tag is a variable.  The value of the variable is taken from what we are sending in the URL.  If you send in a URL with “#3”, then the image tag will be
If you send in a URL with “#FOO”, then the image tag will be

Our task now is to see how we can inject a script using this “num” variable.  Recall that in last week’s post, we did some Cross-Site Scripting where we made it look like we were uploading an image, and we included an alert that would display when there was an error uploading the image. And we set things up so that there would always be an error, because we weren’t really uploading an image at all.  We are going to do the same thing here.  
Let’s craft our URL.  We will begin with
because this is how the URL always starts.
Next, we’ll add
because we want to make it look like we are following the pattern of choosing an image number.
Now we’ll add
because we want to trick the code into thinking that the image URL is completed. This means that the code will try to load an image called “cloud3” instead of “cloud3.jpg”, which will generate an error.
Now we can add our on-error script:
https://xss-game.appspot.com/level3/frame#3  onerror=’alert(“Hacked!”)’
When the alert is triggered, a popup window will appear with the “Hacked!” message.
Let’s try it!  Take the entire URL:
https://xss-game.appspot.com/level3/frame#3 ‘ onerror=’alert(“Hacked!”)’
Paste it into the URL window, and click the “Go” button.  
You should see the popup window appear, and you have solved the challenge!
Strategy Three: Use a Security Testing Tool
As you can see from the example above, crafting an XSS attack takes a little time.  You may have many places in your application that you need to test for XSS, and not much time to test them.  This is where automated tools come in!  With an automated tool, you can send hundreds of XSS attacks in less than a minute.  In next week’s blog post, we’ll take a look at how to use an oddly-named tool called Burp Suite to automate XSS attacks!

What is Cross-Site Scripting, and Why Should You Care?

In discussions about security testing, you have probably heard about Cross-Site Scripting (XSS), but you may not have a good definition of what it is.  Cross-Site Scripting is an attack in which a malicious user finds a way to execute a script on another user’s website.  Today we’ll learn about two different kinds of XSS attacks, do a hands-on demo of each, and discuss why they are harmful to the end user.

Reflected XSS:

Reflected XSS is an attack that is executed through the web server, but is not stored in the code or the database.  Because the attack is not stored, the owner of a site may have no idea that the attack is happening.

In order to demonstrate this attack, we’ll go to a great training site from Google called the XSS Game. This site has a series of challenges in which you try to execute XSS attacks.  The challenges become increasingly more difficult as they progress.  Let’s try the first challenge.

On this page, you see a simple search field and button.  To execute the attack, all you need to do is type
<script>alert(“XSS here!”)</script>
into the text field, and click the button.  You will see your message, “XSS here!”, pop up in a new window.

What is happening here is that you are sending a script to execute a popup alert to the server.  The client-side code does not have appropriate safeguards in place to prevent a script from executing, so the site executes the script.

You might be thinking “This is a fun trick, but how could a malicious user use this to hack me?  I’m typing into my own search window.”  One way this is used is through a phishing link.  Let’s say that you are the owner of a website.  A malicious user could create a link that goes to your site, but appends a script to the end of the URL, such as
(This is simply the attack we used earlier, with HTML encoding.) The malicious user could send this link in an email to an unsuspecting visitor to your site, making the email look like it came from you.  When the person clicks on the link, the script will navigate to your site, and then execute the popup script.  The malicious user will craft the script so that instead of containing the message “XSS here!”, it contains a message that encourages the visitor to interact with it, in order to obtain the user’s account number, or other sensitive information.

Stored XSS:

Stored XSS is an attack where the malicious script is actually stored in the database or code of a website, so it executes whenever a user navigates to the page or link. This could happen if the creator of the site did not put adequate input sanitization in the back-end database.

We’ll take a look at how to craft this attack by looking at the second challenge of the XSS Game.  (In order to see this challenge, you’ll need to have solved the first challenge, so follow the instructions above.)

In the second challenge, you are presented with a chat app.  To solve the challenge, you need to add some text to the application that will execute a script.  You can do this by typing in
<img src=’foobar’ onerror=’alert(“xss”)’>

As soon as you submit this entry, you should see a popup window with the “XSS alert!” message.  And not only that, if you navigate away from this page and return to it, you will see the popup window again.  The attack has been stored in your comment on the chat page, where it will cause a popup for any users who navigate to it.

Let’s parse through the script we entered to see what it’s doing:

<img src=’foobar’ onerror=’alert(“xss”)’>
The items in red indicate that we are passing in an image element.

<img src=’foobar’ onerror=’alert(“xss”)’>
The section in blue is telling the server what the source of the image should be.  And here’s the trick- there is no URL of foobar, so the image cannot load.

<img src=’foobar’ onerror=’alert(“xss”)’>
The section in green is telling the server that if there is an error, that a popup window should be generated with the “xss” text.  Because we have set things up so that there will always be an error, this popup will always execute.

One way that stored XSS might be used is to spoof a login window.  When a user navigates to a hacked site, they will be presented with a login window that has been crafted to look authentic.  When they enter their login credentials, their credentials will be sent to the malicious user, who can now use them to log in to the site, impersonating the victim.

Next week, we’ll discuss more ways to test for XSS attacks!

Testing for IDOR Vulnerabilities

In this week’s post, we will learn how to test for IDOR.  IDOR stands for Insecure Direct Object Reference, and it refers to a situation when a user can successfully request access to a webpage, a data object, or a file that they should not have access to.  We’ll discuss four different ways this vulnerability might appear, and then we’ll actually exploit this vulnerability in a test application using Chrome’s Developer Tools and Postman.

One easy way to look for IDOR is in a URL parameter.  Let’s say you are an online banking customer for a really insecure bank.  When you want to go to your account page, you login and you are taken to this URL:  http://mybank/customer/27.  Looking at this URL, you can tell that you are customer number 27.  What would happen if you changed the URL to http://mybank/customer/28?  If you are able to see customer 28’s data, then you have definitely found an instance of IDOR!

Another easy place to look is in a query parameter.  Imagine that your name is John Smith, and you work for a company that conducts annual reviews for each of its employees.  You can access your review by going to http://mycompany/reviews?employee=jsmith.  You are very curious about whether your coworker, Amy Jones, has received a better review than you.  You change the URL to http://mycompany/reviews?employee=ajones, and voila!  You now have access to Amy’s review.

A third way to look for IDOR is by trying to get to a page that your user should not have access to.  If your website has an admin page with a URL of http://mywebsite/admin, which is normally accessed by a menu item that is only visible when the user has admin privileges, see what happens if you log in as a non-admin user and then manually change the URL to point to the admin page.  If you can get to the admin page, you have found another instance of IDOR.

Finally, it’s also possible to exploit an IDOR vulnerability to get files that a user shouldn’t have access to.  Let’s say your site had a file called userlist.txt with the names and addresses of all your users.  If you can log in as a non-admin user and navigate to http://mywebsite/files?file=userlist.txt, then your files are not secure.

Let’s take a look at IDOR in action, using Postman, Chrome Developer Tools, and an awesome website called the OWASP Juice Shop!  The OWASP Juice Shop is an application created by Bjorn Kimminich to demonstrate the most prevalent security vulnerabilities.  You can download it and run it locally by going to https://github.com/bkimminich/juice-shop, or you can access an instance of it by going to https://juice-shop.herokuapp.com.  For this tutorial, we’ll use the heroku link.  Once you navigated to the site on Chrome, create a login for yourself.  Click the Login button in the top left, and then click the “Not yet a customer?” link. You can use any email address and password to register (don’t use any real ones!).  Log in as your new user, and click on any of the juices on the Search page in order to add it to your shopping basket. 

Before you take a look at your basket, open up the Chrome Developer Tools by clicking on the three dots in the top right corner of the browser, selecting “More Tools”, and then “Developer Tools”.  A new window will open up on either the right or the bottom of your browser.  In the navigation bar of the tools, you should see a “Network” option.  Click on this.  This network tool will display all of the network requests you are making in your browser. 

Click on the “Your Basket” link in the top right of the Juice Shop page.  You will be taken to your shopping cart and you should see the juice that you added to the basket.  Take a look in the Network section of the Developer Tools.  The request that you are looking for is one that is named simply with a number, such as “6” or “7”.  Click on this request, and you should see that the request URL is https://juice-shop.herokuapp.com/rest/basket/<whateverYourAccountIdIs>, and that the request type is a GET.  Scrolling down a bit, you’ll see that in the Request Headers, the Authorization is set to Bearer and then there is a long string of letters and numbers.  This is the auth token.  Copy everything in the token, including the word “Bearer”. 

Next, we’ll go to Postman.  Click on the plus tab to create a new request.  The request should already be set to GET by default.  Enter https://juice-shop.herokuapp.com/rest/basket/<yourAccountId> into the URL.  Now go to the Headers section, and underneath the Key section, type “Authorization”, and underneath the Value section, paste the string that you copied.  Click to Send the request, and if things are set up correctly, you will be able to see the contents of your shopping basket in the response. 

Now for the fun part!  Change the account id in the URL to a different number, such as something between 1 and 5, and click Send.  You will see the contents of someone else’s basket!  Congratulations!  You have just exploited an IDOR vulnerability! 

Introduction to Security Testing

Until a few years ago, security testing was seen as something separate from QA; something that an InfoSec team would take care of.  But massive data breaches have demonstrated that security is everyone’s responsibility, from CEOs to product owners, from DBAs to developers, and yes, to software testers.  Testers already verify that software is working as it should so that users will have a good user experience; it is now time for them to help verify that software is secure, so that users’ data will be protected.

The great news is that much of what you already do as a software tester helps with security testing!  In this post, I will outline the ways that testers can use the skills they already have to start testing with security in mind, and I will discuss the new skills that testers can learn to help secure their applications.

Things you are probably already testing:

  • Field Validation: It’s important to make sure that fields only accept the data types they are expecting, and that the number and type of characters is enforced. This helps ensure that SQL injection and cross-site scripting can’t be entered through a data field.
  • Authentication: Everyone knows that it’s important to test the login page of an application. You are probably already testing to make sure that when login fails, the UI doesn’t provide any hints as to whether the username or password failed, and testing to make sure that the password isn’t saved after logout or displayed in clear text.  This serves to make it more difficult for a malicious user to figure out how to log in.
  • Authorization: You are already paying attention to which user roles have access to which pages.  By verifying that only authorized users can view specific pages, you are helping to insure that data does not fall into the wrong hands.

    Things you can learn for more comprehensive security testing:

    • Intercepting and Manipulating Requests: It is easy to intercept web requests with free tools that are available to everyone online.  If attackers are doing this (and they are), then it is important for you to insure that they can’t get access to information that they shouldn’t have.
    • Cross-site Scripting (XSS): This involves entering scripted code that will be executed when someone navigates to a page or retrieves data.  Any text field on a page, even any URL, represents a potential attack point for a malicious user to insert a script.
    • SQL Injection: This is exploiting potential security holes in communication with the database in order to retrieve more information than the application intended.  As with cross-site scripting, any text field or URL has the potential to be used to extract data.
    • Session Hijacking: It’s important to learn if usernames, passwords, tokens, or other sensitive information is displayed in clear text or poorly encrypted. Malicious users can take this information and use it to log in as someone else.  

      Security testing involves a shift in mindset from traditional testing.  When we test software, we are usually thinking like an end user.  For security testing, we need to think like a malicious user.  End users take the Happy Path, because they are using the software for its intended purpose, whereas hackers are trying to find any possible security holes and exploit them.  Because of this, security testing requires a bit more patience than traditional testing.  In the next few posts, I’ll be discussing the new skills we can learn, and the ways that we can Think Like a (Security) Tester!

      Understanding JSON Data

      New API testers will often be mystified by the assortment of curly braces, colons, and commas that they see in the body of the response to their GET requests.  Trying to create a valid JSON body for a POST request is even more puzzling.  In this week’s post, I’ll discuss how JSON data is formed and offer up some resources that will make working with JSON easier.

      JSON stands for JavaScript Object Notation.  It’s simply a way to organize data so that it can easily be parsed by the code.  The fundamental building block in JSON is the name-value pair.  Here are some examples of name-value pairs:
      "Name": "Dino"
      "Color": "Purple"
      A group of name-value pairs is separated by commas, like this:
      "FirstName": "Fred",
      "LastName": "Flintstone",
      "City": "Bedrock"
      Note that the final name:value pair does not have a comma.  This is because it’s at the end of the group.
      An object is simply a grouping of one or more name-value pairs.  The object is represented with curly braces surrounding the name-value pairs.  For example, we might represent a pet object like this:
      "Name": "Dino",
      "Type": "Dinosaur",
      "Age": "5",
      "Color": "Purple"
      An array is a group of objects.  The array is represented with square braces, and the objects inside the array have curly braces.  For example:

      "residents": [
      "FirstName": "Fred",
      "LastName": "Flintstone"
      "FirstName": "Barney",
      "LastName": "Rubble"
      "FirstName": "Wilma",
      "LastName": "Flintstone"

      Notice that Fred Flintstone’s last name does not have a comma after it.  This is because the LastName is the last name-value pair in the object.  But, notice that the object that contains Fred Flinstone does have a comma after it, because there are more objects in the array.  Finally, notice that the object that contains Wilma Flintstone does not have a comma after it, because it is the last object in the array.

      Not only can an array contain objects, but an object can contain an array.  When you are sending in JSON in the body of an API request, it will always be in the form of an object, which means that it will always begin and end with a curly brace.  Also, name-value pairs, objects, and arrays can be very deeply nested.  It would not be unusual to see something like this contained in a POST for city data:

      "residents": [
      "firstName": "Fred",
      "lastName": "Flintstone",
      "contactInfo": {
      "phoneNumber": "555-867-5309",
      "email": "[email protected]"
      "firstName": "Wilma",
      "lastName": "Flintstone",
      "contactInfo": {
      "phoneNumber": "555-423-4545",
      "email": "[email protected]"
      "pets": [
      "name": "Dino",
      "type": "dinosaur",
      "color": "purple"
      "name": "Hoppy",
      "type": "hopparoo",
      "color": "green"

      Notice that the contactInfo is deeply nested in the city object.  If we were testing this API and you wanted to assert that Fred Flintstone’s phone number was correct, we would access it like this:


      The first array in the city object is the residents array, and Fred is the first resident in the array, so we access him with residents[0].  Next, we move to the contactInfo, and since the contactInfo is an object rather than array, we don’t need to specify a number in braces.  Finally, we specify the phoneNumber as the name-value pair within the contactInfo object that we are looking for.

      Understanding this nested structure is also important when passing in query parameters in a URL.  For example, if we were to do a GET request on the city object, and we only wanted to have the residents of the city returned, we could use a URL like this:


      If we wanted to narrow the results further, and only see the first names and email addresses of our residents, we could use a URL like this:

      http://myapp/city/Bedrock?fields=residents(firstName), residents(contactInfo(email))

      First we are asking for just the residents, and we specify only the firstName within the residents array.  Then we ask for the residents, and we specify only the contactInfo within the residents and only the email within the contactInfo.

      Even with the explanations above, you may find working with JSON objects frustrating.  Here are two great, free, tools that can help:

      JSONLint– paste any JSON you have into this page, and it will tell you whether or not it is valid JSON.  If it is invalid JSON, it will let you know at what line it becomes invalid.

      JSON Pretty Print– it’s sometimes hard to format JSON so that the indents are all correct.  Also, having correct indents will make it easier to interpret the JSON.  Whenever you have a JSON object that is not indented correctly, you can paste it into this page and it will format it for you.

      Over the last several weeks, we’ve covered everything you need to know to be successful with API testing.  If you have any unanswered questions, please mention them in the comments section of this post.  Next week, we’ll begin a discussion of application security!

      What API Tests to Automate, and When to Automate Them

      Last week, we talked about running API tests from the command line using Newman, and how to add Newman into your Continuous Integration system so that your API tests run automatically.  But knowing how to run your tests isn’t that helpful unless you make good choices about what tests to run, and when to run them.  Let’s first think about what to automate.

      Let’s imagine that we have an API with these requests:

      POST user
      GET user/{userId}
      PUT user/{userId}
      DELETE user/{userId}

      The first category of tests we will want to have are the simple Happy Path requests.  For example:

      POST a new user and verify that we get a 200 response
      GET the user and verify that we get a 200 response, and that the correct user is returned
      PUT an update to the user and verify that we get a 200 response
      DELETE the user, and verify that we get a 200 response

      The next category of tests we want to have are some simple negative requests.  For example:

      POST a new user with a missing required field and verify that we get a 400 response
      GET a user with an id that doesn’t exist and verify that we get a 404 response
      PUT an update to the user with an invalid field and verify that we get a 400 response
      DELETE a user with an id that doesn’t exist and verify that we get a 404 response

      You’ll want to test 400 and 404 responses on every request that has them.  It’s not necessary to test every single trigger of a 400- for example, you don’t need to have automated tests for every single missing required field- but you will want to have one test where one required field is missing.

      The third category of tests we want to have are more Happy Path requests, with variations. For example:

      POST a new user with only the required fields, rather than all fields, and verify that we get a 200 response
      GET a user with query parameters, such as user/{userId}?fields=firstName,lastName, and verify that we get a 200 response, and the appropriate values in the response
      PUT a user where one non-required field is replaced with null, and one field that is currently null is replaced with a value, and verify that we get a 200 response

      It’s worth noting that we might not want to test every possible combination in this category.  For example, if our GET request allows us to filter by five different values: firstName, lastName, username, email, and city, there are dozens of possibilities of what you could filter on.  We don’t want to automate every single combination; just enough to show that each filter is working correctly, and that some combinations are working as well.

      Finally, we have the category of security tests.  For example, if each request needs an authorization token to run, verify that we get an appropriate error:

      POST a new user without an authorization token, and verify that we get a 401 response
      GET a user with an invalid token, and verify that we get a 403 response
      PUT an update to the user with an invalid token, and verify that we get a 403 response
      DELETE a user without an authorization token, and verify that we get a 401 response

      For each request, you’ll want to test for both a 401 response (an unauthenticated user’s request) and a 403 response (an authorized user’s request).

      There may be many more tests than the ones that have been listed here that are appropriate for an API you are testing.  But these examples serve to get you thinking about the four different types of tests.

      Now let’s take a look at how we might use these four types in automation!  First, we want to have some Smoke Tests that will run very quickly when code is deployed from one environment to another, up the chain to the Production environment.  What we want to do with these tests is simply verify that our endpoints can be reached.  So all we need to do is run the first category of tests: the simple Happy Path requests.  In our example API, we only have four request types, so we only need to run four tests.  This will only take a matter of seconds.

      We’d also like to have some tests that run whenever new code is checked in.  We want to make sure that the new code doesn’t break any existing functionality.  For this scenario, I recommend doing the first two categories of tests: the simple Happy Path requests, and the simple negative requests.  We could have one positive and one or two negative tests for each request, and this will probably be enough to provide accurate feedback to developers when they are checking in their code.  In our example API, this amounts to no more than twelve tests, so our developers will be able to get feedback in about one minute.

      Finally, it’s also great to have a full regression suite that runs nightly.  This suite can take a little longer, because no one is waiting for it to run.  I like to include tests of all four types in the suite, or sometimes I create two nightly regression suites: one that has the first three types of tests, and one that has just the security tests.  Even if you have a hundred tests, you can probably run your full regression suite in just a few minutes, because API tests run so quickly.

      Once you have your Smoke, Build, and Regression tests created and set to run automatically, you can relax in the knowledge that if something goes wrong with your API, you’ll know it.  This will free you up to do more exploratory testing!

      Next week we’ll take look at API request formats: JSON structure and query parameters!