With the advent of PSD2 APIs, we had the opportunity to test some of them upon request from our clients. Although internet-facing APIs were already a thing thanks to smartphone apps, it seems that regulatory requirements and 3-way setups (customer, bank, provider) led to some surprises. Here are some of the things we found.
Back to the ’90s: SOAP
Putting yet another twist at the classic quote from Jamie Zawinski, some people, when confronted with an API problem, think “I know, I’ll use SOAP.” Now they have two problems. There are SOAP standards for everything, this includes several forms of authentication. While providing some form of static credential such as a password or token can be abused by altering the message, signing the parameters using a private key can be advantageous. However, as per usual, rules are nothing without enforcement, so we tried seeing what the API accepted.
- Resending requests without modification showed that replay attacks are possible. (There’s no tampering here, the signature stays valid.)
- Resending requests past their temporal validity showed that replay attacks can be performed indefinitely, even after the validity period is over.
- Tampering with the message and its signature revealed that the server didn’t even enforce the request to be signed.
In the end, the API only checked whether there was a BinarySecurityToken field, everything else could be removed. For those lucky folks unaware of the inner workings of WS-Security, this field contains the X.509 certificate of the signer (here: the TPP), which is not considered sensitive, making it easy to obtain and exploit this bug.
New kids on the block: JWT
Of course, every generation of developers has their tools en vogue. The first decade of the third millennium was SOAP with WS-Security, the second was REST with JWT (and JOSE and so on). Just like how former is a great footgun, latter is not really an improvement either. Unsurprisingly, in both cases, numerous apologists are claiming that both can be implemented securely, but on a similar note, I wouldn’t want to cross a minefield just because some people have managed to cross it unharmed. I don’t even want to start describing how bad they are, the linked resources do a much better job at this than I ever could.
However, some implementers could take it one step further. JWT tries to mimic some parts of X.509, only by using JSON instead of ASN.1, latter being notorious for memory corruption issues. It seems that trying to avoid these by ditching the binary format came at the cost of introducing injection vulnerabilities by adopting a text format. In one case, TPPs could register a name for their application and this was included in all further JWT tokens issued by the service. However, no escaping was performed, thus adding quotes made it possible to close the attribute and add further JSON key-value pairs. This led to a security problem since this token also stored access control entries right next to this entry.
This was not easy to debug as well, since many financial organizations opted for using black-box appliances and just put them in front of the API to do authentication. In such a scenario, asking the developers of the API or local IT operators anything about the inner workings of a box sitting between the public internet and an API with access to financial information usually yields something along the lines of “this is magic”. This of course becomes a large issue when such a system fails open, thus like in the first WS-Security issue above, everything works well (for certain values of well) until
everybody follows the rules – and no alarm will sound when somebody doesn’t.
PKI-based authentication is more complex than you think
Finally, there are the oldschool bit warriors, who just laugh at JWT and SOAP authentication methods, and since TLS is already pretty much mandatory, they realize they could use its mutual PKI-based authentication. As Filippo Valsorda wrote, “signing […] is not a tooling problem, but a trust and key distribution problem”, and as it turned out, both have significant non-technical costs. In this case for example, while integration testing was taking place, the above issues were solved by passing around private keys. We asked how this would work in production, thinking about some workflows involving submitting CSRs and receiving certificates, or worse, private keys. Then reality hit when they replied; there was no plan, and they had to come up with one after the fact.
To aid people in similar situations, here’s our conclusion in the form of a questionnaire, best answered before going live with such an API.
- If you have some form of signing, do you have the technical and administrative background for managing trust and key distribution?
- Are you aware how your parsers digest user input? What are the possible failure modes? How do you mitigate these risks?
- Have you considered all the security implications of the chosen protocols?
- Does your authentication layer fail open? If so, do you have any automated tests that will alert you and your team of the fact?