Curious Pandas

When input becomes code

Cross-site scripting and SQL injection are one bug in two costumes - text from a user crossing over into code. Break a toy comment box and a toy search, then fix both without changing a character of the input.

15 min+40 XP for finishing

A web page is code with gaps in it: “Hello, ___”. A database query is too: “find the panda named ___”. The gaps are for data. The bug in this lesson is what happens when the code is built by gluing text together, so whatever fills a gap gets read with the same eyes as the code around it. If the person filling the gap types something that looks like code, it becomes code.

That one mistake has two famous names. In a browser it’s cross-site scripting (XSS): a visitor’s comment becomes JavaScript running in everyone else’s page. In a database it’s SQL injection: a search box rewrites the query it was meant to fill in.

Visual guideGlued in, or passed as data
Glued in, or passed as dataUser input glued into code text is read by the browser or database parser as part of the code, so it can run. The same input passed separately as a value is never parsed as code and stays data.user inputa comment, a nameglued into codeone stringparser reads allbrowser or databaseinput runsas HTML or SQLsent separatelya value, not codestays datashown or matchednever parsedGlued in, or passed as dataUser input glued into code text is read by the browser or database parser as part of the code, so it can run. The same input passed separately as a value is never parsed as code and stays data.user inputa comment, a nameglued into codeone stringparser reads allbrowser or databaseinput runsas HTML or SQLsent separatelya value, not codestays datashown or matchednever parsed
  1. Something arrives from outside. It might be ordinary text. It might be text shaped like code.
  2. The bug: it is pasted into the page or query as one long string. Nothing marks where the code ends and the input begins.
  3. The parser has no way to tell the two apart, so input shaped like code is run as code.
  4. The fix: keep the input in its own lane - text for the page, a parameter for the database. It is never parsed, so it can only ever be data.

A comment that runs

The comment box below shows what a browser builds from a comment, without ever running it: the page parses the comment in an inert document where scripts are switched off and nothing loads. Pick the image with a handler, then switch between the two ways a page can put a comment on screen.

A comment boxparsed, never run
What readers see
What the browser built from it

    innerHTML hands the text to the browser’s HTML parser. MDN’s security notes are precise about what follows: a <script> tag inserted this way doesn’t run, but that’s little comfort, because an <img> whose onerror attribute holds code does. The same comment glued into a page on the server is worse still: there, the <script> runs too.

    textContent never parses anything. Every character, angle brackets included, is shown as a character. The comment looks exactly as it was typed and can’t do anything.

    OWASP’s advice, in order: let your framework’s automatic escaping do its job (most templating systems escape by default), use safe sinks like textContent instead of innerHTML, and add a Content Security Policy as a second layer. It’s useful, but too easy to get wrong to be the main defence. Every framework also has an escape hatch, like React’s dangerouslySetInnerHTML, and that’s where these bugs live now.

    Check yourself+10 XP

    A page sets element.innerHTML = comment. A visitor's comment is <img src=x onerror=steal()>. What happens?

    A search that answers a different question

    The keeper search builds its query the quick way: glue the name between two quotes. Try an ordinary name, then each of the others.

    Find a keepertoy database, in this page
    Try:
    What the database receives

    ' OR '1'='1 closes the page’s opening quote early, adds a condition that is always true, and leaves a quote for the page’s closing quote to pair with. The database then answers a different question: every keeper, every role. Lin' -- uses --, SQL’s comment marker, to switch off the rest of the query.

    Then there’s O'Neil, who is a real visitor, not an attacker. Their apostrophe breaks the glued query too. Injection isn’t only a security hole. It’s a bug that real names trigger by accident.

    Switch to sends the name as a parameter. The query text is now fixed, with a ? where the value goes, and the value travels separately. OWASP’s primary defence is exactly this: prepared statements with parameterised queries, which “treat bound values as data rather than SQL syntax”. The attack string becomes a very strange name that matches nobody, and O'Neil just works.

    Check yourself+10 XP

    A search query is built as "... WHERE name = '" + name + "'". Which fix actually closes the hole?

    Check yourself+10 XP

    What do XSS and SQL injection have in common?

    Sources

    Finished reading?

    Missions and quizzes above earn their own XP. This marks the lesson done.