Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 20 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,37 @@
# Secure Coding with Python.

## Chapter 2: SQL Injection
### Testing
Testing for SQL injections is a tedious job, it's mostly done by hand or using special scanners, like web scanners or
SAST/DAST tools. For this chapter we will be writing a very simple fuzzer function and create unit tests that use them
in order to test for injections.

First we install `pytest`:
```bash
> pip install pytest
```
or
```bash
> pip install -r requirements.txt
```
### Fix
Given that we have seen that the way this injection works is by breaking out of the `'`'s, we can use PostgreSQL
escaping `E'\''`. For that we change our SQL query and replace every occurrence of `'` with `\'`:

The fuzzer helper looks like this:
```python
import pytest

from psycopg2.errors import SyntaxError

def sqli_fuzzer(client, url, params):
fail = False
injections = ["'"]
for injection in injections:
for param in params:
data = {k: 'foo' for k in params}
data[param] = injection
try:
client.post(url, data=data)
except SyntaxError:
print('You seems to have an SQLi in %s for param %s' % (url, param))
fail = True

if fail:
pytest.fail('Seems you are vulnerable to SQLi attacks')
sql = "INSERT INTO listings (title, description) VALUES (E'%s', E'%s')" % (
title.replace("'", "\\'"), description.replace("'", "\\'")
)
```

After running `pytest --tb=short` we get:
With that our test now pass:

```text
============================= test session starts ==============================
platform linux -- Python 3.5.3, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
(venv) > $ pytest --tb=short
================================================================================================== test session starts ===================================================================================================
platform linux -- Python 3.5.3, pytest-5.0.0, py-1.8.0, pluggy-0.12.0
rootdir: {...}
collected 1 item
tests/test_listings.py . [100%]
================================================================================================ 1 passed in 0.95 seconds ================================================================================================
```

tests/test_listings.py F [100%]

=================================== FAILURES ===================================
_________________________________ test_create __________________________________
tests/test_listings.py:6: in test_create
sqli_fuzzer(client, '/listings/create', ['title', 'description'])
tests/helpers/sqlifuzzer.py:19: in sqli_fuzzer
pytest.fail('Seems you are vulnerable to SQLi attacks')
E Failed: Seems you are vulnerable to SQLi attacks
----------------------------- Captured stdout call -----------------------------
INSERT INTO listings (title, description) VALUES (''', 'foo')
You seems to have an SQLi in /listings/create for param title
INSERT INTO listings (title, description) VALUES ('foo', ''')
You seems to have an SQLi in /listings/create for param description
=========================== 1 failed in 0.32 seconds ===========================
But this is not sufficient, if we modify our payload to be `injection\', (select version()))-- -` our query will end up being:

```sql
INSERT INTO listings (title, description) VALUES (E'injection\\', (select version()))-- -', E'\'')
```

**Proceed to [next section](https://github.com/nxvl/secure-coding-with-python/tree/2.1-sql-injection/fix)**
and attacker will still be able to exploit our app.

**Proceed to [next section](https://github.com/nxvl/secure-coding-with-python/tree/2.2-sql-injection/test)**

## Index
### 1. Vulnerable Components
Expand Down
4 changes: 3 additions & 1 deletion marketplace/listings.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def register():
db = get_db()
cur = db.cursor()

sql = "INSERT INTO listings (title, description) VALUES ('%s', '%s')" % (title, description)
sql = "INSERT INTO listings (title, description) VALUES (E'%s', E'%s')" % (
title.replace("'", "\\'"), description.replace("'", "\\'")
)
print(sql, file=sys.stdout)
cur.execute(sql)
db.commit()
Expand Down