October 23, 2026
OWASP Top 10 Explained with Lab Examples
OWASP Top 10 Explained with Lab Examples
By Nitesh pachar
5 min read
The OWASP Top 10 gets mentioned constantly in cybersecurity learning paths, but for a while it was just a list of names to me β SQL Injection, XSS, Broken Access Control β without much feel for what they actually look like in a real application. Over the past couple of weeks I went through the theory, then spent time in TryHackMe rooms and PortSwigger's Web Security Academy actually triggering these vulnerabilities myself. This post walks through three categories I practiced hands-on β Broken Access Control, SQL Injection, and Cross-Site Scripting β plus two more I've studied in theory but haven't lab-tested yet.
One thing worth saying up front: the OWASP Top 10 is an awareness document, not a complete checklist. It highlights the risk categories that show up most often and cause the most damage β it doesn't cover every possible vulnerability a real application could have.
Broken Access Control
This category covers what happens when an application fails to properly check whether a user is allowed to do something β as opposed to just checking who they are. Three PortSwigger labs made this very concrete:
- Unprotected admin functionality β the admin panel simply had no authorization check on it. If you knew (or guessed) the URL, you were in.
- Unprotected admin functionality with an unpredictable URL β this one tried to be clever by using a random-looking admin path instead of
/admin. It didn't matter: the URL was disclosed elsewhere in the application (like in a JavaScript file or a robots.txt-style leak), so "security through obscurity" bought nothing.
- User role controlled by request parameter β the application decided whether you were an admin based on a cookie value the client could justβ¦ edit. Changing that one value was enough to unlock the admin panel.
The common thread across all three: client-side hiding is not access control. A hidden URL, an obscure path, or a cookie the browser controls are not substitutes for the server checking, on every single privileged request, "is this specific user actually allowed to do this?"
SQL Injection
SQL Injection happens when user input gets stitched into a SQL query in a way that lets the input change the query's actual logic instead of just supplying a value. I worked through this in two stages: a TryHackMe room for the fundamentals, then three PortSwigger Academy labs to go deeper.
- Retrieving hidden data via the WHERE clause β a classic case where unsafe query construction let me pull back data the application intended to keep hidden.
- Login bypass β the authentication query itself was vulnerable, which meant the SQL logic behind "is this password correct?" could be manipulated directly. This one made the stakes click for me: this isn't just about leaking data, it can mean walking straight past a login screen.
- Filter bypass via XML encoding (Practitioner-level) β a stock-check feature filtered input, but the filter alone wasn't a real defense. Encoding the payload differently was enough to get past it and use a UNION-based technique to pull data from other tables.
The third lab was the most useful lesson of the three: filtering and encoding tricks are not the fix. They're a patch on top of the real problem, which is unsafe query construction. The actual fix is parameterized queries / prepared statements, so user input is always treated as data and never as part of the SQL syntax itself. Input validation and least-privilege database accounts are good supporting controls, but they don't replace that core fix.
Cross-Site Scripting (XSS)
XSS is about untrusted input ending up somewhere it can be interpreted as active code in someone else's browser, instead of as plain data. I went through both the general theory (reflected vs. stored vs. DOM-based) and then three specific PortSwigger labs:
- Reflected XSS, nothing encoded β a search feature took the query and reflected it straight back into the HTML response with no encoding. If the browser treats that reflected input as markup rather than text, you get script execution.
- Stored XSS, nothing encoded β same root problem, but the unsafe content gets saved by the application first and served to other users later, which makes it more dangerous since it doesn't require tricking someone into clicking a crafted link.
- DOM-based XSS via
location.searchintodocument.writeβ this one was the most interesting because there's no server involved in the vulnerable step at all. Client-side JavaScript read directly from the URL's query string (location.search) and wrote it into the page usingdocument.write, with no safe handling in between. The source (attacker-controllable URL data) and the sink (document.write) were connected without any sanitization.
The defensive pattern across all three is the same: context-aware output encoding, and on the client side, preferring safe DOM APIs like textContent over anything that can interpret a string as HTML/script. A Content-Security-Policy header helps as a second layer of defense, but it's not a substitute for actually encoding output correctly in the first place.
Security Misconfiguration & Vulnerable/Outdated Components
- Security Misconfiguration covers things like default credentials left in place, debug mode enabled in production, directory listing exposed, or overly broad permissions. The fix is largely about "least functionality" β only enabling what the system actually needs β and reviewing configuration baselines regularly.
- Vulnerable and Outdated Components covers the risk of running libraries or frameworks with known vulnerabilities, or versions that no longer get security patches. An important nuance I picked up here: an old version isn't automatically a vulnerable one β what matters is whether it has a known, relevant vulnerability and whether it's still supported.
I'll follow up with hands-on labs for these once I get to them in the plan, rather than claiming practical experience I don't have yet.
What This Taught Me
A pattern shows up across almost everything above: trusting the client is the root cause more often than not. A hidden URL trusted to stay hidden. A cookie trusted to hold the "real" role. A filter trusted to catch every bad input. A query trusted to only ever receive well-behaved data. Nearly every lab came down to the server trusting something it shouldn't have β and the fix, almost every time, was to move the actual check to the server side and stop relying on anything the client could see or control.
All of this was practiced in authorized environments only β TryHackMe and PortSwigger's own Academy labs, both of which exist specifically for this kind of practice. None of these techniques get pointed at anything outside an authorized lab or a system I own.