0%

Input That Becomes Code

Data That Is Read as Instructions

Here is a title: <b>Dune</b>. Under the rules you wrote in Lesson 2 it is a valid title. It is text, it is 11 characters long, and nothing in the contract forbids an angle bracket. Run this block and look at what happens to it in two places:

Try it

The second line of output is the book as : {"title": "<b>Dune</b>", "author": "Frank Herbert"}. The title sits between two quotation marks, and whoever parses that JSON gets the same 11 characters back. It is data.

The third line is <li><b>Dune</b></li>. Nothing marks where the title starts or stops anymore. A browser that reads this line finds a <b> tag inside a item. The 11 characters did not change. What changed is who reads them, and that reader takes part of them as instructions.

The pattern

Every weakness in the rest of this chapter has the same three steps.

  1. Text from outside is joined into a larger text.

  2. A parser reads the result: a browser, an client, an , a database.

  3. The parser cannot tell which part was the data. It sees one text, and it reads all of it by its own rules.

This is called injection: data that was joined into a text gets read as part of that text’s instructions.

Chapter 1 put the trust boundary where a request comes in. Injection happens where a boundary is crossed a second time, on the way out: where your server hands text to something else that will parse it.

Four places

Here are four fragments. Each has one line where data crosses into text that will be parsed.

A database query. The database parses the result as SQL:

# title is joined into SQL
query = f"SELECT author FROM books WHERE title = '{title}'"

A . The client parses the head of the response line by line:

# target is joined into a header
line = "Location: " + target + "\r\n"

A file path. The operating system reads / and .. in it as directions:

# name is joined into a path
cover = pathlib.Path(folder) / name

A page. The browser parses it as HTML:

# title is joined into markup
row = "<li>" + title + "</li>"

Your reading list has the last three since this chapter began. To see the second one at work, the next block builds the head of a redirect as plain text, the way Chapter 2 of HTTP with Python wrote requests by hand. The title in it holds a line break:

Try it

A client splits the head at every \r\n, as the does, and the brackets show where each line starts and stops. The server wrote two lines, a status line and a Location line. The client reads three, and the third one, [Set-Cookie: note=x], came out of the title. The empty pairs of brackets at the end are the blank line that ends the head, and the empty text after it.

A server builds a header with line = "Location: " + target + "\r\n". Where does data become instructions?

The same loop gets a title with a blank line in it. Which line does it print third?

title = "Dune\r\n\r\n<p>hi</p>"

head = "HTTP/1.1 303 See Other\r\nLocation: /books?title=" + title + "\r\n\r\n"

for line in head.split("\r\n"):
    print("[" + line + "]")

A first look at SQL

The first fragment is the best known of the four. It is called SQL injection, and this is the whole of it:

query = "SELECT author FROM books WHERE title = '" + title + "'"

A title with a single quote (') in it ends the quoted text early, and whatever follows the quote is read as SQL. The repair is to stop joining. The query keeps a placeholder, the ?, and the travels beside it:

cursor.execute("SELECT author FROM books WHERE title = ?", (title,))

Now the database gets the query and the value as two separate things, and it never parses the value as SQL. Nothing in this course runs SQL, so this is a first look and no more. The course SQL and Databases with Python teaches placeholders properly.

Two more places have the same pattern, and this course only names them. Text from outside that is joined into a shell command is parsed by the shell. And Python’s pickle can run code while it loads pickled bytes, so bytes from outside are never unpickled.

Validation is not the fix

The title was valid. That is the uncomfortable part.

It is tempting to go back to validate_book and refuse < in titles. That breaks honest titles that contain the character, and it still misses the other places. The header was attacked with a line break, not with an angle bracket. The path will be attacked with ... A validator would have to know every place a value might ever be written to, and every character that means something there.

validate_book accepted the title <b>Dune</b>. Why is it still a problem in the page?

So the defense belongs at each place where the text is used, in the code that knows what it is writing. It is a different defense in each place, and the next four lessons write one each:

Where the text goesThe defense
A header valueRefuse a value with a line break
A redirect targetAllow only one shape: a path on this site
A file pathResolve the path, then compare it with the folder
A pageEncode the characters that mean something in HTML

Where does the defense against a title that becomes markup belong?

Validation still matters: it decides what may be stored. It does not replace the work at the other end. Validate on the way in, guard or encode on the way out, and never let one stand in for the other.

The blocks on this page only build text. No header is sent here and no file is opened. In the next lesson your own server writes the header, and a request decides what is in it.