Back to blog

GOTROOT / Penetration Testing

Where Web Apps Actually Break: The Same Few Places, Every Time | GOTROOT

There are many checklists, but apps break in the same few places. Beyond authorization, input, session, and logic, we also look at known flaws in the components an app leans on — with real request/response examples of what scanners catch versus what a person must check.

GOTROOT Research Team Jun 5, 2026

Test web apps long enough and one thing becomes clear: there are hundreds of vulnerability “types,” yet incidents happen in the same few places. So instead of listing every item, this is an honest note on the spots we end up re-checking on almost every engagement. We hope it helps as you prepare.

What scanners catch, what a person must check

First, scanners are far from useless — for pattern-clear flaws like reflected XSS or known misconfigurations, they’re faster and more thorough than a human. The context-heavy parts are where a person helps.

Scanners do well

A person should check

Reflected XSS, known CVEs, missing headers

Access control (IDOR/BOLA) — “should this be allowed?”

Directory exposure, default credentials

Business logic — payments, coupons, step-skipping

Flagging an “old library version”

Whether that version is actually exploitable here

The spot we revisit most: access control

In our experience the most common and quietly dangerous place. Login is locked down, but “after” login no one checks whether this person may see this data. With two accounts we compare whether changing one identifier reveals someone else’s information.

// With account A's session, request account B's invoice
GET /api/invoices/10024 HTTP/1.1
Cookie: session=<user A>

// If this comes back, it's broken access control (should be 403/404)
HTTP/1.1 200 OK
{ "id": 10024, "owner": "user_B", "amount": 480000, ... }

It’s not only numeric IDs — even “unguessable” UUIDs often leak via other screens, logs, or enumeration, so identifier randomness is no substitute for authorization.

Where input becomes code

We look at where untrusted input is interpreted as a query, command, or request. Reflected cases are caught by automation, but stored, blind, and second-order injections often need hands-on checks. Below are inputs we commonly use to confirm.

# SQLi (blind boolean — watch the response change)
id=10 AND 1=1      → normal
id=10 AND 1=2      → response differs → injectable

# Stored XSS (does input execute on another user's screen?)
<img src=x

# SSRF (coax the server to call internal targets — critical in cloud)
url=http://169.254.169.254/latest/meta-data/

Session and business logic

For sessions we check token entropy, logout/expiry handling, cookie attributes (HttpOnly/Secure/SameSite), and CSRF defenses on state-changing requests. And the area scanners can never know is business logic — price tampering, negative quantity/discount, skipping approval steps, coupon-reuse races. We design those scenarios by hand.

Going deeper — the components we lean on

Even with clean code, one framework, library, or plugin the app runs on often becomes the way in — and that’s exactly where targeted attackers (APT) look. So we identify the components and versions in use, cross-check them against public advisories, and confirm within a controlled scope whether they’re actually exploitable here. The 1-day mindset — reading a fresh patch in reverse to reconstruct an exploit — lives here.

If you’re curious about that process, we wrote it up starting from patch diffing in How 0-days Are Found. The goal is to show not “the version is old” but “here’s how it’s exploited.”

Field note: the thing developers appreciate most in a report is the reproduction steps. Following the same request to see the flaw with their own eyes makes the fix noticeably faster.

FAQ

Isn’t a scanner enough?

For pattern-clear flaws, scanners are efficient. But context-heavy areas — access control, business logic, chaining — need human verification for real confidence.

How often should we test?

An annual baseline plus targeted tests when auth or major features change — change is exactly when new gaps appear.

Closing

A good test isn’t about filling a checklist; it’s about prioritizing the places that actually break. If you’d like to find out together how far your service holds, feel free to ask us at penetration testing.