Skip to main content

One post tagged with "security"

View All Tags

The 10-Day Battle Against a Sybil Attack on Fina

· 7 min read
Shawn Cao
Founder @ Fina Money

CAUTION: This article is about security, and it gets a bit technical. For some people, it may feel a little boring. 😅

Sybil Attack: A concept from network security where an attacker creates a large number of pseudonymous identities to gain a disproportionately large influence or drain resources from a system.

Round 1: The Hacker Created 5,400 Fake Accounts in One Hour

Five days ago, I opened the Fina dashboard while sipping my morning coffee and noticed something strange: 5,400 new accounts had been created in Fina.

If this were real, it would be a huge overnight success for a finance app. Even though we believe we have built a pretty good product, we had never experienced anything close to that kind of growth.

So my instinct immediately told me: something went wrong.

I started peeking through the emails of the latest accounts. Almost all of them used the same email domains: first emalupe.com, and later uberip.com.

It didn’t take long to search for them and find out that they were throwaway email providers.

It was not difficult to figure out what happened: someone had created a huge number of fake email addresses and was using Firebase’s public API to create accounts in Fina.

And yes, by default, a simple API call can create an account in your system when you expose Firebase Authentication to the client.

So why is the Firebase API key public? How did the hacker get it?

Actually, there is nothing particularly secret about the Firebase API key. It is normal to put Firebase configuration in your web client. In our case, it was inside our React application.

const firebaseConfig = {
apiKey: "AIzaSyYourPublicAPIKeyGoesHere", // <-- This is your public key
authDomain: "://firebaseapp.com",
projectId: "your-app-id",
storageBucket: "://appspot.com",
messagingSenderId: "1234567890",
appId: "1:12345:web:abcde12345"
};

Anyone can download your client-side JavaScript bundle and scan it to find the apiKey, projectId, and other Firebase configuration information.

This is public by design. The problem is not that the key is public; the problem is what the public-facing API allows someone to do with it if you don’t have enough protection around it.

Defense

Now that we knew the fake-account email pattern, we needed a mechanism to stop it.

Firebase provides a feature called Blocking Functions, which allows you to run custom logic before creating an account or before logging in.

blocking-functions.png

We deployed a Cloud Function like the following and configured it through the Blocking Functions section:

export const beforecreated = beforeUserCreated((event) => {
const user = event.data;

// Example: Block users with unauthorized email domains
const {
email,
} = user;

if (email &&
(
email.includes("@emalupe.com") ||
email.includes("@uberip.com") ||
email.startsWith("mk") ||
email.startsWith("probe")
)) {
throw new HttpsError("invalid-argument", "Unauthorized email domain.");
}

return;
});

This worked.

But I knew this defense could only stop this particular attack.

Without solving the root problem, the hacker would simply come from another direction. I knew they weren’t going to give up that easily.

At the same time, we also added HTTP referrer restrictions to the API key, limiting requests to our app domain, app.fina.money.

Technically, we knew this wouldn’t help much by itself, because a determined attacker could potentially simulate or manipulate the relevant request context. But it was still another layer of protection.

And then, as expected, the hacker came back.

Round 2: The Hacker Abused Our Partners’ Services

Fina integrates with partners to provide bank connections for our users. These integrations are done through APIs.

Like I expected, the hacker started programmatically creating a large number of fake accounts without following the previous email pattern.

This time, the accounts mostly ended with googlemail.com or gmail.com, and their email handles looked like randomized variations of legitimate-looking Gmail addresses:

Why did the hacker choose this method?

Because Google treats email addresses with or without dots as identical for personal Gmail accounts.

This is convenient for regular users, but it is also convenient for someone trying to generate huge numbers of seemingly different email addresses.

With some email automation, the attacker could even automatically read the verification emails sent by Fina and verify these accounts.

And needless to say, AI makes this kind of automation much easier.

It enables hackers to do bad things faster and with less effort, too.

Through this flaw, the hacker started sending hundreds and then thousands of Link requests through Fina’s API to our partners, Plaid and MoneyKit.

The result was troublesome. Our partners started temporarily cutting off our access to stop the abusive traffic. That meant our real users could no longer link their banks during the suspension.

What a loss.

Firefighting

At this point, we had to move quickly.

  1. We deployed a throttling guard.

We limited the number of API calls that could be made within a small time window. This helped reduce the cost and slow down the attack, but it didn’t really solve the problem.

  1. We deleted the fake accounts.

We needed to remove these accounts so their identities could no longer be used to access Fina’s APIs. This was much harder this time because the emails no longer followed an obvious pattern. Fortunately, the majority of these accounts were still unverified, so we could identify and delete them programmatically. Unfortunately, about 100 of them had already been verified. For those, I had to use my own judgment and delete them manually.

It was messy. But we bought ourselves something important: time.

Round 3: Fix the Root Problem

Before the hacker could launch another massive attack, we had a small window of time to fix the root problem.

But how?

As I mentioned earlier, the hacker was taking advantage of Firebase’s public-facing account creation flow.

To completely solve the problem, we had to give up some of that convenience.

It was painful, but only for a short time.

We went directly into the Firebase console and disabled the “Enable create (sign-up)” option under User Actions. This completely disabled user account creation through the public Firebase API.

enable-create.png

But we still needed normal users to be able to sign up with email and password.

So we moved user creation into our own API layer.

Then, to protect this new endpoint from another variant of the same attack, we added another layer of protection:

reCAPTCHA.

After this migration, clients could no longer directly use Firebase’s public API to create a user account. Instead, account creation had to go through our own API, which was protected by reCAPTCHA and our own server-side controls.

Finally, I felt like we could breathe a little better.

The hacker actually helped us make the system more secure and more robust.

But honestly, I still really hope they don’t launch another time-wasting attack like this. 😅

Conclusion

This is the story of how we fought a Sybil attack against Fina over a 10-day window.

What made it interesting was that it didn’t happen as one single attack. It came in multiple rounds.

We would block one approach, and the hacker would come back with another variation. We watched them try different ways to bypass our checks, and it really felt like a boxing match on a stage.

Round 1: Block the obvious fake email domains.

Round 2: Stop the attacker from abusing our partners through newly created accounts.

Round 3: Finally fix the root problem and move account creation behind our own API.

The biggest lesson for me is simple:

Never leave an exposed cut and trust your luck.

We could have applied the root fix on the first day instead of letting the battle last for 10 days.

And today, with AI making automation dramatically easier, I think this lesson is even more important.

  • Attackers can move faster.
  • They can try more variations.
  • They can automate more of the boring work.

So we need to assume that if an attack surface exists, sooner or later someone will find it.

Security is not about hoping nobody notices the door is open. It’s about closing the door once you know it is.