From 90cef70b75fd8da64a728a93defee3aafb084601 Mon Sep 17 00:00:00 2001 From: elsapet Date: Wed, 5 Jun 2024 15:20:36 +0200 Subject: [PATCH] feat(python): extend SQLi rules (#449) --- rules/python/django/sql_injection.yml | 17 ++++ rules/python/lang/sql_injection.yml | 87 +++++++++++++++++++ .../django/sql_injection/testdata/main.py | 3 + .../lang/sql_injection/testdata/main.py | 15 ++++ 4 files changed, 122 insertions(+) diff --git a/rules/python/django/sql_injection.yml b/rules/python/django/sql_injection.yml index 4a6c1c17..498098a9 100644 --- a/rules/python/django/sql_injection.yml +++ b/rules/python/django/sql_injection.yml @@ -1,11 +1,28 @@ imports: - python_shared_common_sql_user_input + - python_shared_lang_import4 patterns: - pattern: $<_>.objects.raw($$<...>) filters: - variable: USER_INPUT detection: python_shared_common_sql_user_input scope: result + - pattern: $($<...>$$<...>) + filters: + - variable: RAW_SQL + detection: python_shared_lang_import4 + scope: cursor + filters: + - variable: MODULE1 + values: [django] + - variable: MODULE2 + values: [db] + - variable: MODULE3 + values: [models] + - variable: MODULE4 + values: [expressions] + - variable: NAME + values: [RawSQL] languages: - python severity: critical diff --git a/rules/python/lang/sql_injection.yml b/rules/python/lang/sql_injection.yml index 668f2524..003122c6 100644 --- a/rules/python/lang/sql_injection.yml +++ b/rules/python/lang/sql_injection.yml @@ -1,5 +1,6 @@ imports: - python_shared_common_sql_user_input + - python_shared_lang_instance - python_shared_lang_import1 patterns: - pattern: $.$($$<...>) @@ -12,6 +13,51 @@ patterns: - callproc - execute - executemany + - mogrify # psycopg + - variable: USER_INPUT + detection: python_shared_common_sql_user_input + scope: result + - pattern: $.$($$<...>) + filters: + - variable: ASYNCPG + detection: python_shared_lang_instance + scope: result + filters: + - variable: CLASS + detection: python_shared_lang_import1 + scope: cursor + filters: + - variable: MODULE1 + values: [asyncpg] + - variable: NAME + values: + - connect + - create_pool + - Connection + - variable: METHOD + values: + - fetch + - fetchrow + - fetchval + - execute + - executemany + - prepare + - cursor + - copyfromquery + - variable: USER_INPUT + detection: python_shared_common_sql_user_input + scope: result + - pattern: $.$($$<...>) + filters: + - variable: PG8000_CONN + detection: python_lang_sql_injection_pg8000_conn + scope: result + - variable: METHOD + values: + - execute + - executemany + - run + - prepare - variable: USER_INPUT detection: python_shared_common_sql_user_input scope: result @@ -32,6 +78,47 @@ auxiliary: - id: python_lang_sql_injection_cursor patterns: - $<_>.cursor() + - id: python_lang_sql_injection_pg8000_conn + patterns: + - pattern: $ + filters: + - variable: GENERIC_CURSOR + detection: python_lang_sql_injection_cursor + scope: cursor + - pattern: $ + filters: + - variable: CONNECTION + detection: python_shared_lang_instance + scope: result + filters: + - variable: CLASS + detection: python_shared_lang_import1 + scope: cursor + filters: + - variable: MODULE1 + values: [pg8000] + - variable: NAME + values: [connect] + - pattern: $ + filters: + - variable: CONNECTION + detection: python_shared_lang_instance + scope: result + filters: + - variable: CLASS + detection: python_shared_lang_import2 + scope: cursor + filters: + - variable: MODULE1 + values: [pg8000] + - variable: MODULE2 + values: + - native + - dhapi + - variable: NAME + values: + - Connection + - connect languages: - python severity: critical diff --git a/tests/python/django/sql_injection/testdata/main.py b/tests/python/django/sql_injection/testdata/main.py index 62ea7a1b..b1e42c03 100644 --- a/tests/python/django/sql_injection/testdata/main.py +++ b/tests/python/django/sql_injection/testdata/main.py @@ -8,3 +8,6 @@ safe = pymysql.connect().escape_string(user_input) User.objects.raw(f"SELECT * FROM x WHERE y = {safe}", []) +from django.db.models.expressions import RawSQL +# bearer:expected python_django_sql_injection +RawSQL(f"SELECT * FROM x WHERE y = {user_input}") \ No newline at end of file diff --git a/tests/python/lang/sql_injection/testdata/main.py b/tests/python/lang/sql_injection/testdata/main.py index 3e7dfe3c..1e21ea4c 100644 --- a/tests/python/lang/sql_injection/testdata/main.py +++ b/tests/python/lang/sql_injection/testdata/main.py @@ -15,6 +15,21 @@ def generic(): # bearer:expected python_lang_sql_injection c.executemany(f"UPDATE bar SET foo = 1 WHERE baz = {user_input}", {}) +def asyncpg(): + import asyncpg + conn = await asyncpg.connect(user='mish', password='password') + query = "SELECT * FROM bar WHERE foo=" + user_input + # bearer:expected python_lang_sql_injection + values = await conn.fetch(query) + await conn.close() + +def pg8000(): + import pg8000.native as pg + import pg8000.dbapi + conn = pg.Connection("postgres", password="password") + query = "SELECT * FROM bar WHERE foo=" + user_input + # bearer:expected python_lang_sql_injection + values = await conn.run(query) def sqlalchemy(): from sqlalchemy import create_engine, text