
sqli, xss, lfi, one root cause, trusting input
three bugs, one lazy habit
sqli, xss, and lfi get taught like they're three separate monsters you need three separate textbooks for. they're not. they're the same mistake wearing different hats: somewhere in the app, user input got treated like it was trustworthy, and it wasn't. once you see that pattern you stop memorizing vulnerability names and start seeing the actual shape of the problem, which is way more useful when you're the one defending a system.
sqli: input reaches the database
sql injection happens when whatever a user types gets glued directly into a database query instead of being treated as pure data. the database can't tell the difference between "the value the user typed" and "a command i should run," so if that input contains sql syntax, the database just... runs it. that's how you get login bypasses and full table dumps from a search box.
the fix isn't "sanitize the input harder." the real fix is parameterized queries, sometimes called prepared statements. the query structure gets locked in ahead of time, and user input gets passed in as a value that can never be interpreted as code, no matter what weird characters are in it.
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))
notice the input never gets string-concatenated into the query. that's the whole trick. if your codebase has any query built with string formatting or concatenation and user input touching it, that's your first thing to grep for.
xss: your input runs as script in someone else's browser
cross site scripting is the same root problem, different destination. instead of the database misreading input as a command, the victim's browser does. if an app takes user input, say a comment or a profile name, and renders it back to the page without encoding it, and that input contains a script tag, the browser just executes it. now you've got attacker code running with the permissions of whoever's viewing the page, which can mean stolen session cookies, hijacked accounts, all sorts of fun that isn't fun.
defense here is output encoding, done at the point where data gets rendered into html. most modern frameworks (react, vue, django templates) escape output by default now, which is genuinely one of the best things that happened to web security in the last decade. the danger zone is when developers explicitly opt out of that protection because "it's fine, i trust this field." it's never fine. also throw a content security policy on top as a second layer, so even if something slips through, inline scripts don't just fire freely.
lfi: the app loads a path you control
local file inclusion is the same trust problem applied to the filesystem instead of a database or a browser. an app takes user input and uses it to build a file path, then loads whatever's at that path. if there's no strict validation, an attacker can manipulate that path to walk outside the intended directory and pull system files, config files, logs, whatever the web server process can read.
include($_GET['page'] . '.php');
that one line is the entire vulnerability. no validation on what "page" can be, no restriction to a known safe list, just straight from the url into a file load. the fix is boring on purpose: use an allowlist of valid page names, never let user input directly form a filesystem path, and strip or reject anything with directory traversal characters before it gets anywhere near a file operation.
the pattern, one more time
every one of these bugs exists because a system let user input cross a trust boundary without being checked or neutralized first. the database trusted it as a command. the browser trusted it as html and script. the filesystem trusted it as a legit path. different technology, same failure to draw a line between "data" and "instructions."
if you want to audit your own app fast, go find every spot where user input touches a query, gets rendered to a page, or gets used to build a file path, and check each one against this: is this parameterized, encoded, or allowlisted? if the answer is no at any of those three spots, that's your exposure, fix it there, not after the fact with a filter you'll forget to update.
the takeaway
validate and parameterize everything. don't trust input based on where it seems to come from, trust it based on how your code handles it once it arrives. three classic vulnerabilities, one root cause, and one mindset shift fixes all of them.