The OWASP Top 10 reads like an abstract list when you first encounter it. Injection, broken authentication, cross-site scripting. Sounds like stuff that happens to other people's code. Then you start doing code reviews and realize these vulnerabilities are everywhere. I've seen every single one of them in real codebases.
Here are the ones that show up most often, with real patterns I've caught.
SQL Injection: Still Happening in 2022
I thought SQL injection was a solved problem. Parameterized queries exist. ORMs exist. How is anyone still concatenating user input into SQL strings?
Then I reviewed a codebase where the search feature built queries like this:
query = f"SELECT * FROM products WHERE name LIKE '%{search_term}%'"
Classic. The developer probably wrote it quickly, planned to "fix it later," and never did. A single apostrophe in the search field would break the query. A crafted input could dump the entire database.
The fix is always the same: parameterized queries. Every database library supports them. Every ORM uses them by default. There is zero reason to build SQL strings with user input. If I see string concatenation anywhere near a SQL query in a code review, it's an instant rejection.
Cross-Site Scripting (XSS)
XSS is sneaky because it often appears in code that "works fine" during normal testing. You only see the problem when someone enters <script>alert('xss')</script> as their username.
I've seen reflected XSS in search pages that echo back the search term without encoding. I've seen stored XSS in comment systems, profile fields, and even admin dashboards. One particularly bad case was a support ticket system where the ticket description was rendered as raw HTML. An attacker could submit a ticket with malicious JavaScript that would execute when a support agent viewed it.
The fix: always encode output. React does this by default (unless you use dangerouslySetInnerHTML, which is aptly named). In server-rendered templates, use the auto-escaping features your template engine provides. If you need to allow some HTML, use a sanitization library like DOMPurify. Never write your own HTML sanitizer.
Broken Authentication
This one covers a lot of ground, but the patterns I see most often are:
No rate limiting on login endpoints. I reviewed an API where you could hammer the login endpoint thousands of times per second with no throttling. Brute forcing a weak password would take minutes.
Session tokens that don't expire. I've seen JWTs with no expiration claim, meaning they're valid forever. If a token gets leaked, the attacker has permanent access. Always set a reasonable expiration and implement token refresh.
Password reset flaws. One app I reviewed sent a password reset link with a sequential numeric token. You could reset anyone's password by guessing the next number in the sequence. Reset tokens need to be cryptographically random and single-use.
Broken Access Control
This is the number one item on the 2021 OWASP list, and I understand why. I see it constantly.
The most common pattern: an API endpoint that checks if you're logged in but doesn't check if you're authorized to access the specific resource. Something like GET /api/users/123/invoices that returns invoices for user 123, but any authenticated user can change that ID and see other people's invoices.
This is called an Insecure Direct Object Reference (IDOR), and it's embarrassingly common. The fix is straightforward. Every request that accesses a resource needs to verify that the authenticated user has permission to access that specific resource. Not just "is the user logged in" but "does this user own this data."
Security Misconfiguration
Debug mode in production. Default credentials on admin panels. Verbose error messages that leak stack traces and internal paths. Directory listing enabled on web servers. CORS set to allow all origins.
I once found a production application with Django's DEBUG = True still enabled. Every error showed the full stack trace, all environment variables (including database credentials), and the complete source code of the failing view. That's not a vulnerability, that's handing an attacker a map to your entire system.
The Pattern
Most of these vulnerabilities share a root cause: the developer was focused on making things work and didn't think about how things could be abused. I get it. Deadlines are real. But security isn't something you bolt on later. The time to think about injection is when you write the query. The time to think about access control is when you write the endpoint.
My rule now: for every feature I build, I spend five minutes thinking about how someone could misuse it. What happens if the input is malicious? What happens if the user isn't who they claim to be? What happens if they try to access something they shouldn't? Five minutes of adversarial thinking catches most of these issues before they ship.