APPLICATION SECURITY
Knowledge Base
Search Our Knowledge Base
CWE 639: Insecure Direct Object Reference
Flaw
CWE 639: Insecure Direct Object Reference is an access control problem that allows an attacker to view data by manipulating an identifier (for example, a document or account number).
Direct object references are maps of an identifier directly to a resource; they are insecure direct object references when they allow an unauthorized user to access data. For example, a method that retrieves a record from a database to later display to a user:
@RequestMapping(value = "/records/{id}"),
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@Timed
public ResponseEntity<Record> get(@PathVariable Long id) {
log.debug("REST request to get record" {}", id);
...
So a user might visit:
http://example.org/#/records/921106108
And this will display record 921106108
; the get
method takes the id
of a record to fetch from a URL parameter.
This example code is insecure, because it does not check to see if the authenticated user is authorized to see that record. An attacker can simply change the parameter in the URL to see any record they wish. This sort of attack is easy to automate, which can lead to massive data breaches.
Attackers can use similar flaws to learn about your sytems. For example, attackers have used this flaw enumerate users when changing a userID parameter results in either a "user not found" page or that user's profile page.
NB: insecure direct object references are not limited to URLs, though that's where they're most commonly found. Any client-controlled value -- cookies, form values, header values, etc. -- can potentially be the source of an insecure direct object reference flaw.
Fix
To fix an Insecure Direct Object Reference, you have two options. The first is to add an authorization check before displaying any information that might be useful to an attacker. For example:
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@Timed
+@PreAuthorize("hasRole('ADMIN') OR hasRole('RecordOwner')")
public ResponseEntity<Record> get(@PathVariable Long id) {
log.debug("REST request to get record" {}", id);
view fixed code only This approach is preferred, since as long as your authorization system is effective, an unauthorized user can't access data through this path, which makes things very difficult for attackers.
NB: There are many different authorization mechanisms, so beware of just using this example verbatim. Make sure you understand the authentication and authorization capabilities your application is using, and follow those patterns.
Unfortunately, there are times when information needs to be available to anonymous users, but you still don't want attackers to easily enumerate the data. In this case, you can create a level of indirection by creating a map connecting unpredictable values to the real, predictable IDs. A good way to do this is to use java.util.UUID.randomUUID()
to generate Universally Unique IDs that can be mapped to your more-predictable keys. Your code might then look something like:
-@RequestMapping(value = "/records/{id}"),
+import java.util.UUID;
+...
+
+@RequestMapping(value = "/records/{safe_id}"),
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@Timed
+@PreAuthorize("hasRole('ADMIN') OR hasRole('RecordOwner')")
-public ResponseEntity<Record> get(@PathVariable Long id) {
+public ResponseEntity<Record> get(@PathVariable UUID safe_id) {
+ id = getRealIDforUUID(safe_id);
log.debug("REST request to get record" {}", id);
...
view fixed code only With this in place, your URLs would look something like:
http://example.org/#/records/946933e0-fab5-419b-8910-cc3d0367d95b
and the getRealIDforUUID
method accepts 946933e0-fab5-419b-8910-cc3d0367d95b
as its parameter, and retrieves the real id (921106108
) from a key-value store, database, etc.. Because there are so many UUID possibilities, and random UUIDs aren't predictable, an attacker would have a nearly impossible task to attempt to enumerate these records.
References
CWE ↪