1
1
/**
2
- * Provides classes modeling security-relevant aspects of the 'SqlAlchemy' package.
3
- * See https://pypi.org/project/SQLAlchemy/.
2
+ * Provides classes modeling security-relevant aspects of the `SQLAlchemy` PyPI package.
3
+ * See
4
+ * - https://pypi.org/project/SQLAlchemy/
5
+ * - https://docs.sqlalchemy.org/en/14/index.html
4
6
*/
5
7
6
8
private import python
@@ -10,93 +12,209 @@ private import semmle.python.ApiGraphs
10
12
private import semmle.python.Concepts
11
13
private import experimental.semmle.python.Concepts
12
14
15
+ /**
16
+ * Provides models for the `SQLAlchemy` PyPI package.
17
+ * See
18
+ * - https://pypi.org/project/SQLAlchemy/
19
+ * - https://docs.sqlalchemy.org/en/14/index.html
20
+ */
13
21
private module SqlAlchemy {
14
22
/**
15
- * Returns an instantization of a SqlAlchemy Session object.
16
- * See https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session and
17
- * https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.sessionmaker
23
+ * Provides models for the `sqlalchemy.engine.Engine` and `sqlalchemy.future.Engine` classes.
24
+ *
25
+ * These are so similar that we model both in the same way.
26
+ *
27
+ * See
28
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Engine
29
+ * - https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Engine
18
30
*/
19
- private API:: Node getSqlAlchemySessionInstance ( ) {
20
- result = API:: moduleImport ( "sqlalchemy.orm" ) .getMember ( "Session" ) .getReturn ( ) or
21
- result = API:: moduleImport ( "sqlalchemy.orm" ) .getMember ( "sessionmaker" ) .getReturn ( ) .getReturn ( )
22
- }
31
+ module Engine {
32
+ /** Gets a reference to a SQLAlchemy Engine class. */
33
+ private API:: Node classRef ( ) {
34
+ result = API:: moduleImport ( "sqlalchemy" ) .getMember ( "engine" ) .getMember ( "Engine" )
35
+ or
36
+ result = API:: moduleImport ( "sqlalchemy" ) .getMember ( "future" ) .getMember ( "Engine" )
37
+ }
23
38
24
- /**
25
- * Returns an instantization of a SqlAlchemy Engine object.
26
- * See https://docs.sqlalchemy.org/en/14/core/engines.html#sqlalchemy.create_engine
27
- */
28
- private API:: Node getSqlAlchemyEngineInstance ( ) {
29
- result = API:: moduleImport ( "sqlalchemy" ) .getMember ( "create_engine" ) .getReturn ( )
30
- }
39
+ /**
40
+ * A source of instances of a SQLAlchemy Engine, extend this class to model new instances.
41
+ *
42
+ * This can include instantiations of the class, return values from function
43
+ * calls, or a special parameter that will be set when functions are called by an external
44
+ * library.
45
+ *
46
+ * Use the predicate `Engine::instance()` to get references to instances of a SQLAlchemy Engine.
47
+ */
48
+ abstract class InstanceSource extends DataFlow:: LocalSourceNode { }
31
49
32
- /**
33
- * Returns an instantization of a SqlAlchemy Query object.
34
- * See https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query#sqlalchemy.orm.Query
35
- */
36
- private API:: Node getSqlAlchemyQueryInstance ( ) {
37
- result = getSqlAlchemySessionInstance ( ) .getMember ( "query" ) .getReturn ( )
50
+ private class EngineConstruction extends InstanceSource , DataFlow:: CallCfgNode {
51
+ EngineConstruction ( ) {
52
+ this = classRef ( ) .getACall ( )
53
+ or
54
+ this = API:: moduleImport ( "sqlalchemy" ) .getMember ( "create_engine" ) .getACall ( )
55
+ or
56
+ this =
57
+ API:: moduleImport ( "sqlalchemy" ) .getMember ( "future" ) .getMember ( "create_engine" ) .getACall ( )
58
+ or
59
+ this .( DataFlow:: MethodCallNode ) .calls ( instance ( ) , "execution_options" )
60
+ }
61
+ }
62
+
63
+ /** Gets a reference to an instance of a SQLAlchemy Engine. */
64
+ private DataFlow:: TypeTrackingNode instance ( DataFlow:: TypeTracker t ) {
65
+ t .start ( ) and
66
+ result instanceof InstanceSource
67
+ or
68
+ exists ( DataFlow:: TypeTracker t2 | result = instance ( t2 ) .track ( t2 , t ) )
69
+ }
70
+
71
+ /** Gets a reference to an instance of a SQLAlchemy Engine. */
72
+ DataFlow:: Node instance ( ) { instance ( DataFlow:: TypeTracker:: end ( ) ) .flowsTo ( result ) }
38
73
}
39
74
40
75
/**
41
- * A call to `execute` meant to execute an SQL expression
42
- * See the following links:
43
- * - https://docs.sqlalchemy.org/en/14/core/connections.html?highlight=execute#sqlalchemy.engine.Connection.execute
44
- * - https://docs.sqlalchemy.org/en/14/core/connections.html?highlight=execute#sqlalchemy.engine.Engine.execute
45
- * - https://docs.sqlalchemy.org/en/14/orm/session_api.html?highlight=execute#sqlalchemy.orm.Session.execute
76
+ * Provides models for the `sqlalchemy.engine.base.Connection` and `sqlalchemy.future.Connection` classes.
77
+ *
78
+ * These are so similar that we model both in the same way.
79
+ *
80
+ * See
81
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Connection
82
+ * - https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Connection
46
83
*/
47
- private class SqlAlchemyExecuteCall extends DataFlow :: CallCfgNode , SqlExecution :: Range {
48
- SqlAlchemyExecuteCall ( ) {
49
- // new way
50
- this = getSqlAlchemySessionInstance ( ) . getMember ( "execute" ) . getACall ( ) or
51
- this =
52
- getSqlAlchemyEngineInstance ( )
53
- .getMember ( [ "connect" , "begin" ] )
54
- .getReturn ( )
55
- . getMember ( "execute" )
56
- . getACall ( )
84
+ module Connection {
85
+ /** Gets a reference to a SQLAlchemy Connection class. */
86
+ private API :: Node classRef ( ) {
87
+ result =
88
+ API :: moduleImport ( "sqlalchemy" )
89
+ . getMember ( "engine" )
90
+ .getMember ( "base" )
91
+ .getMember ( "Connection" )
92
+ or
93
+ result = API :: moduleImport ( "sqlalchemy" ) . getMember ( "future" ) . getMember ( "Connection" )
57
94
}
58
95
59
- override DataFlow:: Node getSql ( ) { result = this .getArg ( 0 ) }
96
+ /**
97
+ * A source of instances of a SQLAlchemy Connection, extend this class to model new instances.
98
+ *
99
+ * This can include instantiations of the class, return values from function
100
+ * calls, or a special parameter that will be set when functions are called by an external
101
+ * library.
102
+ *
103
+ * Use the predicate `Connection::instance()` to get references to instances of a SQLAlchemy Connection.
104
+ */
105
+ abstract class InstanceSource extends DataFlow:: LocalSourceNode { }
106
+
107
+ private class ConnectionConstruction extends InstanceSource , DataFlow:: CallCfgNode {
108
+ ConnectionConstruction ( ) {
109
+ this = classRef ( ) .getACall ( )
110
+ or
111
+ this .( DataFlow:: MethodCallNode ) .calls ( Engine:: instance ( ) , [ "begin" , "connect" ] )
112
+ or
113
+ this .( DataFlow:: MethodCallNode ) .calls ( instance ( ) , "connect" )
114
+ }
115
+ }
116
+
117
+ /** Gets a reference to an instance of a SQLAlchemy Connection. */
118
+ private DataFlow:: TypeTrackingNode instance ( DataFlow:: TypeTracker t ) {
119
+ t .start ( ) and
120
+ result instanceof InstanceSource
121
+ or
122
+ exists ( DataFlow:: TypeTracker t2 | result = instance ( t2 ) .track ( t2 , t ) )
123
+ }
124
+
125
+ /** Gets a reference to an instance of a SQLAlchemy Connection. */
126
+ DataFlow:: Node instance ( ) { instance ( DataFlow:: TypeTracker:: end ( ) ) .flowsTo ( result ) }
60
127
}
61
128
62
129
/**
63
- * A call to `scalar` meant to execute an SQL expression
64
- * See https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session.scalar and
65
- * https://docs.sqlalchemy.org/en/14/core/connections.html?highlight=scalar#sqlalchemy.engine.Engine.scalar
130
+ * Provides models for the `sqlalchemy.orm.Session` class
131
+ *
132
+ * See
133
+ * - https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session
134
+ * - https://docs.sqlalchemy.org/en/14/orm/session_basics.html
66
135
*/
67
- private class SqlAlchemyScalarCall extends DataFlow:: CallCfgNode , SqlExecution:: Range {
68
- SqlAlchemyScalarCall ( ) {
69
- this =
70
- [ getSqlAlchemySessionInstance ( ) , getSqlAlchemyEngineInstance ( ) ]
71
- .getMember ( "scalar" )
72
- .getACall ( )
136
+ module Session {
137
+ /** Gets a reference to the `sqlalchemy.orm.Session` class. */
138
+ private API:: Node classRef ( ) {
139
+ result = API:: moduleImport ( "sqlalchemy" ) .getMember ( "orm" ) .getMember ( "Session" )
73
140
}
74
141
75
- override DataFlow:: Node getSql ( ) { result = this .getArg ( 0 ) }
142
+ /**
143
+ * A source of instances of `sqlalchemy.orm.Session`, extend this class to model new instances.
144
+ *
145
+ * This can include instantiations of the class, return values from function
146
+ * calls, or a special parameter that will be set when functions are called by an external
147
+ * library.
148
+ *
149
+ * Use the predicate `Session::instance()` to get references to instances of `sqlalchemy.orm.Session`.
150
+ */
151
+ abstract class InstanceSource extends DataFlow:: LocalSourceNode { }
152
+
153
+ private class SessionConstruction extends InstanceSource , DataFlow:: CallCfgNode {
154
+ SessionConstruction ( ) {
155
+ this = classRef ( ) .getACall ( )
156
+ or
157
+ this =
158
+ API:: moduleImport ( "sqlalchemy" )
159
+ .getMember ( "orm" )
160
+ .getMember ( "sessionmaker" )
161
+ .getReturn ( )
162
+ .getACall ( )
163
+ }
164
+ }
165
+
166
+ /** Gets a reference to an instance of `sqlalchemy.orm.Session`. */
167
+ private DataFlow:: TypeTrackingNode instance ( DataFlow:: TypeTracker t ) {
168
+ t .start ( ) and
169
+ result instanceof InstanceSource
170
+ or
171
+ exists ( DataFlow:: TypeTracker t2 | result = instance ( t2 ) .track ( t2 , t ) )
172
+ }
173
+
174
+ /** Gets a reference to an instance of `sqlalchemy.orm.Session`. */
175
+ DataFlow:: Node instance ( ) { instance ( DataFlow:: TypeTracker:: end ( ) ) .flowsTo ( result ) }
76
176
}
77
177
78
178
/**
79
- * A call on a Query object
80
- * See https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query#sqlalchemy.orm.Query
179
+ * A call to `execute` on a SQLAlchemy Engine, Connection, or Session.
180
+ * See
181
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Engine.execute
182
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Connection.execute
183
+ * - https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Connection.execute
184
+ * - https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session.execute
81
185
*/
82
- private class SqlAlchemyQueryCall extends DataFlow:: CallCfgNode , SqlExecution:: Range {
83
- SqlAlchemyQueryCall ( ) {
84
- this =
85
- getSqlAlchemyQueryInstance ( )
86
- .getMember ( any ( SqlAlchemyVulnerableMethodNames methodName ) )
87
- .getACall ( )
186
+ private class SqlAlchemyExecuteCall extends DataFlow:: MethodCallNode , SqlExecution:: Range {
187
+ SqlAlchemyExecuteCall ( ) {
188
+ this .calls ( Engine:: instance ( ) , "execute" )
189
+ or
190
+ this .calls ( Connection:: instance ( ) , "execute" )
191
+ or
192
+ this .calls ( Session:: instance ( ) , "execute" )
88
193
}
89
194
90
- override DataFlow:: Node getSql ( ) { result = this .getArg ( 0 ) }
195
+ override DataFlow:: Node getSql ( ) { result in [ this .getArg ( 0 ) , this . getArgByName ( "statement" ) ] }
91
196
}
92
197
93
198
/**
94
- * This class represents a list of methods vulnerable to sql injection.
95
- *
96
- * See https://github.com/jty-team/codeql/pull/2#issue-611592361
199
+ * A call to `scalar` on a SQLAlchemy Engine, Connection, or Session.
200
+ * See
201
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Engine.scalar
202
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Connection.scalar
203
+ * - https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Connection.scalar
204
+ * - https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session.scalar
97
205
*/
98
- private class SqlAlchemyVulnerableMethodNames extends string {
99
- SqlAlchemyVulnerableMethodNames ( ) { this in [ "filter" , "filter_by" , "group_by" , "order_by" ] }
206
+ private class SqlAlchemyScalarCall extends DataFlow:: MethodCallNode , SqlExecution:: Range {
207
+ SqlAlchemyScalarCall ( ) {
208
+ this .calls ( Engine:: instance ( ) , "scalar" )
209
+ or
210
+ this .calls ( Connection:: instance ( ) , "scalar" )
211
+ or
212
+ this .calls ( Session:: instance ( ) , "scalar" )
213
+ }
214
+
215
+ override DataFlow:: Node getSql ( ) {
216
+ result in [ this .getArg ( 0 ) , this .getArgByName ( "statement" ) , this .getArgByName ( "object_" ) ]
217
+ }
100
218
}
101
219
102
220
/**
@@ -146,3 +264,47 @@ private module SqlAlchemy {
146
264
override DataFlow:: Node getAnInput ( ) { result = this .getArg ( 0 ) }
147
265
}
148
266
}
267
+
268
+ private module OldModeling {
269
+ /**
270
+ * Returns an instantization of a SqlAlchemy Session object.
271
+ * See https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session and
272
+ * https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.sessionmaker
273
+ */
274
+ private API:: Node getSqlAlchemySessionInstance ( ) {
275
+ result = API:: moduleImport ( "sqlalchemy.orm" ) .getMember ( "Session" ) .getReturn ( ) or
276
+ result = API:: moduleImport ( "sqlalchemy.orm" ) .getMember ( "sessionmaker" ) .getReturn ( ) .getReturn ( )
277
+ }
278
+
279
+ /**
280
+ * Returns an instantization of a SqlAlchemy Query object.
281
+ * See https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query#sqlalchemy.orm.Query
282
+ */
283
+ private API:: Node getSqlAlchemyQueryInstance ( ) {
284
+ result = getSqlAlchemySessionInstance ( ) .getMember ( "query" ) .getReturn ( )
285
+ }
286
+
287
+ /**
288
+ * A call on a Query object
289
+ * See https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query#sqlalchemy.orm.Query
290
+ */
291
+ private class SqlAlchemyQueryCall extends DataFlow:: CallCfgNode , SqlExecution:: Range {
292
+ SqlAlchemyQueryCall ( ) {
293
+ this =
294
+ getSqlAlchemyQueryInstance ( )
295
+ .getMember ( any ( SqlAlchemyVulnerableMethodNames methodName ) )
296
+ .getACall ( )
297
+ }
298
+
299
+ override DataFlow:: Node getSql ( ) { result = this .getArg ( 0 ) }
300
+ }
301
+
302
+ /**
303
+ * This class represents a list of methods vulnerable to sql injection.
304
+ *
305
+ * See https://github.com/jty-team/codeql/pull/2#issue-611592361
306
+ */
307
+ private class SqlAlchemyVulnerableMethodNames extends string {
308
+ SqlAlchemyVulnerableMethodNames ( ) { this in [ "filter" , "filter_by" , "group_by" , "order_by" ] }
309
+ }
310
+ }
0 commit comments