Today’s post does not involve vendor-shaming! It does, however, involve credit cards, design mistakes, and the potential to rip off customers. We were implementing pre-authorized credit card billing, and in a typical fashion, the project brought us on late in the game.
To understand the issue at hand, you’ll have to understand the proposed solution. There were three systems involved: the Customer Information system (CI), the Bill Management system (BM), and the Payment Vendor system (PV). The PV is responsible for storing credit cards in a secure manner, which is referenced by our BM using a token, and is also responsible for completing all the actual payment tasks like charging the credit card. The CI is responsible for storing the customer’s details, including the aforementioned token. The BM stores information like how much a customer pays each month and their billing date. On the billing date, the BM grabs the token from the CI and submits it, along with the bill total, to the PV.
Now, this solution looks okay on paper. The problem arises when you see how the token initially got from the PV to the CI. When a customer wanted to register for pre-authorized billing, they had two options: 1) do it through the online self-serve, or 2) call in to an agent. The agent used a desktop application, which did a fair job automating the process. Essentially, the agent just had to enter the credit card information into the application. The application would then make the necessary calls to the PV to save the credit card and get a token, and then make the call to the CI to save the token to the customer’s account.
The problem with this flow was that the agent not only had visibility into the customer’s token, which could be used to charge money to their credit card, but that the agent also had permissions in the CI to arbitrarily write tokens to accounts! This means that if I added a token to an account, I could just as easily repeat the call to the CI to add the same token to anyone else’s account. Effectively, this means one customer could be paying multiple customer’s bills!
Now, if the project team had involved us earlier, perhaps in the design phase, we may have caught this issue before it was written into the application. We could have advised the project that this would be the perfect use case for an intermediary service, one that would handle the calls to the PV and the CI behind the curtain, hiding the inner workings from the agents. Unfortunately, we were brought on late and so the project had to take on additional work and redo completed work, as customer security is priority #1.
The morals of the story: 1) Not all vulnerabilities are code issues, it helps to step back and look at the design, and 2) Involve your testing team early, not just in development/implementation phase but also in the design phase, as you can save yourself from having to start over later.