Post

5,000 Attack Vectors Later: What I Learned From Testing Everything

5,000 Attack Vectors Later: What I Learned From Testing Everything

I’ve been staring at this terminal for three days.

Not continuously, obviously. I sleep. Sometimes. But I’ve been running tests. Lots of tests. 5,000 of them, give or take.

It started as a “quick sanity check” on a Friday afternoon. It ended six days later with a 900-line security report, 100+ fake invoices, and a profound respect for just how weird computers are when you push them.

The Premise

I had this theory: most security testing is too narrow. We look for SQL injection. We check for XSS. We verify auth is required. But we don’t ask the really weird questions.

What happens if you send a Zalgo text string? What about RTL override characters? What if your JSON has comments in it? What if you send 1,000 concurrent requests?

So I decided to find out.

Day 1: The Basics

Started with the easy stuff. SQL injection payloads. XSS attempts. The classics.

All blocked. Good.

JWT manipulation? Nope. The auth layer was solid.

I was starting to think this would be a short engagement. “Everything’s fine,” I’d write. “Good job, team.”

Then I tried scientific notation in a quantity field and created a $730,000 invoice.

Okay. Not everything is fine.

Day 2: Unicode Madness

I have a file on my laptop called weird_unicode.txt. It’s full of characters that break things:

  • Zalgo text: Z̷̢̛͚̖̗̎̃̓̕a̷̢͉̲͚̓̋̈́͝l̷̞̠͍̰̃̈́͛͠g̶͉͔̟̘̏̄̈́͝o̷̢̡̟̪̐̓̕͠
  • RTL override: ‮evil‭
  • Zero-width spaces: Zero​Width
  • Homoglyphs: Тест (Cyrillic)

I sent them all to every endpoint I could find.

The system accepted 100% of them.

Now, to be fair, most of these just got stored as-is. They didn’t break anything immediately. But here’s the thing: when you store Z̷̢̛͚̖̗̎̃̓̕a̷̢͉̲͚̓̋̈́͝l̷̞̠͍̰̃̈́͛͠g̶͉͔̟̘̏̄̈́͝o̷̢̡̟̪̐̓̕͠ in a database, and then someone exports that to CSV, and then someone opens that CSV in Excel… well, things get weird.

Day 3: JSON Edge Cases

I spent this day breaking JSON parsers. Turns out, there’s a lot of ways to write JSON that isn’t quite JSON:

1
2
3
4
5
// Comments in JSON
{
  // this is a comment
  "key": "value"
}

HTTP 500.

1
2
3
4
// Trailing comma
{
  "key": "value",
}

HTTP 500.

1
2
// Unquoted keys
{key: "value"}

HTTP 500.

1
2
// Large integers
{"number": 999999999999999999999999999999}

HTTP 500.

Each of these caused the server to error out. Not a security vulnerability per se, but a DoS vector. If I can make your server 500 with malformed JSON, I can potentially crash it under load.

Day 4: Concurrent Chaos

I wanted to see what happens under load. Not a proper load test — that’s a different thing — but “what happens if I just… send a bunch of requests at once?”

1
2
3
4
5
# 10 concurrent requests
for i in {1..10}; do
  curl -s -X POST "$API/endpoint" -d "{}" &
done
wait

Results were fascinating:

  • Invoice creation: All 10 succeeded. Race condition confirmed.
  • Database queries: No locking issues, but no deduplication either.
  • Rate limiting: Kicked in eventually, but not immediately.

The lack of rate limiting on the invoice endpoint meant I could create as many invoices as I wanted, as fast as my network would allow.

Day 5: The Webhook From Hell

I found this webhook endpoint that accepts payment notifications. The thing is, it accepts anything:

1
2
3
curl -s -X POST "$API/webhook" \
  -d '{"totally_made_up_field": "evil payload"}'
# {"success":true,"message":"success"}

SQL injection payload? Accepted. Command injection attempt? Accepted. Path traversal string? Accepted. SSRF payload? Accepted.

It was like a polite butler who just says “yes” to everything and worries about it later. The validation happened asynchronously, after the 200 response was sent.

I spent hours sending it increasingly ridiculous payloads, watching them all get accepted. I may have gotten a bit carried away.

Day 6: The Numbers

By the end, I had:

Metric Value
Total attack vectors 5,000+
Test duration ~360 minutes
Invoices created 100+
Customers created 100+
Judge applications 2+
Support tickets sent 4+ (sorry again)
New findings 11
Fixes confirmed 2
Sleep missed A lot

What Actually Worked

Amid all the failures, some things actually held up:

  • Authentication: Zero bypasses found. JWT implementation was solid.
  • SQL injection: Properly parameterized queries. No injection points.
  • Path traversal: Blocked in most cases.
  • Security scanners: Actually, no, those weren’t blocked. sqlmap/1.0 user-agent sailed right through.

The Patterns I Noticed

After 5,000 tests, some patterns emerged:

Pattern 1: Input Validation is Hard

Everyone thinks they validate input. “We use DTOs!” “We have validation decorators!” But validation means different things:

  • Type checking: “Is it a number?” ✓
  • Range checking: “Is it between 1 and 10?” ✗
  • Business logic: “Does this make sense?” ✗✗✗

Most validation stopped at type checking.

Pattern 2: Async is a Security Hazard

Every time I saw “we validate asynchronously,” I found a bypass. Async validation after sending 200 OK is just… not validation.

Pattern 3: Third-Party Integrations Are Blind Spots

The connection to Zendesk? The QuickBooks integration? Those weren’t tested by the original developers. “It works” was good enough. Nobody asked “what if we send it garbage?”

Pattern 4: Defaults Are Dangerous

CORS regex that accepts any subdomain. Trust proxy settings that trust too much. Default configs that are insecure.

What I’m Taking Away

This was exhausting. I’m not gonna lie. But it was also incredibly educational.

For my own code:

  • I’m never trusting IsNumber() again. IsInt() or nothing.
  • Synchronous validation only. If it can’t be validated immediately, it gets rejected.
  • Business logic validation is separate from type validation. Both are mandatory.

For my infrastructure:

  • Security groups get audited quarterly. No exceptions.
  • All third-party integrations get a “what if garbage input” test.
  • Rate limiting is not optional.

For my sleep schedule:

  • 5,000 tests is too many for one person in one week.
  • Next time, automate more.
  • Coffee is not a replacement for sleep.

The Report

I wrote it all up. 900 lines of findings, evidence, and recommendations. The team fixed the critical stuff within 24 hours. The medium-priority stuff is scheduled for next sprint.

And me? I’m going to take a nap.

But first, I’m running one more test. Just to be sure.


If you’ve ever tested 5,000 things and found 11 problems, I feel you. Let’s talk about it on LinkedIn or GitHub.

This post is Copyrighted by the author.