What Is a Vulnerability, Really? Source, Sink, and Taint
DEV Community

What Is a Vulnerability, Really? Source, Sink, and Taint

Two Java methods. One of them will let an attacker delete your entire products table. The other is completely safe. public int deleteA(HttpServletRequest request, Connection conn) { String id = request.getParameter("id"); String sql = "DELETE FROM products WHERE id = " + id; return conn.createStatement().executeUpdate(sql); } public int deleteB(HttpServletRequest request, Connection conn) { String id = request.getParameter("id"); if (!id.matches("[0-9]+")) { throw new IllegalArgumentException("id must be numeric"); } String sql = "DELETE FROM products WHERE id = " + id; return conn.createStatement().executeUpdate(sql); } Four lines different. If you can already see which is which and why, you know more security than most working developers. If you can't - that's what this article is for. By the end you'll be able to look at almost any injection vulnerability and describe exactly what's wrong with it, using three words. I'm building an automated vulnerability scanner, in public. Those three words are the entire foundation it's built on. The three words Nearly every common web vulnerability has the same shape: The program takes input from the user and uses it somewhere dangerous, without cleaning it first. Three terms capture that: SOURCE - where outside input enters your program. request.getParameter("id") reads something a user typed. Since anyone can be that user, you must assume the worst possible value. SINK - an operation that becomes dangerous with the wrong input. executeUpdate(sql) hands a string to your database and says "run this." TAINT - the idea that untrusted data stains everything it touches. Input arrives tainted. Copy it into a variable, that variable is tainted. Concatenate it into a bigger string, the whole string is tainted. The stain spreads. The tap and the glass Picture a tap that might be running dirty water - that's your source. Picture a glass you're about to drink from - that's your sink. Taint is the dirt. A vulnerability is when dirty water flows from the tap to the glass with no filter in between. That's it. That's the whole model. Now look at the two methods again: - deleteA - tap โ†’ glass, nothing in between. Vulnerable. - deleteB - tap โ†’ filter โ†’ glass. Thematches("[0-9]+") check rejects anything that isn't pure digits. Safe. What the attack actually looks like For deleteA , a normal request sends id=42 : DELETE FROM products WHERE id = 42 Fine. One product deleted. Now an attacker sends id=42 OR 1=1 : DELETE FROM products WHERE id = 42 OR 1=1 1=1 is true for every row. Your entire products table is gone. The attacker didn't break into anything. They typed text into a field you gave them. Your code took that text and made it part of a command. That's the thing worth sitting with: injection bugs aren't about breaking in. They're about your program treating a stranger's text as instructions. In deleteB , 42 OR 1=1 fails the [0-9]+ check and the method throws before any SQL is built. Same tap, same glass, but the filter catches the dirt. The same shape, four different bugs Here's why this model is worth learning: once you see it, four of the most common vulnerability classes collapse into one idea with different taps and glasses. | Vulnerability | The dangerous sink | What an attacker gets | |---|---|---| | SQL injection (CWE-89) | a database query | reads or destroys your data | | Command injection (CWE-78) | running a system command | runs any program on your server | | Path traversal (CWE-22) | opening a file by name | reads files they shouldn't see | | XSS (CWE-79) | writing into a web page | runs code in your other users' browsers | Different sinks, identical shape. Untrusted input reaches a dangerous operation with nothing neutralising it on the way. What's a CWE number? The Common Weakness Enumeration is a worldwide catalogue that numbers every type of software weakness. "CWE-89" means SQL injection everywhere on earth. It matters because it lets different tools, written by different companies, talk about the same bug - which is what makes the tool comparison later in this series possible at all. Path traversal, for example, is the same story with files: String name = request.getParameter("file"); File f = new File("/var/data/" + name); // sink Send file=report.pdf and you read a report. Send file=../../../etc/passwd and you walk up out of the directory and read the system password file. Tap, glass, no filter. Why this is hard to automate Here's where my project starts. A scanner can trace the path - that's mechanical. Follow the data from getParameter through every variable it touches until it reaches executeUpdate . If a path exists, flag it. That technique is called taint analysis and it's what most security scanners do. But run that on the two methods at the top, and it flags both. Because in deleteB , the data genuinely does flow from source to sink. The matches("[0-9]+") line doesn't break the path - id is still the same variable, still reaching the same query. What that line changes isn't the route, it's the meaning: after it, id can only be digits, so the attack is impossible. A path-tracing tool sees connectivity. It cannot see meaning. So real scanners face an ugly choice: hand-code a list of every function that counts as a filter (impossible to keep complete - every library has its own), or report the flow anyway and let developers sort it out. Most choose the second. That's why security tools have a reputation for crying wolf. Going deeper (skip if you just want the lesson) The industry's own test suite makes this concrete. The OWASP Benchmark is a set of Java test cases with known answers, used to grade scanners. In the four categories I detect, it contains 1,478 cases: 777 real vulnerabilities and 701 that are deliberately built to look vulnerable while being safe - exactly thedeleteB pattern, using real filters like numeric allow-lists, character stripping, and encoders.Forty-seven percent of the test set exists purely to punish tools that can't tell meaning from connectivity. That ratio isn't an accident - the benchmark's designers made exactly this distinction the core test. What my scanner does about it The approach I'm testing: let the mechanical part stay mechanical, and hand the meaning question to something that can read code. Fixed rules trace every path from source to sink - reproducibly, missing nothing, and deliberately over-reporting. Then a language model looks at each flagged snippet on its own and answers one question: is this actually exploitable? On my test project it flagged 6 candidates and the AI rejected 2 of them. Here's its verbatim reasoning on the deleteB -style case: "The input 'id' is validated against a numeric-only regex '[0-9]+' before being concatenated into the SQL query. This allow-list guard prevents any SQL injection characters from reaching the sink." That's the judgement the path-tracer structurally cannot make. Whether that holds up at scale - across 1,478 cases, measured against Semgrep and CodeQL - is what the rest of this series is about. What I learned 1. Most vulnerabilities are one sentence. Untrusted input reaches a dangerous operation without being neutralised. Learn to spot the tap, the glass, and whether there's a filter, and you can read most security findings without memorising bug categories. 2. "There's a path" and "there's a bug" are different claims. This trips up beginners and tools alike. The path is necessary but not sufficient. Always ask what happens to the data on the way. 3. The hard part of automation is never the searching. Tracing paths is mechanical. Deciding whether a filter actually filters is judgement - and that distinction shaped my entire project. Next in this series: what happened when I told the AI a scanner had flagged the code - and it agreed with everything. I'm Ali Afana - AI builder and security researcher, writing from Gaza. I build systems in public, measure them against ground truth, and keep the receipts. This scanner is one project on a longer road - follow for what comes next. Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.