The Scenario
In the scenario that I experienced was having the same functionality, but in addition to account owns passwords, it was also having passwords of company employees. The interesting thing is I was able to hack into the Password Vault that stores user passwords by exploiting a Cross-Site Scripting vulnerability that I found in the same domain.So, whenever I test an application the first thing is that I identify what kind of company am I targeting. In this case this was the Password Manager, so as you all know it is a vault that stores passwords. The "Passwords" are the sensitive data that it (tries to) protect. Capturing and retrieving those passwords was my initial goal.
![]() | |
2 records added to Password Vault containing account passwords |
The flow of application
In order to understand how the application is working, we need to understand its functionality and its flow. We need to understand how data is retrieved and from where is it retrieved.
After carefully observing the application and going through each and every request I found that the application was retrieving different information from the API that was located at /api/ of the application.
After a bit of crawling and spidering through the application I found some API endpoints:
API Endpoints to look into
As the application was fully interacting with the APIs, I understood the flow as each endpoint was returning some value and information such as record ID, session token and other things. Let me explain some of the APIs that i though might help in achieving my goal.
The records/all endpoint
An endpoint that was located at /api/v3/records/all and it was accepting a GET request. Once a GET request is sent while being authenticated, it returns JSON objects having record ids and other information related to available records.
![]() |
JSON response from /api/v3/records/all |
The passwords/record endpoint
This endpoint was basically located at /api/v1/passwords/record . After the record IDs were retrieved from the record/all endpoint, this endpoint is used to retrieve passwords and full information from those specific record IDs.
In our case we got the following record IDs:
- 526882 - ID for "Facebook Account" record
- 526883 - ID for "Google Email" record
If a user clicks on the "Facebook Account" record, a POST request to /api/v1/passwords/record will be sent using the following JSON data having the record ID 526882:
![]() |
Record ID being sent to API for retrieving full record information |
![]() |
Full information of specified Record ID returned by the endpoint leaking Password & Usernames. |
The session/token endpoint
So in order to find out how that token was made. I looked up other endpoints to see if there was anything informative and I found that the API endpoint located at /api/v1/session/token was responsible for the generation of CSRF tokens.
Making a GET request to the endpoint returns the following response:
![]() |
Session/CSRF token returned by API endpoint |
Loading the Weapon
Now that we came to know about the flow of the application and the endpoints that are being used for exchange of data. We need to somehow obtain information from the following endpoints:
- Session Token from /api/v1/passwords/record
- Record IDs from /api/v3/records/all
- Record Information from /api/v1/passwords/record
In order to obtain information from the endpoints, a simple trick would be exploiting some misconfigured CORS but the application doesn't seems to be using it for resource sharing.
The other possibility was to find XSS vulnerability somewhere on the same domain in order to get rid of Same-Origin Policy(SOP). Else all of our XHR calls will be vanished and rejected due to violation of SOP.
So, after a while I was successfully able to get XSS at an email activation page where user supplied email was reflecting back improperly.
So let me give the world's most common demonstration of an XSS:
![]() |
A popup without actual demonstration of risk |
Alright, we just got ammo for our weapon. Now no need to worry about the SOP and we can easily make XHR calls in order to communicate with the APIs in the same way the application did.
Replicating the application flow
Now that we have all the things required, we have to replicate the application flow. In order to make an XSS exploit that replicates the application flow and grabs all the information needed we need to make sure it proceeds in the same way.
First, we will use the javascript function fetch() in order to make a GET request to /api/v3/records/all in order to obtain all the record IDs:
![]() |
Using fetch() to retrieve record IDs from the API |
after the records are grabbed the next thing is to get the session token in order to make POST requests. I also converted the response of records to JSON and called the value of record ID directly from the JSON object. A fetch() was used for sending a GET request for capturing the token and retrieving its value from the JSON object:
Now we got the "session_token" & the "record IDs". Now all we have to do is to is to send a POST request having the "record ID" to /api/v1/passwords/record. I'll use the XHR to send a POST request with a specified record ID. I will loop through the record IDs so each record information will be retrieved one by one:
As you can see from line #30-34, XHR is being configured with proper details. On the line #45 the values are placed in a proper form {"id":record_ID_here,"is_organization:false} and the request is made afterwards.
Once the request is made, the response will be parsed and values will be grabbed such as Title, URL, Username, Password from the response. The values will be then added to a dummy variable "data_chunks" for final processing.
After the dummy variable is filled with collected data, it will be converted to base64 to avoid conflicts of bad characters and will be sent to attacker's host.
![]() |
A fetch() being used for retrieving session_token as seen on line #20 |
Now we got the "session_token" & the "record IDs". Now all we have to do is to is to send a POST request having the "record ID" to /api/v1/passwords/record. I'll use the XHR to send a POST request with a specified record ID. I will loop through the record IDs so each record information will be retrieved one by one:
Once the request is made, the response will be parsed and values will be grabbed such as Title, URL, Username, Password from the response. The values will be then added to a dummy variable "data_chunks" for final processing.
![]() |
Storing data chunks to a dummy variable |
After the dummy variable is filled with collected data, it will be converted to base64 to avoid conflicts of bad characters and will be sent to attacker's host.
![]() |
Sending collected data as base64 |
Note: There are many other methods to properly send the grabbed data but in order to demonstrate i'm using a simple way such as directly sending the base64 encoded data. Sending data via POST to a specific file would also be an exciting option.
Aiming & Shooting the target
Now that our exploit is completed, we have to inject it into the vulnerable area of XSS. There are 2 simply tricks that can be used when exploiting an XSS.
- Hosting your javascript exploit on external host ( You might have to set up CORS in order to make it accessible )
- Including the payload directly with eval and atob
The second method is quick and can be used for handling short payloads. I'll be using the following payload:
![]() |
Base payload to use |
now simply replace atob()'s value with our base64 encoded source code will do the trick. First our payload will be base64 decoded by atob and then it will be executed using eval().
So here is the final payload:
![]() |
Final payload ready for execution |
Note that some people will say its a kind of large payload, obviously it is but still we can just load the .js from external Host but in order to avoid setting up CORS, I'm using this technique.
Now i'll just host a exploit.html file having the following code:
![]() |
HTML file for making a redirection to a larger URL |
Now simply giving a URL for exploit.html, the attacker can make a user redirect form http://attacker.com/exploit.html to the page where large payload is injected.
As a result, we will get the data to our host that we configured for retrieving data:
![]() |
Exploit successful! Vault information retrieved. |
From the screenshot above, you can clearly see that the records stored in the Password Vault were finally retrieved and we successfully exploited and escalated the impact of an XSS vulnerability!
The purpose for this write-up was to clarify that:
XSS isn't just a popup, XSS vulnerability can lead to serious damage if properly exploited. Even if it's demonstrated via a harmless popup execution still it poses a risk.
If you guys love this write-up, Share! :)
Btw, I've uploaded my exploit-code here so you can review: https://gist.github.com/shawarkhanethicalhacker/e40a7c3956fdd24b9fb63d03d94c3d34