Skip to content

Commit 5a02b38

Browse files
committed
Python: use SqlConstruction in SqlAlchemy and
`SqlInjection`
1 parent e5b68d6 commit 5a02b38

File tree

6 files changed

+25
-23
lines changed

6 files changed

+25
-23
lines changed

python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,11 @@ module SqlAlchemy {
313313
* A construction of a `sqlalchemy.sql.expression.TextClause`, which represents a
314314
* textual SQL string directly.
315315
*/
316-
abstract class TextClauseConstruction extends DataFlow::CallCfgNode {
316+
abstract class TextClauseConstruction extends SqlConstruction::Range, DataFlow::CallCfgNode {
317317
/** Gets the argument that specifies the SQL text. */
318-
DataFlow::Node getTextArg() { result in [this.getArg(0), this.getArgByName("text")] }
318+
final override DataFlow::Node getSql() {
319+
result in [this.getArg(0), this.getArgByName("text")]
320+
}
319321
}
320322

321323
/** `TextClause` constructions from the `sqlalchemy` package. */

python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,17 @@ module SqlInjection {
4343
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
4444

4545
/**
46-
* A SQL statement of a SQL execution, considered as a flow sink.
46+
* A SQL statement of a SQL construction, considered as a flow sink.
4747
*/
48-
class SqlExecutionAsSink extends Sink {
49-
SqlExecutionAsSink() { this = any(SqlExecution e).getSql() }
48+
class SqlConstructionAsSink extends Sink {
49+
SqlConstructionAsSink() { this = any(SqlConstruction c).getSql() }
5050
}
5151

5252
/**
53-
* The text argument of a SQLAlchemy TextClause construction, considered as a flow sink.
53+
* A SQL statement of a SQL execution, considered as a flow sink.
5454
*/
55-
class TextArgAsSink extends Sink {
56-
TextArgAsSink() { this = any(SqlAlchemy::TextClause::TextClauseConstruction tcc).getTextArg() }
55+
class SqlExecutionAsSink extends Sink {
56+
SqlExecutionAsSink() { this = any(SqlExecution e).getSql() }
5757
}
5858

5959
/**

python/ql/test/library-tests/frameworks/flask_sqlalchemy/SqlExecution.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# - https://github.com/pallets/flask-sqlalchemy/blob/931ec00d1e27f51508e05706eef41cc4419a0b32/src/flask_sqlalchemy/__init__.py#L765
1313
# - https://github.com/pallets/flask-sqlalchemy/blob/931ec00d1e27f51508e05706eef41cc4419a0b32/src/flask_sqlalchemy/__init__.py#L99-L109
1414

15-
assert str(type(db.text("Foo"))) == "<class 'sqlalchemy.sql.elements.TextClause'>"
15+
assert str(type(db.text("Foo"))) == "<class 'sqlalchemy.sql.elements.TextClause'>" # $ constructedSql="Foo"
1616

1717
# also has engine/session instantiated
1818

@@ -44,8 +44,8 @@
4444

4545

4646
# text
47-
t = db.text("foo")
47+
t = db.text("foo") # $ constructedSql="foo"
4848
assert isinstance(t, sqlalchemy.sql.expression.TextClause)
4949

50-
t = db.text(text="foo")
50+
t = db.text(text="foo") # $ constructedSql="foo"
5151
assert isinstance(t, sqlalchemy.sql.expression.TextClause)

python/ql/test/library-tests/frameworks/sqlalchemy/SqlExecution.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class User(Base):
4646
connection.execute("some sql") # $ getSql="some sql"
4747

4848
# Injection requiring the text() taint-step
49-
t = text("some sql")
49+
t = text("some sql") # $ constructedSql="some sql"
5050
session.query(User).filter(t)
5151
session.query(User).group_by(User.id).having(t)
5252
session.query(User).group_by(t).first()

python/ql/test/library-tests/frameworks/sqlalchemy/new_tests.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# either v1.4 or v2.0, such that we cover both.
77

88
raw_sql = "select 'FOO'"
9-
text_sql = sqlalchemy.text(raw_sql)
9+
text_sql = sqlalchemy.text(raw_sql) # $ constructedSql=raw_sql
1010

1111
Base = sqlalchemy.orm.declarative_base()
1212

@@ -169,7 +169,7 @@ class For14(Base):
169169

170170
# and now we can do the actual querying
171171

172-
text_foo = sqlalchemy.text("'FOO'")
172+
text_foo = sqlalchemy.text("'FOO'") # $ constructedSql="'FOO'"
173173

174174
# filter_by is only vulnerable to injection if sqlalchemy.text is used, which is evident
175175
# from the logs produced if this file is run
@@ -298,7 +298,7 @@ class For14(Base):
298298
assert scalar_result == "FOO"
299299

300300
# This is a contrived example
301-
select = sqlalchemy.select(sqlalchemy.text("'BAR'"))
301+
select = sqlalchemy.select(sqlalchemy.text("'BAR'")) # $ constructedSql="'BAR'"
302302
result = conn.execute(select) # $ getSql=select
303303
assert result.fetchall() == [("BAR",)]
304304

python/ql/test/library-tests/frameworks/sqlalchemy/taint_test.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ def test_taint():
88

99
ensure_tainted(ts) # $ tainted
1010

11-
t1 = sqlalchemy.text(ts)
12-
t2 = sqlalchemy.text(text=ts)
13-
t3 = sqlalchemy.sql.text(ts)
14-
t4 = sqlalchemy.sql.text(text=ts)
15-
t5 = sqlalchemy.sql.expression.text(ts)
16-
t6 = sqlalchemy.sql.expression.text(text=ts)
17-
t7 = sqlalchemy.sql.expression.TextClause(ts)
18-
t8 = sqlalchemy.sql.expression.TextClause(text=ts)
11+
t1 = sqlalchemy.text(ts) # $ constructedSql=ts
12+
t2 = sqlalchemy.text(text=ts) # $ constructedSql=ts
13+
t3 = sqlalchemy.sql.text(ts) # $ constructedSql=ts
14+
t4 = sqlalchemy.sql.text(text=ts) # $ constructedSql=ts
15+
t5 = sqlalchemy.sql.expression.text(ts) # $ constructedSql=ts
16+
t6 = sqlalchemy.sql.expression.text(text=ts) # $ constructedSql=ts
17+
t7 = sqlalchemy.sql.expression.TextClause(ts) # $ constructedSql=ts
18+
t8 = sqlalchemy.sql.expression.TextClause(text=ts) # $ constructedSql=ts
1919

2020
# Since we flag user-input to a TextClause with its' own query, we don't want to
2121
# have a taint-step for it as that would lead to us also giving an alert for normal

0 commit comments

Comments
 (0)