Self-Check Your Website's Security with the OWASP Top 10 (2025)

The ten OWASP Top 10 (2025) items explained for business owners and developers: what each means, how to test with two accounts, plus an automated check script that was run.

Phát LâmPublished Updated 6 min readIntermediate
Self-Check Your Website's Security with the OWASP Top 10 (2025)
On this page
  1. What the OWASP Top 10 is, and why the 2025 edition
  2. Before you start
  3. How to do it: the ten items
  4. A01. Reaching what is not yours (Broken Access Control)
  5. A02. Misconfiguration (Security Misconfiguration)
  6. A03. Vulnerable dependencies (Software Supply Chain Failures)
  7. A04. Weak encryption (Cryptographic Failures)
  8. A05. Injection
  9. A06. Design without safety in mind (Insecure Design)
  10. A07. Weak sign-in (Authentication Failures)
  11. A08. Software or data changed along the way (Software or Data Integrity Failures)
  12. A09. Logging and alerts (Security Logging and Alerting Failures)
  13. A10. Handling errors the wrong way (Mishandling of Exceptional Conditions)
  14. For those who write code: automate the easy parts
  15. Mistakes when checking yourself
  16. What to do next

Plenty of people hear "security check" and picture hiring an outside team: expensive, and a long wait. In practice, most of the problems I find in my own apps come out of a checklist run steadily at every release. This post is that checklist, written so a business owner knows what to ask the developer, and so a developer has a ready way to test each item.

A ring split into ten segments with a teal scanning beam, most checked, two flagged amber
The ten OWASP Top 10 categories, scanned one by one.

What the OWASP Top 10 is, and why the 2025 edition

OWASP is a non-profit focused on web security. Every few years it publishes the ten most common groups of weaknesses, called the Top 10, and the whole industry uses it as a frame for reviews. Before writing this I opened the official page: OWASP Top 10:2025 is the current edition, replacing 2021. Many tutorials online still follow the old one. What changed: software supply chain failures is a new item (A03); mishandling of exceptional conditions is also brand new (A10); the weakness where a server is tricked into fetching an address chosen by the user (SSRF) is no longer its own item and is folded into A01; and misconfiguration jumped from fifth place to second.

Before you start

Only test what is yours, or what you have written permission to test. Use a test environment with fake data and two test accounts (call them A and B) with different permissions. Every check below I ran against a sample app I wrote for this purpose, deliberately flawed, running on my own machine.

How to do it: the ten items

A01. Reaching what is not yours (Broken Access Control)

The most common problem: the "view order" button is hidden in the interface, but the API still returns data to anyone who calls it. To test, sign in as account A, take B's order ID and call the API directly; open admin pages while signed out. The right answer is 403 or 404, never 200. For any feature like "paste a link and the app downloads the image" (where SSRF lives), allow only domains from an allow-list and block internal addresses.

A02. Misconfiguration (Security Misconfiguration)

Debug mode left on, /.env or /.git downloadable, error pages printing server paths, default accounts never changed, security headers missing. I wrote up the web server side separately in Making Your Nginx Website Safer, Step by Step.

A03. Vulnerable dependencies (Software Supply Chain Failures)

Your app stands on hundreds of packages written by others. Keep the lockfile in git, run npm audit or composer audit regularly, turn on two-factor sign-in for your package registry accounts, and give CI the least permissions it needs. I ran npm audit and composer audit on a test folder and found no known vulnerabilities. pip-audit in a test virtual environment flagged the old pip itself, exactly the kind of finding you want to see for this item.

A04. Weak encryption (Cryptographic Failures)

Passwords must be hashed with a slow function such as Argon2id, scrypt or bcrypt, never MD5 or bare SHA-256. Sensitive data needs encryption with an integrity seal, and keys stay out of source code. Details in Encrypting Sensitive Data the Right Way with AES-GCM.

A05. Injection

Text typed by users gets glued straight into a database query, so a single apostrophe can change what the command means. The fix is parameterised queries. I ran this on in-memory SQLite: string concatenation with x' OR '1'='1 returned both users, while the parameterised version returned nothing.

user_input = "x' OR '1'='1"
db.execute(f"select name from users where name = '{user_input}'")    # concatenated: returns alice and bob
db.execute("select name from users where name = ?", (user_input,))   # parameterised: returns nothing

A06. Design without safety in mind (Insecure Design)

Not a coding slip but a missing mechanism: a discount code that can be reused forever, a password reset with a guessable code, no limit on attempts. To check, sit down and draw the business flow, ask "what if someone abuses this on purpose", and turn each scenario into a test case.

A07. Weak sign-in (Authentication Failures)

Limit wrong attempts, offer two-factor sign-in, change the session ID after login, and make password reset links single-use and time-limited. If you use JWT, see A Shared JWT Login Token for Node.js, PHP, Python and C#.

A08. Software or data changed along the way (Software or Data Integrity Failures)

Scripts loaded from a CDN should carry an integrity attribute so the browser verifies them. Do not turn untrusted data back into live objects in your code. Your deployment pipeline needs control over who may change it.

A09. Logging and alerts (Security Logging and Alerting Failures)

Record failed logins, permission errors and permission changes. Keep passwords and tokens out of the logs. Most important, make sure a real person receives the alerts, because a log file nobody opens is as good as none.

A10. Handling errors the wrong way (Mishandling of Exceptional Conditions)

When something breaks, the system should close the door, not open it. The classic example: a permission check crashes and an except block swallows the error and lets the request through.

def can_view(user, doc):
    try:
        return policy_service.check(user, doc)
    except Exception:
        logger.exception("policy check failed")
        return False        # on error, deny, never return True

For those who write code: automate the easy parts

Machines can check some items. I wrote a small script (Python standard library only) that calls a few addresses and checks 13 things: five security headers, cookie flags, the /.env file, whether the 500 error page leaks paths, and a cross-account read of an order between two accounts.

st, _, _ = get('/api/orders/2', {'Authorization': 'Bearer tok-alice'})   # bob's order
check(st in (403, 404), f"alice cannot read bob's order (HTTP {st})")

I re-ran it today on both versions of the sample app. The deliberately weak one: 13 FAIL. The fixed one: 13 out of 13 PASS, and another user's order returns 404. For SSRF, I wrote a URL checker (must be https, domain on the allow-list, every resolved IP must be public) and tried five cases: only the single valid case passed, while http, an unknown domain, a domain pointing at 127.0.0.1, and the metadata address 169.254.169.254 were all blocked.

Mistakes when checking yourself

I often see people click around the interface without ever calling the API directly, or test only with an admin account and so never see a permissions bug. Some run the list once and leave it, though it only has value inside the release routine. And automated scanners do not understand your business, so they almost never catch A01 and A06. Rely on them alone and you miss the most serious problems.

A clipboard-style checklist with tick boxes and a magnifier over one row
The checklist belongs in the release process, not in a one-off review.

What to do next

Pick one afternoon, set up two test accounts, go through these ten items on a test environment, note which ones fail, and order the fixes by severity. Then put the list into every release. Once your app passes the items above, the next step is having someone run a proper, permitted test, which I describe in Testing Your Own Website's Defenses With Permission: The Basics.

Audit OWASPBảo mật full-stackPythonowaspowasp-top-10-2025auditchecklistappsec

References

  1. OWASP Top 10:2025
  2. OWASP Top 10:2025 - Introduction (what changed)
  3. OWASP Web Security Testing Guide
  4. OWASP Cheat Sheet Series
  5. OWASP SSRF Prevention Cheat Sheet