Building a Fintech Product: Handful Security Tips (Part 1)

by Eric McWinNEr

.

Updated Fri Mar 08 2024

Building a Fintech Product: Handful Security Tips (Part 1)

Few kinds of software receive the massive attacks that fintech applications receive daily.

When a new fintech application is launched, it receives attacks left, right, and center — from people trying to make free money to those trying to test their hacking skills.

Few kinds of software also carry the tremendous risk that fintech applications pose in the wake of a successful attack.

It could lead to the loss of millions of dollars of investor and user funds, and when this happens, the team that built the system would have to face the music.

Developers can take several practical steps when building fintech applications to mitigate the effect of these attacks. In this two-part article, we go through different scenarios, solutions, and issues that you could pay attention to and apply to protect your application today. This series is not exhaustive, but it represents a sample from research, personal experiences, and interviews with developers who have built financial systems. Some developers have to learn the hard way, and you don’t want to learn the hard way.

Never debit negative amounts.

Financial applications that deal with crediting and debiting funds can be susceptible to a negative debit attack. A negative debit attack is when a malicious user tries to transfer or debit negative funds from their account. The problem with this attack is that if the application doesn’t reject negative amounts, a debit of negative funds will result in a credit x -(-y) = x + y.

This simple, basic vulnerability could be very severe and put a fintech product out of business on launch day because a malicious user can keep crediting themselves by debiting negative funds. More applications than you might imagine have fallen prey to this attack, and the worst (or best) part is that it can be prevented with about three lines of code.

Solution

A piece of code like the one below could save you from this attack:

if(amount < 0.01) {
  throw Error("Invalid transaction amount")
}

It’s easy to scoff and say, “It’s such a simple code. How could a developer forget this basic check?” but it’s easier to omit than one might think. One omission at the right (or wrong) place is all a malicious user needs to drain your system.

Prevent Race Conditions

A race condition is a situation where a system tries to perform multiple operations simultaneously that should ideally be performed one at a time. When this happens, it’s normally not certain which operation will finish first, and if the system does things in the wrong order, it could lead to undesirable results.

Malicious users try to force your server to handle race conditions when you’re debiting their account or doing similar operations to make you inadvertently give them twice the value. They could do this by clicking a button multiple times while the request is still being processed or by setting up specialized tools to poll your endpoints several times per second.

To illustrate why this is a severe problem, imagine a simple fintech application allowing users to transfer funds from one wallet to another. If a user polls the transfer endpoint multiple times per second, the API might start processing a new transfer before the database has finished saving the transfer and updating the wallets’ balances. This could mean that the receiver could receive twice the amount the user is sending, and they could send it back and forth repeatedly, doubling the fund on each cycle.

Solution

There are several approaches to solving this problem. The bottom line is to ensure the system doesn’t handle more than one transaction or action before the database is completely done writing its records. As a rule of thumb, you shouldn’t write multiple transactions for the same user(s) at the same time.

Different developers use different methods to solve this problem. Here’s a list of potential solutions:

  1. Some developers do a simple if-else check on the requests. If they see multiple transactions with the same data happening at the same time or multiple transactions happening for the same set of users at the same time, they disable the request from proceeding.

  2. We could also solve this by using a queue or messaging system. Subjecting every transaction to a queue means the queue system would have to complete each transaction before a new one is processed, effectively preventing any race condition. We could achieve this with a message system like RabbitMQ, Apache Kafka, or Amazon SQS.

  3. We could set up our databases to use atomic transactions. Most SQL databases allow us to create transactions that treat related operations as one unit of work. This would ensure all actions related to that unit of work are completed before they’re committed to the database.

  4. We can disable the button on the frontend and prevent the browser from sending multiple requests when a response hasn’t been received yet.

I personally solve this problem on the backend by using lock mechanisms. I create a server cache when a transaction is initialized and kill it when it is done. Whenever a new transaction is initialized, I check for the presence of a record in my cache tied to that user and disable any further transaction if one is present. This is the same idea most Operating Systems use to prevent multiple processes from simultaneously writing to the same file.

Different methods have pros and cons. The method to use would be largely dependent on the system, your team, the programming stack, and a host of other factors. We effectively prevent these kinds of attacks once we can ensure race conditions aren’t present.

