This seems like a no-brain-er but I have seen it more times than I can count and I have seen it happen to some very experienced developers. Put simply: authentication is not enough; you need to make sure that the authenticated user is actually authorized to perform an action. It is one thing to know who a user is and an entirely different — though equally important — thing to know what a user is allowed to do.
This article covers the concepts of authentication and authorization.
Authentication is knowing who the logged in user is. Authorization is knowing what the user can and can’t do and can be as easy as checking that the user ID in the database matches the user ID of the authenticated user. It can also be as complex as Access Control Lists, social graphs, and multi-moderator systems. Either way it needs to be taken care of. Very few things are more devastating than when a malicious user finds that all you have to do to edit another user’s data is to change the value of a POST variable or all you need to do to access someone’s private photos is to change a URL. Do either of those two cases sound familiar?
Thinking like a hacker
If I were to try to exploit this feature: how would I go about it? - you, every time you write code (hopefully)
Let’s start by looking at a imaginary site that makes an Ajax request to delete a resource via a RESTful API. The HTTP request probably looks a bit like this:
POST /resources/1234.json HTTP/1.1 Host: www.example.com Content-Length: 14 _method=DELETE
Now, make the assumption that you can find the ID of any resource on the system. This could be because it is exposed in a “view” URL or it could be that it is returned in an API call for search. Either way, assume that if it exists a hacker can find it either by design or through flaws in the code. Never assume primary keys are a secret. The next step is to find an ID of a resource not owned by the hacker (in this example: 5678) and to go the command line (assuming cURL is installed on your system):
curl -d "_method=delete" http://www.example.com/resources/5678.json
Curl doesn’t share cookies with the web browser so if that call succeeded and record 5678 was actually deleted then the application is not checking to see if the user is authenticated. There is no need to go any further, you’ve already found a devastating exploit. If this is your application (I hope that you aren’t using this article to try to hack other people’s apps!) it is time to go back to the code, add a check to make sure that the user is logged in. Then come back here to read on.
If the call didn’t work then it is time to try to delete the resource as an authenticated (but hypothetically not authorized) user:
curl -d "username=you&password=abcdefg" -c "cookies.txt" http://www.example.com/login/ curl -d "_method=delete" -b "cookies.txt" http://www.example.com/resources/5678.json
The first line authenticates the user and stores the cookies. The second line tries the delete method again with the new cookies. Make sure to replace all the appropriate variables and URLs in all of these examples. If all went well bad the resource 5678 should now be deleted. If that happened then the application needs to check for authorization as well as authentication.
One thing to watch out for is any request that takes the user ID as a parameter. It should raise a red flag. Whenever possible, get the user ID from the currently authenticated user. I once saw a password vault web application that returned the entire password list from a SOAP call given only the user ID. Just so you are sufficiently mortified, I’ll rephrase it: I could enter in any user’s ID and get back a list of passwords for other sites on the Internet (including Google). Don’t let that happen to you!
Taking care of business
Addressing the problem takes as much thought and planning then actually technical know-how. Imagine a user for each role (resource owner, administrator, moderator, customer support, friend of the user — if you are a social network — etc.) then ask three questions:
- User X can/can’t view resource Y because…
- User X can/can’t edit resource Y because…
- User X can/can’t delete resource Y because…
Then for each of those, check to make sure the code reinforces that statement. It is also a good idea to give these stories to the testers and have them try to break your code. And remember: the authentication system tells you WHO the user is and authorization system tells you WHAT actions the user can perform.
How complex a system you need for authorization depends on your application. It can range from one-off code to full-featured generic systems that can be used for any type of resource imaginable. Social networks are the most complicated of the bunch because authorization often depends on a personal relationship with the user requesting access to the resource. The simplest form of authentication is:
if ( $user->id != $resource->owner_id ) throw new Exception("Access denied");
Or if your application is a social network and you give friend’s access to resources:
if ( $user->id != $resource->owner_id && !$user->isFriendsWith($resource->owner_id) ) throw new Exception("Access denied");
For a more robust system you’ll probably want to implement an Access Control List (ACL). An ACL at its core is just a mapping of users to resources. For example: Joe has view, edit, and delete access to resource 1234.
More advanced access control lists also have groups (called “roles”) and they can cascade. Roles introduce some ambiguity, and multiple entries in the list may govern the same action. If that happens, the most specific one is taken. For example, editing a resource may be governed by the rules:
- "Joe" is in the group "Basic Users" and "Basic Users" explicitly can NOT edit resources of type "forum post"
- "Joe" CAN edit resources of type "forum post" with ID "1234"
Since Joe has edit rights to the forum post with an ID of 1234 it doesn’t matter that the role Joe plays (a “Basic User”) cannot edit any forum posts. There are numerous articles on implementing an ACL in a PHP application and many frameworks have built-in classes for ACL.
When developing web applications (or any application for that matter): always be cognizant of authentication and authorization. Remember, authentication answers the question of WHO and authorization answers the question of WHAT. The application must always know the answer to both of those questions and be able to deny or allow certain actions based on those answers. It might be useful for newer developers to to actually put themselves in the shoes of a hacker and attempt to find exploits for their own website. Eventually, it will become second nature.