From 4b9c9b0c8d71d8ed444ceff248443a38f32cbc37 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 4 May 2022 10:58:29 +0200 Subject: [PATCH] move most of asyncpg test into SqlInjection after moving MaD sql-injection sink --- .../library-tests/frameworks/asyncpg/test.py | 44 +++++----- .../SqlInjection.expected | 73 ++++++++++++++++ .../Security/CWE-089-SqlInjection/asyncpg.py | 87 +++++++++++++++++++ 3 files changed, 182 insertions(+), 22 deletions(-) create mode 100644 python/ql/test/query-tests/Security/CWE-089-SqlInjection/asyncpg.py diff --git a/python/ql/test/library-tests/frameworks/asyncpg/test.py b/python/ql/test/library-tests/frameworks/asyncpg/test.py index 572fc454bed..b9d6aee5b18 100644 --- a/python/ql/test/library-tests/frameworks/asyncpg/test.py +++ b/python/ql/test/library-tests/frameworks/asyncpg/test.py @@ -7,17 +7,17 @@ async def test_connection(): try: # The file-like object is passed in as a keyword-only argument. # See https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.connection.Connection.copy_from_query - await conn.copy_from_query("sql", output="filepath") # $ getSql="sql" getAPathArgument="filepath" - await conn.copy_from_query("sql", "arg1", "arg2", output="filepath") # $ getSql="sql" getAPathArgument="filepath" + await conn.copy_from_query("sql", output="filepath") # $ getAPathArgument="filepath" + await conn.copy_from_query("sql", "arg1", "arg2", output="filepath") # $ getAPathArgument="filepath" await conn.copy_from_table("table", output="filepath") # $ getAPathArgument="filepath" await conn.copy_to_table("table", source="filepath") # $ getAPathArgument="filepath" - await conn.execute("sql") # $ getSql="sql" - await conn.executemany("sql") # $ getSql="sql" - await conn.fetch("sql") # $ getSql="sql" - await conn.fetchrow("sql") # $ getSql="sql" - await conn.fetchval("sql") # $ getSql="sql" + await conn.execute("sql") # $ + await conn.executemany("sql") # $ + await conn.fetch("sql") # $ + await conn.fetchrow("sql") # $ + await conn.fetchval("sql") # $ finally: await conn.close() @@ -27,7 +27,7 @@ async def test_prepared_statement(): conn = await asyncpg.connect() try: - pstmt = await conn.prepare("psql") # $ getSql="psql" + pstmt = await conn.prepare("psql") # $ pstmt.executemany() pstmt.fetch() pstmt.fetchrow() @@ -43,10 +43,10 @@ async def test_cursor(): try: async with conn.transaction(): - cursor = await conn.cursor("sql") # $ getSql="sql" constructedSql="sql" + cursor = await conn.cursor("sql") # $ constructedSql="sql" getSql="sql" await cursor.fetch() - pstmt = await conn.prepare("psql") # $ getSql="psql" + pstmt = await conn.prepare("psql") # pcursor = await pstmt.cursor() # $ getSql="psql" await pcursor.fetch() @@ -69,23 +69,23 @@ async def test_connection_pool(): pool = await asyncpg.create_pool() try: - await pool.copy_from_query("sql", output="filepath") # $ getSql="sql" getAPathArgument="filepath" - await pool.copy_from_query("sql", "arg1", "arg2", output="filepath") # $ getSql="sql" getAPathArgument="filepath" + await pool.copy_from_query("sql", output="filepath") # $ getAPathArgument="filepath" + await pool.copy_from_query("sql", "arg1", "arg2", output="filepath") # $ getAPathArgument="filepath" await pool.copy_from_table("table", output="filepath") # $ getAPathArgument="filepath" await pool.copy_to_table("table", source="filepath") # $ getAPathArgument="filepath" - await pool.execute("sql") # $ getSql="sql" - await pool.executemany("sql") # $ getSql="sql" - await pool.fetch("sql") # $ getSql="sql" - await pool.fetchrow("sql") # $ getSql="sql" - await pool.fetchval("sql") # $ getSql="sql" + await pool.execute("sql") # $ + await pool.executemany("sql") # $ + await pool.fetch("sql") # $ + await pool.fetchrow("sql") # $ + await pool.fetchval("sql") # $ async with pool.acquire() as conn: - await conn.execute("sql") # $ getSql="sql" + await conn.execute("sql") # $ conn = await pool.acquire() try: - await conn.fetch("sql") # $ getSql="sql" + await conn.fetch("sql") # $ finally: await pool.release(conn) @@ -93,13 +93,13 @@ async def test_connection_pool(): await pool.close() async with asyncpg.create_pool() as pool: - await pool.execute("sql") # $ getSql="sql" + await pool.execute("sql") # $ async with pool.acquire() as conn: - await conn.execute("sql") # $ getSql="sql" + await conn.execute("sql") # $ conn = await pool.acquire() try: - await conn.fetch("sql") # $ getSql="sql" + await conn.fetch("sql") # $ finally: await pool.release(conn) diff --git a/python/ql/test/query-tests/Security/CWE-089-SqlInjection/SqlInjection.expected b/python/ql/test/query-tests/Security/CWE-089-SqlInjection/SqlInjection.expected index ed4c3e3d313..fe01f3b548d 100644 --- a/python/ql/test/query-tests/Security/CWE-089-SqlInjection/SqlInjection.expected +++ b/python/ql/test/query-tests/Security/CWE-089-SqlInjection/SqlInjection.expected @@ -1,4 +1,28 @@ edges +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:19:36:19:43 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:20:36:20:43 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:22:28:22:35 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:23:32:23:39 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:24:26:24:33 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:25:29:25:36 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:26:29:26:36 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:28:36:28:43 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:37:40:37:47 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:40:40:40:47 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:44:45:44:52 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:47:42:47:49 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:56:36:56:43 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:57:36:57:43 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:59:28:59:35 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:60:32:60:39 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:61:26:61:33 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:62:29:62:36 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:63:29:63:36 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:66:32:66:39 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:70:30:70:37 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:78:28:78:35 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:81:32:81:39 | ControlFlowNode for username | +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:85:30:85:37 | ControlFlowNode for username | | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:21:24:21:77 | ControlFlowNode for BinaryExpr | | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:24:38:24:95 | ControlFlowNode for BinaryExpr | | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:25:26:25:83 | ControlFlowNode for BinaryExpr | @@ -16,6 +40,31 @@ edges | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:50:18:50:25 | ControlFlowNode for username | | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:51:24:51:31 | ControlFlowNode for username | nodes +| asyncpg.py:13:21:13:28 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:19:36:19:43 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:20:36:20:43 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:22:28:22:35 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:23:32:23:39 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:24:26:24:33 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:25:29:25:36 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:26:29:26:36 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:28:36:28:43 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:37:40:37:47 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:40:40:40:47 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:44:45:44:52 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:47:42:47:49 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:56:36:56:43 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:57:36:57:43 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:59:28:59:35 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:60:32:60:39 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:61:26:61:33 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:62:29:62:36 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:63:29:63:36 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:66:32:66:39 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:70:30:70:37 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:78:28:78:35 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:81:32:81:39 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | +| asyncpg.py:85:30:85:37 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | | sql_injection.py:14:15:14:22 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | | sql_injection.py:21:24:21:77 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | sql_injection.py:24:38:24:95 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | @@ -36,6 +85,30 @@ nodes | sqlalchemy_textclause.py:51:24:51:31 | ControlFlowNode for username | semmle.label | ControlFlowNode for username | subpaths #select +| asyncpg.py:19:36:19:43 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:19:36:19:43 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:20:36:20:43 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:20:36:20:43 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:22:28:22:35 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:22:28:22:35 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:23:32:23:39 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:23:32:23:39 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:24:26:24:33 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:24:26:24:33 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:25:29:25:36 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:25:29:25:36 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:26:29:26:36 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:26:29:26:36 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:28:36:28:43 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:28:36:28:43 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:37:40:37:47 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:37:40:37:47 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:40:40:40:47 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:40:40:40:47 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:44:45:44:52 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:44:45:44:52 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:47:42:47:49 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:47:42:47:49 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:56:36:56:43 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:56:36:56:43 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:57:36:57:43 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:57:36:57:43 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:59:28:59:35 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:59:28:59:35 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:60:32:60:39 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:60:32:60:39 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:61:26:61:33 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:61:26:61:33 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:62:29:62:36 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:62:29:62:36 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:63:29:63:36 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:63:29:63:36 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:66:32:66:39 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:66:32:66:39 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:70:30:70:37 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:70:30:70:37 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:78:28:78:35 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:78:28:78:35 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:81:32:81:39 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:81:32:81:39 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | +| asyncpg.py:85:30:85:37 | ControlFlowNode for username | asyncpg.py:13:21:13:28 | ControlFlowNode for username | asyncpg.py:85:30:85:37 | ControlFlowNode for username | This SQL query depends on $@. | asyncpg.py:13:21:13:28 | ControlFlowNode for username | a user-provided value | | sql_injection.py:21:24:21:77 | ControlFlowNode for BinaryExpr | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:21:24:21:77 | ControlFlowNode for BinaryExpr | This SQL query depends on $@. | sql_injection.py:14:15:14:22 | ControlFlowNode for username | a user-provided value | | sql_injection.py:24:38:24:95 | ControlFlowNode for BinaryExpr | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:24:38:24:95 | ControlFlowNode for BinaryExpr | This SQL query depends on $@. | sql_injection.py:14:15:14:22 | ControlFlowNode for username | a user-provided value | | sql_injection.py:25:26:25:83 | ControlFlowNode for BinaryExpr | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:25:26:25:83 | ControlFlowNode for BinaryExpr | This SQL query depends on $@. | sql_injection.py:14:15:14:22 | ControlFlowNode for username | a user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-089-SqlInjection/asyncpg.py b/python/ql/test/query-tests/Security/CWE-089-SqlInjection/asyncpg.py new file mode 100644 index 00000000000..fd9dfed5877 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-089-SqlInjection/asyncpg.py @@ -0,0 +1,87 @@ +import asyncio +import asyncpg + +"""This is adapted from ql/python/ql/test/query-tests\Security\CWE-089 +we now prefer to setup routing by flask +""" + +from django.db import connection, models +from flask import Flask +app = Flask(__name__) + +@app.route("/users/") +async def show_user(username): + conn = await asyncpg.connect() + + try: + # The file-like object is passed in as a keyword-only argument. + # See https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.connection.Connection.copy_from_query + await conn.copy_from_query(username, output="filepath") # BAD + await conn.copy_from_query(username, "arg1", "arg2", output="filepath") # BAD + + await conn.execute(username) # BAD + await conn.executemany(username) # BAD + await conn.fetch(username) # BAD + await conn.fetchrow(username) # BAD + await conn.fetchval(username) # BAD + + pstmt = await conn.prepare(username) # BAD + pstmt.executemany() + pstmt.fetch() + pstmt.fetchrow() + pstmt.fetchval() + + # The sql statement is executed when the `CursorFactory` (obtained by e.g. `conn.cursor()`) is awaited. + # See https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.cursor.CursorFactory + async with conn.transaction(): + cursor = await conn.cursor(username) # BAD + await cursor.fetch() + + pstmt = await conn.prepare(username) # BAD + pcursor = await pstmt.cursor() + await pcursor.fetch() + + async for record in conn.cursor(username): # BAD + pass + + cursor_factory = conn.cursor(username) # BAD + cursor = await cursor_factory + + finally: + await conn.close() + + pool = await asyncpg.create_pool() + + try: + await pool.copy_from_query(username, output="filepath") # BAD + await pool.copy_from_query(username, "arg1", "arg2", output="filepath") # BAD + + await pool.execute(username) # BAD + await pool.executemany(username) # BAD + await pool.fetch(username) # BAD + await pool.fetchrow(username) # BAD + await pool.fetchval(username) # BAD + + async with pool.acquire() as conn: + await conn.execute(username) # BAD + + conn = await pool.acquire() + try: + await conn.fetch(username) # BAD + finally: + await pool.release(conn) + + finally: + await pool.close() + + async with asyncpg.create_pool() as pool: + await pool.execute(username) # BAD + + async with pool.acquire() as conn: + await conn.execute(username) # BAD + + conn = await pool.acquire() + try: + await conn.fetch(username) # BAD + finally: + await pool.release(conn)