The Bug That Exposed Your PayPal Password

And Credit Card Number Too

When hunting for security issues, the pursuit for uncharted assets and obscure endpoints often ends up taking the focus away from obvious, but still critical, functionality.

If you approach a target like you are the first person to ever perform a security assessment on it, and check everything thoroughly, I believe you are bound to find something new — especially if the code you are testing has been in continuous development for a while.

This is the story of a high-severity bug affecting what is probably one of PayPal’s most visited pages: the login form.

Initial discovery

Image for post
Image for post

This immediately drew my attention, because providing any kind of session data inside a valid javascript file usually allows it to be retrieved by attackers.

In what is known as a cross-site script inclusion (XSSI) attack, a malicious web page can use an HTML <script> tag to import a script cross-origin, enabling it to gain access to any data contained within the file.

Sure enough, a quick test confirmed the XSSI vulnerability and, although a javascript obfuscator was used to randomize variable names on each request, the interesting tokens were still placed in fairly predictable locations, making it possible to retrieve them with just a bit of extra work.

Image for post
Image for post

However, a secret is only as good as the damage you can do with it. I immediately set out to find out what exactly _csrf and _sessionID were and if they could actually be used in a real attack.

Digging further

Next, I went back to the vulnerable script and followed the tokens to find what they were actually used for. This led to a deep dive into one of PayPal’s main protection mechanisms used to prevent brute force attacks, the security challenge. While this functionality is used in many places, I will be focusing on the main login form.

Image for post
Image for post

The idea is pretty simple: After a few failed login attempts, you are required to solve a reCAPTCHA challenge before you can try again. The implementation, however, may raise some eyebrows.

Upon detecting a possible brute-force attempt, the response to the next authentication attempt is a page containing nothing but a Google captcha. If the captcha is solved by the user, an HTTP POST request to /auth/validatecaptcha is initiated.

Image for post
Image for post

The familiar _csrf and _sessionID are present in the request body, as well as two other values, which we will get to a bit later.

The response to the captcha validation request is meant to re-introduce the user into the authentication flow. To this end, it contains a self-submitting form with all the data provided in the user’s latest login request, including their email and plain text password.

Image for post
Image for post

I realized that, with the correct timing and some user interaction, knowing all the tokens used in this request was enough to get the victim’s PayPal credentials. In a real-life attack scenario, the only user interaction needed would have been a single visit to an attacker-controlled web page.

So I went back and tried to figure out what the missing parameters were. This was easier than expected:

  • The value of jse was not validated at all.
  • recaptcha was the token provided by Google upon solving a reCAPTCHA challenge. It was not tied to a specific session, so any valid token— for example, from an automated solving service — would be accepted.


First, the proof of concept would exploit the initial XSSI vulnerability to get a set of tokens which were valid in the victim’s session. It would then launch a few authentication requests with random credentials from the victim’s browser, simulating a brute force attempt, which would trigger the security challenge flow.

Once the victim logged in to PayPal using the same browser, the cached random credentials would be replaced by the user’s own email and password. The last step was obtaining a fresh reCAPTCHA token, after which the plain text credentials would be retrieved with a server-side request to the /auth/validatecaptcha endpoint and displayed on the page.

Image for post
Image for post
The final page shown by my proof of concept code contained your email and password

I later found that the same vulnerable process was also used on some unauthenticated checkout pages, allowing plain text credit card data to be leaked using the same technique.


Following a quick acknowledgement by the PayPal team and a few additional questions, I was awarded a $15,300 bounty on the 10th of December. The reward amount corresponds with the bug’s 8.0 (High) CVSS score, which is the same score that I had initially suggested when submitting the report.

A patch was applied around 24 hours later, meaning that the bug was fixed only five days after PayPal became aware of it — quite an impressive turnaround time.

Fix and prevention advice

While this properly fixes the vulnerability, I believe that the whole thing could have been prevented when designing the system by following one of the oldest and most important pieces of infosec advice: Never store passwords in plain text.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store