Email Subscription Form

Saturday, May 26, 2018

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! 






Saturday, May 19, 2018

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!


Saturday, May 12, 2018

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": "fflintstone@slaterock.com"
      }
    },
    {
      "firstName": "Wilma",
      "lastName": "Flintstone",
      "contactInfo": {
        "phoneNumber": "555-423-4545",
        "email": "wflinstone@dailygranite.com"
      }
    }
  ],
  "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:

residents[0].contactInfo.phoneNumber

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:

http://myapp/city/Bedrock?fields=residents

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!







Saturday, May 5, 2018

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!





Six Steps to Writing an Effective Test Report

As testers, we know how important it is to test our software thoroughly and document our findings meticulously.  But all of our talent will ...