Injection

Learning Objectives

Injection Attacks

Injection vulnerabilities are present when user input can be interpreted as code without sanitization.

SQL Injection

A common case is SQL injection. Take the following (poorly-written) endpoint for an example:

app.post("/users", (req, res) => {
    console.log(req.body.username);
    db.exec(
        `INSERT INTO users(username) values("${req.body.username}");`,
        (err) => {
            console.log(err);
            res.sendStatus(201);
        }
    );
});

The code takes the user input from the request’s body and plugs it directly into the SQL statement that gets executed. However, a malicious user could exploit this by carefully constructing a string to enter into the username text input:

Input box with 'charlie"); DROP TABLE users;' as input

app.post("/users", (req, res) => {
    console.log(req.body.username); // charlie"); DROP TABLE users;
    db.exec(
        `INSERT INTO users(username) values("${req.body.username}");`,
        (err) => {
            console.log(err);
            res.sendStatus(201);
        }
    );
});

The resultant SQL that gets executed would therefore be:

INSERT INTO users(username) values("charlie"); DROP TABLE users;");

This would insert the username but then drop the entire table!

Cross-Site Scripting (XSS)

Another common form of injection is Cross-Site Scripting (XSS). When a browser encounters a <script> tag, it executes the code inside automatically. Cross-site scripting relies on this behaviour. We’ll illustrate this with an example.

Let’s imagine we’re creating an app that allows users to enter their name to sign-up for an event. When they enter their name, it is sent to a server and stored in a database. When the admin visits the site, the following code is run to fetch the users from the database and render them to her browser.

// app.js
app.get("/", async (req, res) => {
    let guests = await getGuests(); // fetches guest names from database
    res.render("guests", { guests });
});
{{!-- guests.handlebars --}}
<h1>Guest List</h1>
{{#each guests}}
    <li>{{{this}}}</li>
{{/each}}

This handlebars code simply loops through each string in the guests array and sets it as the inner-html of an <li> tag.

Given normal user input, this app works fine:

normal HTML displayed

However, if a user was to enter their name as “<mark>Gav</mark>”, the following would be rendered:

Gav list item displayed with yellow highlighting

A user could also input their name as: "<script>alert('You have an XSS vulnerability')</script>" which would create a popup alert when the admin opened the page. However, much more sinister actions can be performed once the hacker can inject scripts into another user’s browser. For example, the script could redirect the user to a malicious website; manipulate information shown on the screen; or make requests on the user’s behalf (exploiting any session tokens stored in the user’s browser).

Preventing Injection

Modern libraries - like sqlite3 and handlebars - are designed to protect against injection. They do this by providing a parameterized interface.

A parameterized interface means that, when constructing some code like an SQL query or an HTML page, user input is explicitly marked out in little slots. The text in these slots is then escaped such that the interpreter knows to treat it simply as text rather than code.

To fix the above handlebars example, we need to use the double curly-bracket syntax {{ }}, rather than the triple {{{ }}}. The double-bracket version is safer to use with user input because it escapes any special characters.

If we change our template to:

{{!-- guests.handlebars --}}
<h1>Guest List</h1>
{{#each guests}}
    <li>{{this}}</li>
{{/each}}

It will now output the following

<h1>Guest List</h1>
<li>Charlie</li>
<li>Bernard</li>
<li>Mandy</li>
<li>Dan</li>
<li>&lt;mark&gt;Gav&lt;/mark&gt;</li>
<li>&lt;script&gt;alert(&#x27;You have an XSS vulnerability&#x27;)&lt;/script&gt;</li>

Notice how the characters which have special meaning in HTML have been replaced. For example, the < symbol has been replaced with &lt;.

When the browser renders this HTML, it converts the escaped characters back to their normal form:

html tags appear in the rendered text

So the name appears exactly as the user typed and is treated as text rather than code.

When writing SQL queries, we mark the slots where our data goes in our query and pass our parameters in separately:

db.run(
    `INSERT INTO users(username) values(?);`,
    [req.body.username],
    (err) => {
        console.log(err);
        res.sendStatus(201);
    }
);

The library will escape the parameters (req.body.username in this case) so that it is treated as data rather than code.

It’s always a good idea to use a library's parameterized interface rather than try and do the escaping yourself. The library will be aware of the context in which the user input is being used, and so the escaping will be bespoke to the particular code being executed.

Assignment

Solve the "DOM XSS" challenge on Juice Shop

Additional Resources

OWASP Injection