diff --git a/README.md b/README.md index 46e8245..a914103 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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) \ No newline at end of file +* [9-sensitive-data-exposure/fix](https://github.com/nxvl/secure-coding-with-python/tree/9-sensitive-data-exposure/fix) diff --git a/requirements.txt b/requirements.txt index 95a4a80..88fd963 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Flask==1.0.3 safety==1.8.5 psycopg2==2.8.3 +pytest==5.1.0 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..391301a --- /dev/null +++ b/tests/conftest.py @@ -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() diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/helpers/sqlifuzzer.py b/tests/helpers/sqlifuzzer.py new file mode 100644 index 0000000..7d8629a --- /dev/null +++ b/tests/helpers/sqlifuzzer.py @@ -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') diff --git a/tests/test_listings.py b/tests/test_listings.py new file mode 100644 index 0000000..ac5b912 --- /dev/null +++ b/tests/test_listings.py @@ -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'])