diff --git a/python/ql/lib/semmle/python/frameworks/internal/PEP249Impl.qll b/python/ql/lib/semmle/python/frameworks/internal/PEP249Impl.qll index 358f4ec1356..bf63bbb3731 100644 --- a/python/ql/lib/semmle/python/frameworks/internal/PEP249Impl.qll +++ b/python/ql/lib/semmle/python/frameworks/internal/PEP249Impl.qll @@ -178,4 +178,27 @@ module PEP249 { override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("sql")] } } + + private DataFlow::TypeTrackingNode executemany(DataFlow::TypeTracker t) { + t.startInAttr("executemany") and + result in [Cursor::instance(), Connection::instance()] + or + exists(DataFlow::TypeTracker t2 | result = executemany(t2).track(t2, t)) + } + + private DataFlow::Node executemany() { executemany(DataFlow::TypeTracker::end()).flowsTo(result) } + + /** + * A call to the `executemany` method on a cursor or a connection. + * + * See https://peps.python.org/pep-0249/#executemany + * + * Note: While `executemany` method on a connection is not part of PEP249, if it is used, we + * recognize it as an alias for constructing a cursor and calling `executemany` on it. + */ + private class ExecutemanyCall extends SqlExecution::Range, DataFlow::CallCfgNode { + ExecutemanyCall() { this.getFunction() = executemany() } + + override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("sql")] } + } } diff --git a/python/ql/src/change-notes/2022-10-10-pep249-executemany-modeling.md b/python/ql/src/change-notes/2022-10-10-pep249-executemany-modeling.md new file mode 100644 index 00000000000..556520df64f --- /dev/null +++ b/python/ql/src/change-notes/2022-10-10-pep249-executemany-modeling.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added model of `executemany` calls on PEP-249 compliant Database APIs, resulting in additional sinks for `py/sql-injection`. diff --git a/python/ql/test/library-tests/frameworks/pymssql/pep249.py b/python/ql/test/library-tests/frameworks/pymssql/pep249.py index bfbc9b6bc3b..2ecd00ad464 100644 --- a/python/ql/test/library-tests/frameworks/pymssql/pep249.py +++ b/python/ql/test/library-tests/frameworks/pymssql/pep249.py @@ -3,4 +3,4 @@ connection = pymssql.connect(host="localhost", user="user", password="passwd") cursor = connection.cursor() cursor.execute("some sql", (42,)) # $ getSql="some sql" -cursor.executemany("some sql", [(42,)]) # $ MISSING: getSql="some sql" +cursor.executemany("some sql", [(42,)]) # $ getSql="some sql" diff --git a/python/ql/test/library-tests/frameworks/pymysql/pep249.py b/python/ql/test/library-tests/frameworks/pymysql/pep249.py index 363e55f1fc3..1f9352875c5 100644 --- a/python/ql/test/library-tests/frameworks/pymysql/pep249.py +++ b/python/ql/test/library-tests/frameworks/pymysql/pep249.py @@ -3,3 +3,4 @@ connection = pymysql.connect(host="localhost", user="user", password="passwd") cursor = connection.cursor() cursor.execute("some sql", (42,)) # $ getSql="some sql" +cursor.executemany("some sql", [(42,)]) # $ getSql="some sql"