Advanced

Injection beyond the page

I escaped HTML but passed the same text to a shell and to SQL. Every sink has its own escaping, and a value that reaches two sinks needs two guards.

What you will see

value is one argument, ; and | are literal

About 2 min of reading

Try It

Guard the same value at two different sinks.

1

Bind the value as a SQL parameter so it is data, not code.

cur.execute('SELECT name WHERE q = ?', (user_q,))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
value treated as data, no clause can be injected
2

Pass the value as an argv element so no shell parses it.

subprocess.run(['grep', user_q, 'file'])

Before you run it, say out loud what it will print. Then open the answer.

What it prints
value is one argument, ; and | are literal
Do not build a shell string like 'grep ' + user_q. A value with ; or | becomes commands. The argv list form never invokes a shell, so the value cannot break out.

Data Flow

One value reaches several sinks (HTML, SQL, shell); each sink needs its own guard, and data-passing APIs (bind, argv) remove the injection door.
1 Value reaches many sinks
↓
2 HTML escape only guards HTML
↓
3 SQL needs bind, shell needs argv
↓
4 Guard at each sink
Why this works
Escape at the sink

A value is safe for one sink and unsafe for another. HTML escaping does not protect SQL, and neither protects a shell. Guard each place the value is consumed, at that place, with that sink's tool (bind, arg list, escape).

Prefer APIs over strings

Where a sink has an API that takes data (bind parameters, an argv list, a DOM setter), use it instead of building a string. String building is where injection hides; the API removes the door entirely.

Examples

The SQL door

cur.execute('SELECT ' + name)   # name can close the quote

Splicing text into SQL lets it end a string and add clauses. Use a bound parameter (?) so the value is data, never code.

The shell door

subprocess.run(['ls', path])   # list form, no shell

Building a shell string lets ; rm -rf run. Pass an argument list so nothing is parsed by a shell.

One value, many sinks

q = request.args['q']
render(html.escape(q))
cur.execute('... ?', (q,))

The same q feeds HTML, SQL, and a command. Guard each sink at the sink, not once at the top.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Handing over access safely
Previous Lesson
Handing over access safely
Next Lesson
Rotating a live secret
Rotating a live secret →