#SecurityCulture: Authorization Testing
Authorization is the idea that a user can only do what they should be able to based on their role. It is synonymous with access control.
Consider the case of a consulting firm with:
- Consultants that record time and submit timesheets (Let's say Joe and Brian are consultants)
- Managers who approve timesheets (Let's say Matt is a manager)
There are several types of authorization that need to be implemented in a typical time tracking system.
We need vertical access control implemented to prevent a consultant from approving their own timesheet.
We need horizontal access control or instance based access control to prevent Joe from seeing, modifying or submitting Brian's timesheet.
Unfortunately, in all my years as a developer, I often observed that we needed to apply security to search functions and admin functions but not necessarily update, delete and view functions on an instance - because we thought it would somehow be very difficult to create a fake request. I believe this issue is common in real world applications. We certainly see it in many pen tests.
implementing access control
Implementing vertical access control often comes down to restricting public routes, and applying checks at any controller (or service) method that is exposed to ensure only users in the correct role are able to call that endpoint.
Horizontal access control can be trickier because we need to check that the logged in user is accessing their own data or data that they are supposed to be associated to. This could be done in a query, or in a central service.
Either of these could use a central authorization service to perform the actual checks.
testing authorization code
In a penetration test, the ways we might test this are illustrated in the following examples.
Vertical
- Log in as Matt
- Approve a timesheet (capture that request)
- Log in as Joe
- Replay the request to approve a timesheet but with Joe's session
Horizontal
- Log in as Joe
- Access my timesheet (capture that request)
- Log in as Brian
- Replay the request for Joe's timesheet but with Brian's session
It should be fairly obvious that we can write integration tests that do this exact same thing! It turns out that writing tests for this can be a really effective way to identify gaps in authorization.
Simply fire up Selenium, Cypress or any BDD WebDriver and start requesting different data. The example below shows one way this can be done in more detail.
an example
There is an example from SWTF, a proof of concept we wrote a long time ago!
Here is the feature:
Feature: person is restricted from accessing project they do not own
In order to trust the system
as a user
each person should only be able to access their projects
Scenario: person creates a project
Given a new project created by a user
When a different person attempts to access the project
Then the system should prevent access
Here is the code behind the DSL:
Given(/^a new project created by a user$/) do
uuid = SecureRandom.uuid
@user1 = "fb_user_1_#{uuid}@jemurai.com"
register_as_user(@user1, "password")
new_project("Insecure Deirect Object Reference #{uuid}", "Forceful Browsing Desc")
@url = current_url
end
When(/^a different person attempts to access the project$/) do
logout(@user1)
uuid = SecureRandom.uuid
@user2 = "fb_user_2_#{uuid}@jemurai.com"
register_as_user(@user2, "password")
end
Then(/^the system should prevent access$/) do
visit @url
expect(page).not_to have_content "Forceful Browsing Desc"
end
Obviously, some of the methods in this example are common and captured in a base library: register_as_user, new_project and logout. Once we have some of these base parts started, it becomes easy to extend to write more useful and comprehensive tests.
conclusion
Go write tests for authorization! It is an easy and effective way to ensure that you're not leaving gaps open.
references
- OWASP Top 10: A5
- OWASP Authorization Cheat Sheet
- SWTF - Examples in Cucumber and RSpec (old)
- Cypress
- Selenium