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:
ZeroWidth - 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.0user-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.