← Back to News
Security

How We Stopped an API Abuse Attack in Real Time

On the evening of 11 May 2026, an attacker using an AWS virtual machine systematically worked through every route on motco.co.uk, created throwaway accounts, attempted privilege escalation, and triggered paid data calls without paying. We caught it through real-time alerts from our internal monitoring and shipped fixes while they were still trying.

📅 11th May 2026📖 6 min read

How It Started

At 20:31, an unusual pattern appeared: Vehicle Image and Vehicle Specs calls hitting back to back on registrations we did not recognise. By 20:44 the approach had shifted entirely. The attacker began a sequential scan of every internal management route without any authentication, working through them one after another. They were mapping the API surface before deciding where to push.

Notably, one endpoint fired a successful response before the attacker had even created an account. It was a route that should have been better protected and was not yet on our radar. That gap was closed the same evening.

Account Creation and the Escalation Attempt

At 20:51:57, the attacker created an account using a disposable iCloud address with no name attached. Eight seconds later they were logged in. Their first authenticated request was an attempt to promote their own account to administrator level. It returned 403. They tried three more times in quick succession. Each time, 403. The endpoint validates the caller against a server-side allowlist held in environment variables. There is no request that can change that list.

They continued probing delivery and notification endpoints. At 21:19:41 they registered a second account using a ProtonMail address, chosen for anonymity, and ran the same escalation attempts again with the same result.

Their origin IP was 44.211.187.32, belonging to an AWS cloud server. This was not a home connection. They had spun up a virtual machine to run the attack using a Node.js script, which their own tooling revealed through request metadata.

The Paid Data Calls

At 21:11:51, the attacker created a Stripe checkout session for a made-up registration. They never paid. The problem was that our paid data endpoints had no server-side payment check. Once they had a session URL, they called the data endpoints directly, skipping the payment step entirely.

Between 21:11:54 and 21:40:09 they triggered five separate data calls across our vehicle data services. Every one was charged to our account. They received nothing back that was useful to them and no report was ever generated or delivered.

How We Caught It

Real-time alerts from our internal monitoring flagged the pattern early: dozens of requests per minute, routes cycling in sequence, the same fake registration appearing across every paid endpoint. The Stripe session independently confirmed the attacker's IP and showed the payment had never been completed. They had created the session purely to understand the checkout flow, never intending to pay.

What We Fixed

We shipped fixes in order of damage potential while the attack was still in progress, each as a direct push to production.

  • Payment gate on all paid data endpoints. Every data endpoint now verifies a confirmed purchase record in the database before calling any upstream service. No record, no data.
  • Signups disabled. Both standard account creation and third-party OAuth new-account flows were blocked immediately. Existing users were not affected.
  • Rate limiting corrected. One endpoint was rate-limiting by the subject of the request rather than the origin IP. That meant thousands of variations could be called with no restriction. Switched to a sliding window per IP address.
  • Internal management routes hardened. Several routes were accessible to any authenticated user rather than administrators only. These were locked to admin accounts and a broken endpoint that was crashing on unexpected request methods was fixed to fail cleanly.
  • Scheduled jobs protected. Automated daily jobs are HTTP endpoints like any other. They now require a server-side secret to run, so they cannot be triggered manually by anyone who discovers the URL.

The two attacker accounts were identified by cross-referencing database creation timestamps with monitoring log times, accounting for a timezone offset between the two systems. Both matched exactly. Both accounts were deleted directly from the database.

The Ironic Twist

While working through routes in sequence, the attacker triggered our daily V5C logbook alert job, normally scheduled to run at 9:00 AM. They had no idea what it did. They hit it anyway.

That job queries DVLA records for vehicles being monitored by our users. On that particular evening, one watched vehicle had genuinely had its V5C logbook reissued earlier that day, which we confirmed on the DVLA website. The scheduled morning run had fired before the DVLA updated their records, so no alert was sent. By the time the attacker blindly triggered the job at 9 PM, the DVLA had updated. The job detected the change and sent the alert to the rightful owner.

An attacker attempting to abuse the platform accidentally delivered a legitimate customer notification around 12 hours earlier than it would have arrived the following morning.

What This Taught Us

Paid data endpoints must verify payment on every request server-side, regardless of what the client is expected to do first. A checkout flow in the browser is not a gate. Rate limiting must be keyed on the caller, not the subject. Scheduled job URLs are public routes and need authentication. And real-time monitoring is worth more than any after-the-fact audit when something is actively happening.

Final Score

The attacker triggered five paid data calls, received no reports, gained no admin access, and exposed no user data. Both accounts were deleted. All five vulnerabilities were patched within the same evening.

And one user got their V5C alert a few hours early.