I'm Conor Patrick. Former FTC security research intern. I enjoy open source development. I do research for Secure Embedded Systems at Virginia Tech. I'm into photography. I'm currently looking for work in the government. You can follow me on Twitter.

Security and usage of Braintree, a PayPal backed payment gateway API

08 January 2020

My set up with Braintree

After starting an LLC, I decided I would try setting up Braintree. It’s a payment service (like Stripe) owned by Paypal. Braintree allows businesses to easily collect money from customers through a variety of payment options (Visa, Mastercard, Discover, etc) and be PCI compliant. Since Braintree handles this and provides an interface to transfer collected money to a business account, they are considered to be a payment gateway.

Providing a payment gateway service is not easy from a security perspective. The creators of the PCI standard (large credit card brands) want to minimize credit fraud and won’t accept transactions from non-compliant services. The PCI standard is partly designed to ensure that gateway services minimize information leakage from the user, where the user is a business.

So a gateway service should assume that users are oblivious. They should assume that once users obtain some sensitive information (credit card numbers), they will store that information insecurely and it will be compromised.

In this post I will go over Braintree’s security design and the infrastructure I decided to use to integrate it with my website.

Braintree work flow

General work flow for a business is as follows:

Check out the docs to learn more.

Generation of the client token

Generation of the token is done by Braintree’s server SDK. It involves making a call to Braintree’s back-end to generate a new token. This token is then used client side to request Braintree’s client SDK to create a valid payment form with Braintree as an origin.

This token is necessary for a few reasons. It allows Braintree to create payment gateways only for authorized businesses and allows Braintree to keep track of valid customers. Outsiders won’t be able to submit arbitrary purchase forms since they cannot forge a signed identifier.

Entering payment information

The payment form lives on Braintree’s domain. This ensures that they have control over it and no third parties will have access. So on a browser, the form will live in an iframe targeted at a Braintree domain. The business will not be able to read any information from inside the iframe because of the Same-origin Policy. Braintree must do this to be PCI compliant.

It’s important to design the UI correctly so it’s always clear to the user that:

It would be unfortunate for a client to enter a credit card number in the wrong form as it could allow the business to read the information in violation of PCI.

Nonce representing the transaction

If the business cannot see the payment information, it needs another way to control how it charges customers. This is where Braintree uses a nonce (number used once) as an identifier for a transaction.

After a user submits a valid payment, Braintree will store the potential transaction and return a nonce to the business to use as a handle to the transaction.

Now the Nonce is just another signed token, like the client token. But while the client token may be reused for some applications, the nonce can only be used once. This prevents duplicate charges from occurring (a good measure against those oblivious users) as well as sensitive information leakage later on.

The nonce should be used on the business’ server to securely act on the transaction.

Charging the customer

Presumably, the nonce can only be acted on by a server with the right API key. The users server will have access to the API key and can charge a monetary amount to the transaction using the nonce.

Here is a python example from Braintree docs:

result = braintree.Transaction.sale({
     "amount": "10.00",
    "payment_method_nonce": nonce_from_the_client

Setting up a payment system securely

I’d say Braintree is pretty user proof. But let’s not get lazy and not set up a weak infrastructure!

Let’s assume our infrastructure just needs to support two services: a web application and a payment server.

If I were at a hackathon, I would probably just run both the payment service and web app on the same machine as root. But that’s a disaster waiting to happen.

You should assume that at some point, an application will get compromised. It could be because of an admin mistake or a new vulnerability discovery. It happens. It’s important that all applications remain as separate from each other as possible. I want my web app to be able to use my payment system to get paid, but at the same time I don’t want my payment system and account to get exploited the moment my web app inevitably gets owned. I would like there to be some additional difficulty. This is the basis of privilege separation.

It really doesn’t matter that one of the services is a payment service: this decision process should happen for every service that is distinct and independent.

A basic improvement could be to just run the services as different users on a typical Linux or *Bsd system. Then an attacker would have to gain privilege escalation after compromising the web app to exploit the payment system. But that’s still a little close. Since the payment system isn’t tied to the performance of the web application, why not just run it on a different machine?

Here is the solution I employed.

I have one preexisting digital ocean instance for my blog (web app) and I added another one to run the Braintree system. I set up another Let’s Encrypt cert on my Braintree instance so the web app can securely proxy to the payment service via TLS. For good measure, I set up the HTTP server on the braintree instance to be restricted with a password so only the webapp instance can access it directly.

The result

Here is my Braintree payment form.

It’s live and if you check the page source, the payment info is indeed handled by a Braintree domain. Feel free to try it out: you will be charged $0.10 and I am happy to refund.

As usual, you can see the source for my projects on Github.

Let me know if you see any problems or have ideas/suggestions.

I'm on Twitter if you'd like to follow my interests.