During an interview a few years back I was asked, "If you had the power to remove any application-related vulnerability from existence, what would it be?" My response was pretty generic, going after the heavy hitter - SQL injection. To be fair, this is a genuine response... Remove the 'most threatening' vuln of the bunch and you've effectively taken Frazier's left hook out of his game... That's a huge win.
However, it's been a few years and I'm changing my stance. In today's online arena the game has changed. Frameworks have gotten better at sanitizing user inputs, web application firewalls have improved by detecting/dropping malicious requests, throttling brute force attempts, etc. The vuln that I seem to find all the time now is so simple yet terrifying when discovered under the right circumstance - Insecure Direct Object References.
What Are Insecure Direct Object References?
A direct object reference occurs when a developer exposes a reference to an internal implementation object, such as a file, directory, database record, or key, as a URL or form parameter. An attacker can manipulate direct object references to access other objects without authorization, unless an access control check is in place.
For example, in Internet Banking applications, it is common to use the account number as the primary key. Therefore, it is tempting to use the account number directly in the web interface. Even if the developers have used parameterized SQL queries to prevent SQL injection, if there is no extra check that the user is the account holder and authorized to see the account, an attacker tampering with the account number parameter can see or change all accounts. - OWASP
While this might sound quite trivial, the simple fact is this vulnerability exists within applications across all sectors, including but not limited to:
- Banking and Financial
The Exploitation of Insecure Direct Object References
Now that we've described the issue, let's look into how it's exploited and consider the ramifications of this simple vuln.
The following scenarios are based on real world experience. To this point in my career there have been so many uniquely discovered instances that it would quite literally be impossible to create equivalents for demonstration purposes. However, as with all things... We aren't attempting to train gorillas to conduct penetration tests; we're trying to aid developers in creating more secure applications.
[Note: More on this in the section - Why Is This Even A Thing?]
Scenario - Compromising Bank Account Balances
Let's consider the following POST request issued while viewing our bank account's balance.
POST /MyProfile/Account/CheckBalance HTTP/1.1 Host: abcbank.com User-Agent: Mozilla/5.0 Referer: https://abcbank.com/MyProfile/Account/AccountStatus Cookie: JSESSIONID=230SDF321752AS29834HAHD333211D235 Connection: close Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded Content-Length: 45 accountNo=55364&subAccount=0
Notice accountNo and subAccount.
accountNo is our actual provisioned account number by ABCBank.
subAccount is used to dictate which account within our member profile we are referring to.
- 0 = Checking
- 1 = Savings
- 2 = Retirement
- 3+ = Additional accounts
Words can't describe how often I find the following issue.
What Happens If We Mess With Numbers?
In a manner similar to a child throwing blocks across the room we approach the issue. Using BurpIntruder/Python Scripting/Pick your poison we can quickly iterate through thousands of accounts using simple integer-based permutations.
POST /MyProfile/Account/CheckBalance HTTP/1.1 Host: abcbank.com User-Agent: Mozilla/5.0 Referer: https://abcbank.com/MyProfile/Account/AccountStatus Cookie: JSESSIONID=230SDF321752AS29834HAHD333211D235 Connection: close Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded Content-Length: 45 accountNo=55100&subAccount=0
HTTP/1.1 200 <html><h2>Current Account Balance:</h2> Account Holder Name: Chuck Schumer Account ID: 55100 Current Balance: $17,500
Let's say we wanted to quickly enumerate the total value of all accounts of five thousand users...
POST /MyProfile/Account/CheckBalance HTTP/1.1 Host: abcbank.com User-Agent: Mozilla/5.0 Referer: https://abcbank.com/MyProfile/Account/AccountStatus Cookie: JSESSIONID=230SDF321752AS29834HAHD333211D235 Connection: close Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded Content-Length: 45 accountNo=[55000-6000]&subAccount=[0-4]
The terrifying part about this vulnerability is though it seems so simple, it's often overlooked. Using this sort of scripting, it can be possible to enumerate everything from home addresses to contact information and even SSN's.
Developers will sometimes 'protect' this data from tampering as demonstrated below (Note: MyHealth just an arbitrary domain):
GET /AccountInfo/Results/Tests/Results?ResultsID=UmVwb3J0SUQ9Mjg1NzQ2MzEyMw%3D%3DHTTP/1.1 Host: myhealth.com User-Agent: Mozilla/5.0 Cookie: JSESSIONID=230SDF321752AS29834HAHD333211D235 Connection: close Upgrade-Insecure-Requests: 1
Two things here:
1) The ResultsID parameter value isn't really protected, it's encoded.
2) The use of POST requests is encouraged to prevent disclosure within browser history, web server logs, etc.
Percent Encoding (aka URL Encoding) translates '=' into %3D. Knowing this we can quickly divulge the token is as follows:
This is clearly a base64 encoded string... Not an encrypted token.
echo "UmVwb3J0SUQ9Mjg1NzQ2MzEyMw==" | base64 -D ReportID=2857463123
Using this knowledge we can easily modify the ReportID attribute, re-encode it, and send it to MyHealth. Doing so would divulge the test results (which likely discloses Personal Health Information (PHI)).
In my experience, the most common fail point for protecting resources is in instances of "Export" functionality. This can include but not limited to:
- Test Results
- Bank Account Statements
- Account History
The reason for this is a simple oversight. A downloadID is often provided and it's assumed that the end user will follow normal application flow. Additionally, this "Download URL" may be buried within several initialization requests that would be unknown to end users that aren't using a proxy to view and manipulate traffic.
Now that we've dove into into how trivial it can be to compromise full-blown datasets, let's look into the most important topic of all - "Why!?"
Why Is This Even A Thing?
The simple truth is in today's world deadlines are shorter and feature demands take priority over security. As a result, development teams often overlook scenarios where security implications may arise. The other truth is it's not their fault - it's ours.
It goes without saying that training is quintessential to the successful establishment of ANY security team. As trends and attack vectors change, it's imperative that we empower our developers to produce secure code that effectively fends off attackers.
Developer Training: Offense + Defense
One effective approach to training both offense and defense simultaneously is to partner members of both teams together and work through the issues discovered. Often times, offensively-minded pentesters may not understand the underlying complexity of "just fixing" an issue.
Additionally, there may be several ways to implement remediations. From a defensive perspective, having an attacker demonstrate the alternate methods to achieving the same end point can be of ineffable value. Conducting exercises like this can create synergies between the two sides and offers a foundation to be leveraged while building out an application security team.
Problems with 'Pentesting'
The amount of clutter regarding vulnerabilities in systems can be overwhelming. What do I mean by this?
For one, most 'pentest' shops don't provide results that accurately reflect risk. Instead, they provide output from automated tools such as Nessus/Nexpose/Web Inspect and expect the development team to sift through all of the content. If you've ever had the chance to look over one of these reports you know what I mean when I say it's nothing more than a conglomeration of text presented in the least user-friendly manner possible [much like that run on sentence]. These scanner outputs are absolutely awful yet actual 'pentest' shops are presenting this stack of crap as value added. This doesn't provide value, it merely clutters the list of priorities for the folks that are being tasked with, "Just fix it."
Add to this atrocity of industry-produced garbage the constant stream of sexy new vulnerabilities/Government data dumps and we can quickly begin to see how any free/research time has been siphoned away. Reading about something new and current is far more exciting than reviewing a list thrown together by the OWASP foundation ten (10) years ago - that's simply human nature.
What Can We Do About It?
As always, employers MUST instill a culture based around security. If the security of an application or process is compromised, customer data is exposed to unnecessary risk, jobs are exposed to the risk of massive lawsuits. We hear of new data breaches weekly, in fact four thousand (4,000) breaches were reported last year. 'CyberCrime' isn't some mythical thing that only exists in Hollywood, it happens daily whether organizations approve of testing time windows or not.
Organizations, team leads, and coworkers must work cohesively in order to instill this culture of proactive security. It doesn't happen overnight and it doesn't happen easily. However, with the right folks and processes in place, transformations across application development teams can occur. I've seen it first hand and when it happens it's quite amazing.
[Legal Note: Please don't go out on the Internet and begin trying to compromise websites. Testing web applications for vulnerabilities in the real world is illegal unless you are explicitly given permission to do so by the provider. Failure to follow this rule can easily land you in jail.... Remember this.]