Skip to content

Commit

Permalink
feat(python): extend SQLi rules (#449)
Browse files Browse the repository at this point in the history
  • Loading branch information
elsapet authored Jun 5, 2024
1 parent 28c9764 commit 90cef70
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 0 deletions.
17 changes: 17 additions & 0 deletions rules/python/django/sql_injection.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
imports:
- python_shared_common_sql_user_input
- python_shared_lang_import4
patterns:
- pattern: $<_>.objects.raw($<USER_INPUT>$<...>)
filters:
- variable: USER_INPUT
detection: python_shared_common_sql_user_input
scope: result
- pattern: $<RAW_SQL>($<...>$<USER_INPUT>$<...>)
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
Expand Down
87 changes: 87 additions & 0 deletions rules/python/lang/sql_injection.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
imports:
- python_shared_common_sql_user_input
- python_shared_lang_instance
- python_shared_lang_import1
patterns:
- pattern: $<CURSOR>.$<METHOD>($<USER_INPUT>$<...>)
Expand All @@ -12,6 +13,51 @@ patterns:
- callproc
- execute
- executemany
- mogrify # psycopg
- variable: USER_INPUT
detection: python_shared_common_sql_user_input
scope: result
- pattern: $<ASYNCPG>.$<METHOD>($<USER_INPUT>$<...>)
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: $<PG8000_CONN>.$<METHOD>($<USER_INPUT>$<...>)
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
Expand All @@ -32,6 +78,47 @@ auxiliary:
- id: python_lang_sql_injection_cursor
patterns:
- $<_>.cursor()
- id: python_lang_sql_injection_pg8000_conn
patterns:
- pattern: $<GENERIC_CURSOR>
filters:
- variable: GENERIC_CURSOR
detection: python_lang_sql_injection_cursor
scope: cursor
- pattern: $<CONNECTION>
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: $<CONNECTION>
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
Expand Down
3 changes: 3 additions & 0 deletions tests/python/django/sql_injection/testdata/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
15 changes: 15 additions & 0 deletions tests/python/lang/sql_injection/testdata/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 90cef70

Please sign in to comment.