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
97 changes: 52 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,62 +1,69 @@
# Secure Coding with Python.

## Chapter 2: SQL Injection
### Requirement
For our marketplace application, we first decide to allow the upload of Listings, just text. We will
worry about users later, since we want to focus on getting the DB and Models setup without needed to worry about
authentication and session management at this point.

### Setting up the DB
First we need to install postgresql, you can do that with your preferred package manager, for this tutorial we will be
using postgres 11.4. After installing you would probably need to init the db:
```bash
> initdb /usr/local/var/postgres
```
*Note*: on linux you might need to run the command as root or use sudo.

Then we create the `marketplace` database:
```bash
> createdb marketplace
```
*Note*: on linux you might need to run the command as postgres user by prepending `sudo -u postgres` to the command.
### 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.

Then we need to install the python driver for python, which comes as the `psycopg2` package.
First we install `pytest`:
```bash
> pip install psycopg2
> pip install pytest
```
or
or
```bash
> pip install -r requirements.txt
```
*Note*: On OSX if you installed postgres from homebrew, you might need to prepend
`LDFLAGS="-I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib"` to the command in order to install correctly.

### Development
Since the application will need some more configuration we change the `marketplace/__init__.py` to make use of the
`create_app` factory function. We add the DB connection functions into `marketplace/db.py` and add the factory function.
We also add the DB schema in `schema.sql` and add a flask command to init the DB, which we run with:
```bash
> python -m flask init-db
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')
```

### Vulnerability
Since we are generating the SQL to insert the new listing in a very unsecure way, we can insert SQL commands that will
be run in the DB. For example if we insert `'` as title or description we will get
`psycopg2.errors.SyntaxError: INSERT has more target columns than expressions LINE 1: INSERT INTO listings (title, description) VALUES (''', ''') ^`
instead of a success.
After running `pytest --tb=short` we get:
```text
============================= test session starts ==============================
platform linux -- Python 3.5.3, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: {...}
collected 1 item

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 ===========================

We can for example get the postgresql version or any other SQL function result, to check that out, insert
`injection', (select version()))-- -` as the title. When we do so, the SQL that's going to be executed will be the
following:

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

As it can be seen, the inserted title will be `injection` and the description will be the result of the
`select version()` command, or any other command we wish to insert there, including dropping the DB.

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

## Index
### 1. Vulnerable Components
Expand Down Expand Up @@ -108,4 +115,4 @@ As it can be seen, the inserted title will be `injection` and the description wi
### 9. Sensitive Data Exposure
* [9-sensitive-data-exposure/code](https://github.com/nxvl/secure-coding-with-python/tree/9-sensitive-data-exposure/code)
* [9-sensitive-data-exposure/test](https://github.com/nxvl/secure-coding-with-python/tree/9-sensitive-data-exposure/test)
* [9-sensitive-data-exposure/fix](https://github.com/nxvl/secure-coding-with-python/tree/9-sensitive-data-exposure/fix)
* [9-sensitive-data-exposure/fix](https://github.com/nxvl/secure-coding-with-python/tree/9-sensitive-data-exposure/fix)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Flask==1.0.3
safety==1.8.5
psycopg2==2.8.3
pytest==5.1.0
Empty file added tests/__init__.py
Empty file.
29 changes: 29 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os
import sys

import pytest
from marketplace import create_app
from marketplace.db import get_db, init_db

sys.path.append(os.path.join(os.path.dirname(__file__), 'helpers'))

@pytest.fixture
def app():
app = create_app({
'TESTING': True,
'DATABASE': 'marketplace_test',
})

with app.app_context():
init_db()

yield app

@pytest.fixture
def client(app):
return app.test_client()


@pytest.fixture
def runner(app):
return app.test_cli_runner()
Empty file added tests/helpers/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions tests/helpers/sqlifuzzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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')
6 changes: 6 additions & 0 deletions tests/test_listings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .helpers.sqlifuzzer import sqli_fuzzer

def test_create(client, app):
assert client.get('/listings/create').status_code == 200

sqli_fuzzer(client, '/listings/create', ['title', 'description'])