Direct object references occur when an application enables a user to provide an actual database key, file name, URL, etc as input and obtains access to data as a result. Our example on OWASP A10 is an example of providing a direct object reference. In our post, the final solution enables the user to supply a database key (0, 1, 2, 3, etc), which will then return the appropriate URL for the redirection. The OWASP A10 solution is a secure direct object reference because it validates that the user has legitimate access to the resource. However, there is certainly a security benefit to obfuscating references by creating indirect object maps.
Indirect object maps associate a direct reference to a separate value. The user only sees the separate value which prevents an attacker from enumerating valid keys, files, etc. In addition, this can be used to protect access to resources in the event that your application’s access controls are bypassed in some way. The applicable ASVS reference is: “Verify that direct object references are protected, such that only authorized objects are accessible to each user.” We are taking this a step further by creating an indirect object mapping.
The steps for implementing this solution involve:
- Creating a PHP class that initializes the list of keys/files/whatever to be indirectly associated.
- Mapping the keys to a hash value. The reference will be a per-session value based on a combination of the session ID and the key value.
- Providing a method for accessing these mappings and another method for setting additional indirect references.
The code:
class indirObjectMap { private $objectMap; public function __construct($session, $username) { // Omitting DB connection code here. SELECT the ID values // from the URL table. These values will be associated // with an indirect value. Validate that the user has // access by mapping the ACL on the URL to the GroupID // associated with the user. Use a prepared statement, // which will be explained in our series on OWASP A1. $urlValues = $db->prepare("SELECT ID FROM urlmap INNER JOIN (Users) ON urlmap.ACL = Users.GroupID WHERE Users.Username = ?"); $urlValues->bindValue(1, $username, PDO::PARAM_STR); $urlValues->execute(); $urlList = $urlValue->fetchAll(PDO::FETCH_ASSOC); // Loop through the ID values, and associate them with a // hash stored in the objectMap variable. The hash is // generated with a combination of the session ID and the // key value. foreach($urlList as $row) { $this->objectMap[hash("sha256", $session . $row['ID'])] = $row['ID']; } } public function getMap($objectRef) { return $this->objectMap[$objectRef]; } public function setMap($session, $objectValue) { $this->objectMap[hash("sha256", $session . $objectValue)] = $objectValue; } }
The values and mappings could be initialized with a call like this:
// Store object in a session variable so that it can be // accessed across multiple pages: $_SESSION['refmap'] = new indirObjectMap(session_id(), $_SESSION['username']);
The values can be retrieved or set with the following (using our dataValidator function created in the XSS posts):
// Get a value using the indirect reference. In this // example, the reference is an alphanumeric hash ('an'), // can be any size, and was passed as a POST parameter: $mapping = $_SESSION['refmap']->getMap(dataValidator('an', $_POST['mapValue'], 0)); // Set a value from a provided key. In this example, the // key is a number ('nu') and shouldn't be greater than // five characters: $_SESSION['refmap']->setMap(session_id(), dataValidator('nu', $key, 5));
Note that the above code WILL NOT work if you change session ID’s on each page load with session_regenerate_id(). If you utilize this function, you could instead perform a per user reference map by passing in the username instead of the session ID. Alternatively, you could store the initial session value in the session state ($_SESSION[‘initial’] = session_id();) and then pass this value to the object instead of the current session ID.
And just like that, we have completed over half of the OWASP Top 10 requirements using PHP and Apache. Until next time…
Leave a Reply