Beyond FunctionalLesson 2 of 7
Bahasa IndonesiaSecurity testing for QA
The OWASP Top 10 through a tester's eyes, with checks you can run today.
16 minBeyond Functional
Read this part first
Everything in this lesson is for systems you are authorised to test. Before any of it:
- Written authorisation. A verbal "sure, go ahead" is not it. Scope in writing, naming the environments.
- Only the environments named, never production unless it is explicitly in scope with a window and someone watching.
- No third parties. If your application talks to a payment provider, their systems are not in scope, ever.
- No destructive payloads, no real customer data, no denial of service.
- On a real finding: stop, document, report through the security channel. Do not keep digging to see how far it goes. Proving the door is unlocked is the job; walking through it and taking inventory is not.
T2's non-functional lesson set out these rules and they are not softer here. A tester who probes access control without them is not being thorough, they are being fired.
Why testers find these bugs
You are not going to out-tool a penetration tester. What you have instead is better than a tool: you know what the application is supposed to do, and who is supposed to be able to do it.
Most of the highest-impact security defects are not exotic. They are business logic and authorisation failures — a user reaching another user's data, a step that can be skipped, a price that can be edited. Scanners are famously bad at those, because a scanner does not know that project 7 belongs to someone else. You do.
Broken access control: start here, always
The most common serious class, and the one a tester is best placed to find. Three checks, in order of yield:
1. Change the id in the URL.
/projects/108/settings ← yours
/projects/109/settings ← somebody else's
If that loads, you have found IDOR — insecure direct object reference — and it is a serious defect. Sequential ids make it trivial to enumerate.
2. Call the endpoint the UI hides. This is the automation track's argument made concrete: a hidden button is not a permission check.
curl -X DELETE https://app.example.com/api/v1/suites/s_123 \
-H "Authorization: Bearer $VIEWER_TOKEN"
A viewer token should get 403. If it gets 200, the permission exists only in the interface.
3. Change the role in the request, not the UI. A signup or profile-update
payload that quietly accepts "role": "ADMIN" is privilege escalation, and it
happens more than you would like.
A useful matrix, filled in by trying rather than by reading the code:
| Own record | Another user's | Another org's | |
|---|---|---|---|
| Admin | ✓ | ? | must be 403/404 |
| Member | ✓ | ? | must be 403/404 |
| Viewer | read | ? | must be 403/404 |
| Logged out | must be 401 | must be 401 | must be 401 |
Every cell is a test. Most teams have never filled one in.
The rest of the Top 10, as tester-sized checks
| Class | What to try | What you are looking for |
|---|---|---|
| Injection | ' OR '1'='1, '; -- in inputs, and in URL parameters | A database error, a changed result set, a stack trace |
| XSS | <script>alert(1)</script>, "><img src=x onerror=alert(1)> | Your input coming back executed rather than displayed |
| Auth failures | Old password after a change; session after logout; token after role change | A session that outlives the thing that authorised it |
| Security misconfiguration | /.env, /.git/config, /admin, default credentials | Anything reachable that should not be |
| Sensitive data exposure | Read the API response, not the screen | Password hashes, other users' emails, internal ids in a payload the UI never shows |
| Insecure design | Skip a step in a multi-step flow; replay a request | Business logic enforced only by the order the UI presents |
Two of those deserve more than a row.
Sensitive data exposure is where the UI lies to you most. A profile endpoint that returns the whole user record and lets the front end render three fields is leaking the rest to anyone who opens devtools. Read the response body, always — this is the api-automation lesson's habit turned toward a different question.
Insecure design is the one scanners cannot touch at all. Add an item to a cart, proceed to payment, and then change the quantity or the price in the request. Skip from step 1 to step 4 of a wizard. Replay a "confirm order" request twice. A checkout that validates the price only in the browser is a real and recurring defect, and no tool will find it because nothing about it is malformed.
Things worth checking that are not in the Top 10
- Rate limiting. Can you try 500 passwords? Request 1000 password resets?
- Error messages that distinguish "no such user" from "wrong password" — that is user enumeration, and the same 403-versus-404 argument the automation track made about not confirming a record exists.
- File upload. Does it accept a
.phpor.svg? Is it served back from the same domain? Is there a size limit? - Password reset tokens. Single use? Expiring? Invalidated when the password changes?
- Security headers, quickly:
Content-Security-Policy,Strict-Transport-Security,X-Content-Type-Options.
Tools help, but they are the smaller half
OWASP ZAP in passive mode is the cheapest useful thing: proxy your normal
exploratory session through it and read what it noticed. Dependency scanning
— npm audit, Dependabot, Snyk — belongs in CI, because vulnerable dependencies
are the most common way an application inherits a defect nobody wrote.
But treat all scanner output as findings to verify, not defects to file. False positives are the norm, and a QA who files thirty unverified scanner tickets teaches the security team to ignore them.
Reporting a security finding
Different from a normal bug report in three ways: channel, detail, and blast radius.
- Channel. A security bug in a public tracker is a disclosure. Use whatever private route exists; if none exists, that is itself worth raising.
- Detail. Exact request, exact response, the account and role used, and the minimum steps. "Auth is broken" is not actionable.
- Impact in plain terms. "Any logged-in user can read any other organisation's test cases by changing the id in the URL. Confirmed on staging with two accounts." That sentence gets a fix scheduled today.
And say what you did not do: how far you stopped, what you did not access. That reassures a security team that the finding is contained and demonstrates you worked inside the rules.
Where TestForge fits
The authorisation matrix above is a test suite, and it is one of the few security areas that automates cleanly — the api-automation lesson's viewer-token example is exactly this. Every cell is a request with an expected status code, they run in milliseconds, and they never go stale the way manual checks do.
Writing them as cases with a security tag means the run history answers the
question an auditor eventually asks: when did you last verify that a viewer
cannot delete a suite? "Every build since March" is a much better answer than
"we tested it once".
Next: contract testing — catching integration breakage between services without standing up a full end-to-end environment.
Check your understanding
3 questions. No account needed, nothing is sent anywhere but the grader.
1. Why are testers often better than scanners at finding the highest-impact security defects?
2. You change the id in a URL from /projects/108 to /projects/109 and another organisation's data loads. What should you do next?
3. Which of these checks are things a QA is well placed to run without specialist tooling?(choose all that apply)