Never Trust any Requests to your Servers.

Another way malicious users can infiltrate a system is by pretending to be a trusted client. Imagine this scenario:

  1. A fintech app uses a third-party payment service to charge cards, and after a successful charge, it gives its user some value.

  2. This fintech app uses webhooks to determine when a payment is successful on the third-party payment system and then give the user value.

  3. A malicious user is aware of the webhook endpoint and sends a request in the same format as the payment service.

  4. The fintech app does not verify claims, so malicious users get free value.

It’s necessary for all fintech applications to assume every request could be malicious and take all the necessary steps and validations to ensure your application verifies that every request actually comes from the source we expect.

Solution

This attack can be prevented by providing a source of claim verification between the client and server. Before any request is completed, the server and client would perform a handshake and exchange certificates or some source of claim.

Many third-party systems that supply webhooks allow you to save a hash on your server that they send with every webbook. You can compare this hash to verify that the request is coming from their servers. All parts of your system that communicate with each other should have such protection.

It could also be helpful to whitelist IP Addresses, allowing only trusted parties to request your server if you’re sure the resource's IPs do not change. The bottom line is, do not trust any request you receive on your server. You should have several mechanisms to verify that any request your system acts on comes from a trusted source.

Watch out for your underlying software.

A lot of developers get pressed to watch for vulnerabilities in the code we write for our applications, so we forget that there could be security vulnerabilities in the underlying software powering your application, like Ubuntu, MySQL, or even your backend framework or language. Sometimes, a malicious user doesn’t even need a loophole in your code; a loophole in such lower-level systems can be all they need to wreck havoc.

While problems of this nature aren’t always a hundred percent preventable, there are measures we can take to ensure the potential of issues like this is minimal.

Solution

  1. Keep up to date with any news on security vulnerabilities surrounding your underlying software

  2. Ensure you’re properly configuring your backend frameworks. Ensure you’re not publicly spitting out stack traces, providing access to your file system or configuration files, etc

  3. Provide as little information about your system and stack as possible, such as by hidingX-POWERED-BY headers or using reverse proxies like Cloudflare

  4. Regularly update your underlying system once new versions become stable

Log Everything!

Your fintech app should log anything and everything. All information and data are important, and I don’t believe we can have too much information if it’s not repetitive. You should be able to tell every page each user visited and every endpoint they called with the request and response data and draw up their complete footprint across your app.

This practice is very helpful for many reasons. If there’s any funny business on your system, it’s easier to find out what went wrong and do forensic analyses when you have records of the exact resources each user accessed on your system. You can tell which endpoint a user visited and the request they sent, which third-party API was attended to, etc. Without records like this, finding the source of problems becomes a hassle.

Besides the security and debugging advantages, it also opens up many business opportunities. If your fintech app is successful, these logs can become a source of data you can use to study user behavior and patterns to create more useful solutions. You can tell which services your users use and which they don't and know where to channel efforts. You could train machine models on these data to create predictions and insights on user behavior. The possibilities are limitless.

Conclusion

We’ve come to the end of the first part of this two-part series. In this article, we learned of several actions we can take to make our fintech applications more secure. In the next part of this article,

we dive into more actions we can take to protect our applications. I look forward to seeing you there.

As stated earlier, this series is in no way exhaustive. There are certainly more measures I and the rest of the readers could learn from. Feel free to leave comments on anything I might have missed or any stories from your development experiences that could help developers build more secure apps.

In the meantime, please follow me on Twitter: @ericmcwinner and come say hi.

Whenever you're ready

There are 3 ways we can help you become a great backend engineer:

The MB Platform

Join 1000+ backend engineers learning backend engineering. Build real-world backend projects, learn from expert-vetted courses and roadmaps, track your learnings and set schedules, and solve backend engineering tasks, exercises, and challenges.

The MB Academy

The “MB Academy” is a 6-month intensive Advanced Backend Engineering BootCamp to produce great backend engineers.

Join Backend Weekly

If you like post like this, you will absolutely enjoy our exclusive weekly newsletter, Sharing exclusive backend engineering resources to help you become a great Backend Engineer.

Backend Tips, Every week

Backend Tips, Every week