Introduction
I came across an application where I had to validate the security fix implemented by the development team. The first assessment had findings like Vertical Privilege Escalation and Horizontal Privilege Escalation through Insecure Direct Object Reference. An attacker could simply capture a request in Burp Suite and modify ‘dashboardID’ parameter to get read and write access to another user’s dashboard.
To fix these issues application team implemented AES encryption. Now all the form parameters and URL parameters were encrypted before being sent to the server and it was no longer possible to modify the request through Burp Suite.
Description
The application has the functionality to view and manage dashboards. It is available for admin and non-admin users. But non-admin users cannot see and modify the dashboard of admin users and vice versa. To avoid tampering in Burp Suite application uses the following JS library for encrypting form and URL parameters at the client-side.
- pbkdf2.js
- securityUtil.js
- crypto-js.js
securityUtil.js has the following 3 important functions for encryption.
- queryParamEncryption(): calls getEncriptionUsingAES() to encrypt query parameters.
- formParamEncryption(): calls getEncriptionUsingAES() to encrypt form parameters.
- getEncriptionUsingAES(): performs the actual encryption.
All the above 3 functions take 4 arguments.
- Arg1: The parameter to be encrypted
- Arg2: Initialization Vector (IV)
- Arg3: Salt
- Arg4: Token
IV, Salt and Token are dynamically generated for every request. There is no hard-coded secret key in the source code. Along with encrypted parameters the application also sends IV, Salt and Token with every request to facilitate the server to decrypted form and URL parameters. After decrypting the necessary parameters application performs various operations. Tampering any parameter in the request resulted in an error.
Impact
The consequences of bypassing client-side encryption led to the following issues —
- A non-admin user was able to view, modify and steal an entire dashboard from ‘admin’ users.
- A similar fix was implemented for other reported issues. Hence this finding was applicable to all other issues with a similar fix.
- This had organization-wide repercussions as it (encryption) was treated as a standard way to fix issues in several other applications.
Proof of Concepts
As usual, I started by tampering with the encrypted request parameters to observe the application behaviour. The application responded with ‘ENCInvalid input’ or ‘Content-Length: 0’ if any of the request params were tampered with or completely removed from the request. This led to the conclusion that encryption/decryption is properly implemented and validated at the server-side. At this stage, I felt there is no point in further analyzing the issue.
I got curious to understand how the application encrypted form and URL parameters. I opened up Chrome Dev Tools (F12) and searched (ctrl+shift+s to search in JavaScript) for ‘encryption’.
I ‘Pretty-printed’ the JS files and checked both crypto-js.js and securityUtil.js. SecurityUtil.js looked particularly interesting as it had various functions like queryParamEncryption, formParamEncryption, getEncriptionUsingAES. But to my dismay, the code was a bit cryptic and it was difficult to figure out what was going on inside these functions. But the ‘function name’ gave me some hints and I assumed the following things.
- queryParamEncryption: is used for encrypting query params
- formParamEncryption: is used for encrypting form params
- getEncriptionUsingAES: returns AES encrypted string
With that assumption, I thought of playing around with these functions. I went to the ‘console’ tab and called these functions one by one to see if I can encrypt my random string with a randomly chosen value of IV, Salt and Token.
I could successfully encrypt 1 with randomly chosen values 2, 3, 4 (which is IV, Salt and Token respectively). The result looked promising. :) :)
Next, I tried to check what are the actual arguments for these functions, and for that, I added a breakpoint in ‘formParamEncryption()’.
I logged in as ‘admin’ to get the ‘dashboardID’ of one of my dashboards which was not available to non-admin users. Now if I performed any operation in the application the execution stopped at the breakpoint and I could see precisely what arguments were passed to the function. ‘F10’ shortcut can be used to go to the next line of code.
I got an interesting parameter ‘dashboardId’ which was otherwise encrypted and invisible when I captured the request in Burp Suite. At this point, things got more interesting and I thought what if I modified the ‘dashboardId’ parameter and called the same function from ‘console’ to get the encrypted form parameters? The other parameters for calling the function were already revealed in this process.
Another thing to note is that both the functions queryParamEncryption and formParamEncryption call getEncryptionUsingAES (line number 124 and 141 respectively) to get the encrypted values. At this point, I had already got the ‘dashboardId’ of the admin user. Now I logged in as a non-admin user, noted down all the parameters from Dev Tool required to call the getEncriptionUsingAES function (as described above). I went back to the console tab and called the getEncryptionUsingAES function with appropriate parameters. Note that I have tampered the value of ‘currentDashboardId’ and ‘dashboardname’ parameter which was otherwise impossible to do in Burp.
I got the encrypted form parameters. I used the same ‘Dashboard modification’ request in Repeater which I had used in the previous step to collect all the arguments necessary to call the getEncryptionUsingAES function. Please note if you use any other request the attack fails because IV, Salt and Token are unique for every request. I went to Burp Suite Repeater and replaced the value of the encrypted form parameter with the one I had obtained in ‘console’.
Remediation
Authorization should always be explicitly checked server-side to verify that the user requesting to view data or perform an action is authorized to do so.
This may be as simple as checking that a user is authorized to view a page in the application or checking that the user is authorized to perform an action overall. Many other more fine-grained situations may exist depending on the application context and the complexity of the business functionality. The following are some common situations where a more fine-grained authorization check is necessary -
A user may be authorized to perform an action, but only against certain entities involved in the transaction. For example, a user may be authorized to perform a funds transfer from one account to another, but they may only be authorized to access certain accounts. Authorization checks must be performed to verify that the user is authorized to make a funds transfer as well as verify that the user is authorized to perform that transaction using the supplied accounts.
A user may be authorized to only view data tied to their account. For example, a retail banking customer may be authorized to view the details of each of their accounts at a particular bank but may not be authorized to view the details of other users’ accounts. Authorization checks must be performed to verify that the user is authorized to view account details as well as verify that the user is authorized to view the details of the account, they have requested information for.
Likelihood: High
Impact: Critical
Severity: Critical
References
- https://medium.com/@imayankraheja/tampering-encrypted-parameter-to-account-takeovera5fec7dde360