@@ -93,31 +93,43 @@ private module MySql {
93
93
}
94
94
95
95
/**
96
- * Provides classes modelling the `pg` package .
96
+ * Provides classes modelling the PostgreSQL packages, such as `pg` and `pg-promise` .
97
97
*/
98
98
private module Postgres {
99
+ API:: Node pg ( ) {
100
+ result = API:: moduleImport ( "pg" )
101
+ or
102
+ result = pgpMain ( ) .getMember ( "pg" )
103
+ }
104
+
99
105
/** Gets a reference to the `Client` constructor in the `pg` package, for example `require('pg').Client`. */
100
- API:: Node newClient ( ) { result = API :: moduleImport ( "pg" ) .getMember ( "Client" ) }
106
+ API:: Node newClient ( ) { result = pg ( ) .getMember ( "Client" ) }
101
107
102
108
/** Gets a freshly created Postgres client instance. */
103
109
API:: Node client ( ) {
104
110
result = newClient ( ) .getInstance ( )
105
111
or
106
112
// pool.connect(function(err, client) { ... })
107
113
result = pool ( ) .getMember ( "connect" ) .getParameter ( 0 ) .getParameter ( 1 )
114
+ or
115
+ result = pgpConnection ( ) .getMember ( "client" )
108
116
}
109
117
110
118
/** Gets a constructor that when invoked constructs a new connection pool. */
111
119
API:: Node newPool ( ) {
112
120
// new require('pg').Pool()
113
- result = API :: moduleImport ( "pg" ) .getMember ( "Pool" )
121
+ result = pg ( ) .getMember ( "Pool" )
114
122
or
115
123
// new require('pg-pool')
116
124
result = API:: moduleImport ( "pg-pool" )
117
125
}
118
126
119
- /** Gets an expression that constructs a new connection pool. */
120
- API:: Node pool ( ) { result = newPool ( ) .getInstance ( ) }
127
+ /** Gets an API node that refers to a connection pool. */
128
+ API:: Node pool ( ) {
129
+ result = newPool ( ) .getInstance ( )
130
+ or
131
+ result = pgpDatabase ( ) .getMember ( "$pool" )
132
+ }
121
133
122
134
/** A call to the Postgres `query` method. */
123
135
private class QueryCall extends DatabaseAccess , DataFlow:: MethodCallNode {
@@ -137,17 +149,142 @@ private module Postgres {
137
149
138
150
Credentials ( ) {
139
151
exists ( string prop |
140
- this = [ newClient ( ) , newPool ( ) ] .getParameter ( 0 ) .getMember ( prop ) .getARhs ( ) .asExpr ( ) and
141
- (
142
- prop = "user" and kind = "user name"
143
- or
144
- prop = "password" and kind = prop
145
- )
152
+ this = [ newClient ( ) , newPool ( ) ] .getParameter ( 0 ) .getMember ( prop ) .getARhs ( ) .asExpr ( )
153
+ or
154
+ this = pgPromise ( ) .getParameter ( 0 ) .getMember ( prop ) .getARhs ( ) .asExpr ( )
155
+ |
156
+ prop = "user" and kind = "user name"
157
+ or
158
+ prop = "password" and kind = prop
146
159
)
147
160
}
148
161
149
162
override string getCredentialsKind ( ) { result = kind }
150
163
}
164
+
165
+ /** Gets a node referring to the `pg-promise` library (which is not itself a Promise). */
166
+ API:: Node pgPromise ( ) { result = API:: moduleImport ( "pg-promise" ) }
167
+
168
+ /** Gets an initialized `pg-promise` library. */
169
+ API:: Node pgpMain ( ) {
170
+ result = pgPromise ( ) .getReturn ( )
171
+ or
172
+ result = API:: Node:: ofType ( "pg-promise" , "IMain" )
173
+ }
174
+
175
+ /** Gets a database from `pg-promise`. */
176
+ API:: Node pgpDatabase ( ) {
177
+ result = pgpMain ( ) .getReturn ( )
178
+ or
179
+ result = API:: Node:: ofType ( "pg-promise" , "IDatabase" )
180
+ }
181
+
182
+ /** Gets a connection created from a `pg-promise` database. */
183
+ API:: Node pgpConnection ( ) {
184
+ result = pgpDatabase ( ) .getMember ( "connect" ) .getReturn ( ) .getPromised ( )
185
+ or
186
+ result = API:: Node:: ofType ( "pg-promise" , "IConnected" )
187
+ }
188
+
189
+ /** Gets a `pg-promise` task object. */
190
+ API:: Node pgpTask ( ) {
191
+ exists ( API:: Node taskMethod |
192
+ taskMethod = pgpObject ( ) .getMember ( [ "task" , "taskIf" , "tx" , "txIf" ] )
193
+ |
194
+ result = taskMethod .getParameter ( [ 0 , 1 ] ) .getParameter ( 0 )
195
+ or
196
+ result = taskMethod .getParameter ( 0 ) .getMember ( "cnd" ) .getParameter ( 0 )
197
+ )
198
+ or
199
+ result = API:: Node:: ofType ( "pg-promise" , "ITask" )
200
+ }
201
+
202
+ /** Gets a `pg-promise` object which supports querying (database, connection, or task). */
203
+ API:: Node pgpObject ( ) {
204
+ result = [ pgpDatabase ( ) , pgpConnection ( ) , pgpTask ( ) ]
205
+ or
206
+ result = API:: Node:: ofType ( "pg-promise" , "IBaseProtocol" )
207
+ }
208
+
209
+ private string pgpQueryMethodName ( ) {
210
+ result =
211
+ [
212
+ "any" , "each" , "many" , "manyOrNone" , "map" , "multi" , "multiResult" , "none" , "one" ,
213
+ "oneOrNone" , "query" , "result"
214
+ ]
215
+ }
216
+
217
+ /** A call that executes a SQL query via `pg-promise`. */
218
+ private class PgPromiseQueryCall extends DatabaseAccess , DataFlow:: MethodCallNode {
219
+ PgPromiseQueryCall ( ) { this = pgpObject ( ) .getMember ( pgpQueryMethodName ( ) ) .getACall ( ) }
220
+
221
+ /** Gets an argument interpreted as a SQL string, not including raw interpolation variables. */
222
+ private DataFlow:: Node getADirectQueryArgument ( ) {
223
+ result = getArgument ( 0 )
224
+ or
225
+ result = getOptionArgument ( 0 , "text" )
226
+ }
227
+
228
+ /**
229
+ * Gets an interpolation parameter whose value is interpreted literally, or is not escaped appropriately for its context.
230
+ *
231
+ * For example, the following are raw placeholders: $1:raw, $1^, ${prop}:raw, $(prop)^
232
+ */
233
+ private string getARawParameterName ( ) {
234
+ exists ( string sqlString , string placeholderRegexp , string regexp |
235
+ placeholderRegexp = "\\$(\\d+|[{(\\[/]\\w+[})\\]/])" and // For example: $1 or ${prop}
236
+ sqlString = getADirectQueryArgument ( ) .getStringValue ( )
237
+ |
238
+ // Match $1:raw or ${prop}:raw
239
+ regexp = placeholderRegexp + "(:raw|\\^)" and
240
+ result =
241
+ sqlString
242
+ .regexpFind ( regexp , _, _)
243
+ .regexpCapture ( regexp , 1 )
244
+ .regexpReplaceAll ( "[^\\w\\d]" , "" )
245
+ or
246
+ // Match $1:value or ${prop}:value unless enclosed by single quotes (:value prevents breaking out of single quotes)
247
+ regexp = placeholderRegexp + "(:value|\\#)" and
248
+ result =
249
+ sqlString
250
+ .regexpReplaceAll ( "'[^']*'" , "''" )
251
+ .regexpFind ( regexp , _, _)
252
+ .regexpCapture ( regexp , 1 )
253
+ .regexpReplaceAll ( "[^\\w\\d]" , "" )
254
+ )
255
+ }
256
+
257
+ /** Gets the argument holding the values to plug into placeholders. */
258
+ private DataFlow:: Node getValues ( ) {
259
+ result = getArgument ( 1 )
260
+ or
261
+ result = getOptionArgument ( 0 , "values" )
262
+ }
263
+
264
+ /** Gets a value that is plugged into a raw placeholder variable, making it a sink for SQL injection. */
265
+ private DataFlow:: Node getARawValue ( ) {
266
+ result = getValues ( ) and getARawParameterName ( ) = "1" // Special case: if the argument is not an array or object, it's just plugged into $1
267
+ or
268
+ exists ( DataFlow:: SourceNode values | values = getValues ( ) .getALocalSource ( ) |
269
+ result = values .getAPropertyWrite ( getARawParameterName ( ) ) .getRhs ( )
270
+ or
271
+ // Array literals do not have PropWrites with property names so handle them separately,
272
+ // and also translate to 0-based indexing.
273
+ result = values .( DataFlow:: ArrayCreationNode ) .getElement ( getARawParameterName ( ) .toInt ( ) - 1 )
274
+ )
275
+ }
276
+
277
+ override DataFlow:: Node getAQueryArgument ( ) {
278
+ result = getADirectQueryArgument ( )
279
+ or
280
+ result = getARawValue ( )
281
+ }
282
+ }
283
+
284
+ /** An expression that is interpreted as SQL by `pg-promise`. */
285
+ class PgPromiseQueryString extends SQL:: SqlString {
286
+ PgPromiseQueryString ( ) { this = any ( PgPromiseQueryCall qc ) .getAQueryArgument ( ) .asExpr ( ) }
287
+ }
151
288
}
152
289
153
290
/**
0 commit comments