← Writeups

Email bombing on WhiteMarket: racing the verification-resend limit

The resend-verification endpoint was rate limited, and it even returned 429 under load. But the limit was not atomic, so a parallel burst slipped a stack of emails through before the limiter caught up.

WhiteMarket (white.market) is a marketplace for gaming items. In profile settings, an unverified account can ask the site to resend its verification email. That action is rate limited, so a normal user cannot spam it. The limit, though, was not enforced atomically: firing many copies of the request at the same instant let a burst through at once, and each one sends a real email. The result is a mailbox flooded on demand. Reported to WhiteMarket on HackenProof, rated Low, and resolved.

The HackenProof report WHM-99 on the WhiteMarket Web program: state Resolved, severity Low, category Race Conditions, with M1S0 as the verified author
The report on the WhiteMarket Web program, WHM-99: Resolved, severity Low, category Race Conditions.

The idea in one line

A rate limit normally reads a counter to see how many requests you have made, and if you are under the cap it lets the request through and adds one. Those are two steps. If requests arrive one at a time, the counter is updated before the next is checked and the cap holds. If a pile of requests arrive at the exact same moment, they can all read the counter before any of them has incremented it, so they all pass. That is a race condition, and it is beaten by sending in parallel rather than by sending more.

What makes this one interesting

The limiter here is not absent. Under the parallel load it does eventually fire and return 429 Too Many Requests with a Retry-After. The point is that a meaningful number of requests already succeeded in the moment before it engaged. A limiter that reacts a beat late is still a limiter you can outrun.

The attack

  1. Find the resend action

    Log in and open profile settings at /me?tab=profile-settings. The account details panel has a Resend verification e-mail link.

    The WhiteMarket profile settings page showing the account e-mail field and the Resend verification e-mail link, with the email address blurred
    The resend action in profile settings. The email address is blurred.
  2. Capture the request

    Clicking resend sends a GraphQL call, POST /graphql/api to api.white.market. Send it to Burp Suite.

  3. Group it and fire in parallel

    Duplicate the identical request into a group of roughly a hundred tabs and use Burp's send group in parallel so they all leave together and land in the gap between the limiter's check and its update.

    Burp Suite sending a group of about 98 identical resend requests in parallel to api.white.market; the response shown is HTTP 429 Too Many Requests with Retry-After 8. Session cookie and authorization token are blurred.
    A group of ~100 identical requests sent in parallel. The limiter does engage and return 429 with Retry-After: 8, but only after a batch has already gone through. Session cookie and token are blurred.
  4. Check the inbox

    A single parallel batch delivered two dozen verification emails.

    A Gmail inbox row showing 24 grouped White Market email-verification messages received from one batch
    24 verification emails from WhiteMarket in one grouped thread, from a single burst.

Why it matters

There is no account takeover or data leak, which is why it is rated Low. The value to an attacker is spam and cost, on demand:

The fix

The counter has to be updated in the same atomic step as the check, so two requests can never both pass on the same allowance:

Key takeaways

  • A limiter that returns 429 is not proof the limit holds. If it fires a beat late, a parallel burst has already slipped through, so always test with parallel requests.
  • Check and increment must be a single atomic operation, or concurrent callers will split them.
  • Low severity still means a real, repeatable cost, spam and reputation vector for the target.
  • Edge rate limiting at a CDN is a useful second layer, but the fix has to live where the counter is.
Race Condition Rate Limit Bypass Email Bombing GraphQL
Share on X

More writeups