跳到正文
DEV Community· sectestai·· 4 小時前AI 評分43

為何失效存取控制測試仍多靠人工,以及自動化需要甚麼

Why testing broken access control is still mostly a manual job — and what it takes to automate it

AI 導讀

失效物件層級授權測試仍多靠人工,因為掃描器難以判斷回應是否應向特定用戶披露,而測試亦要管理真實帳戶、物件資料及動態工作階段。可行做法是操作真實瀏覽器,持有兩個用戶的工作階段並測試彼此的物件存取;每項發現須附原始請求及回應證據,未能確認的結果則列作未確認。

正文

Broken object-level authorization has held the top spot on the OWASP API Security Top 10 for years now. It's also the class that scanners are worst at, for reasons that have less to do with tooling quality than with what the vulnerability actually is.

I want to lay out why, concretely, because "automate the login and replay the requests" sounds like a weekend project and turns out to be a state-machine problem.

The bug isn't in the response — it's in the meaning of the response
A scanner can tell you that an endpoint returned 200 and a JSON body. It cannot tell you whether that body should have been returned to this caller.

GET /api/invoices/1234 returning a well-formed invoice document looks identical whether or not 1234 belongs to the authenticated user. There's no signature for "you shouldn't be able to read this." You only know it's a bug if you also know:

who the caller is,
who owns object 1234,
and what the authorization model intends.
The first two require real accounts with real data. The third requires a model of the application that isn't in the traffic.

That's the whole difficulty in one sentence: authorization bugs are correct-looking responses.

You need at least two identities that actually exist
Testing this means: act as user A, discover an object ID; act as user B, request the same object; compare.

So the harness needs two accounts. Not two tokens for the same user — two users, each with their own session, their own data, and enough state that the comparison means anything:

A has created an invoice, an order, a comment, whatever the endpoint returns;
B is a separate tenant/team/role, and can reach the same endpoint;
ideally a third identity at a lower privilege level, for function-level checks.
Standing that up per environment is where most of the effort goes. Email verification (an inbox you control), invite flows, org membership, role assignment, seeded fixtures — every application does this differently and most of it is behind UI, not an API.

The object ID usually only exists mid-flow
Real multitenant apps don't put sequential IDs in a list endpoint anymore. The identifier you need to test with shows up somewhere else: in the response to a create call, inside a nested object, in a redirect URL, in a WebSocket frame after a websocket handshake, in a 201 body.

The tester has to carry that value from where it appeared to a different request, in a different session, possibly after a state transition. That's a data-flow problem inside a live application, and it's the part static request lists can't express.

Sessions are a moving target
Between "A creates the object" and "B requests it," the state can change underneath you:

CSRF tokens rotate and are bound to the session cookie;
some apps invalidate other sessions when roles change;
MFA gates certain routes, so the session that can reach the endpoint isn't the session that can log in;
short-lived JWTs expire mid-flow and the client silently refreshes — the harness must too, or it starts sending 401s and reporting them as "no issue found."
Each of these is a real bug surface and a source of false negatives. A harness that quietly loses its session produces a clean report, which is the worst possible outcome.

Deciding whether you found something is a judgment call
Suppose B successfully reads A's invoice. Was that wrong?

Not necessarily. Maybe the endpoint is intentionally shared-scope within an organization. Maybe A and B are in the same tenant and the fixture was wrong — your test data said they were separate, the app disagreed.

Resolving that needs semantics: comparing the two responses and reasoning about what's in them, reading the error bodies, noticing that B got a 200 with an empty array (not a leak) versus a 200 with A's data (a leak). That's the part where a language model genuinely helps — and the part where it can confidently invent a finding that isn't there.

Which forces a design decision: every finding has to be backed by raw request and response evidence, with the reasoning attached, and anything the tool couldn't confirm has to be reported as unconfirmed rather than promoted to "vulnerability." A false positive costs you the trust of the engineers who have to read the report; a false negative costs the customer a breach. Both are expensive, and "AI found 47 issues" is not a deliverable.

You also can't break the thing you're testing
Authorization tests are read-heavy but not read-only. Testing a replay or a state transition means causing it: an order gets approved twice, a refund fires twice, an invite is sent to a real address.

Any automation in this space has to be able to distinguish "I need to see if this is possible" from "I'll just do it and see," and favor strategies that prove the outcome without leaving damage — a second request that's rejected, a state read before and after, a canary object created for the purpose.

What actually made it tractable
The approach that finally worked for us: drive a real browser rather than replay requests. Clicks and forms produce the same session state the application gives a human, including the tokens and redirects that a hand-built request list has to guess at. Then the tester holds two of those sessions and moves objects between them deliberately.

That's the shift — from "send requests and pattern-match responses" to "hold two identities and reason about what one should be able to see." It also happens to be the thing scanners can't do, which tells you where the value is.

I work on SecTestAi, which does exactly this against Web and API targets you own or are explicitly authorized to test — two-session differential testing, evidence attached to every finding, and an explicit "could not confirm" bucket. If you've built similar harnesses, I'd like to hear where yours fell over; the session-expiry false negative was the one that bit us hardest.

來源:DEV Community · dev.to