@@ -96,28 +96,40 @@ private module MySql {
96
96
* Provides classes modelling the `pg` package.
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,126 @@ 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 ( ) { result = pgPromise ( ) .getReturn ( ) }
170
+
171
+ /** Gets a database from `pg-promise`. */
172
+ API:: Node pgpDatabase ( ) { result = pgpMain ( ) .getReturn ( ) }
173
+
174
+ /** Gets a connection created from a `pg-promise` database. */
175
+ API:: Node pgpConnection ( ) {
176
+ result = pgpDatabase ( ) .getMember ( "connect" ) .getReturn ( ) .getPromised ( )
177
+ }
178
+
179
+ /** Gets a `pg-promise` task object. */
180
+ API:: Node pgpTask ( ) {
181
+ exists ( API:: Node taskMethod |
182
+ taskMethod = pgpObject ( ) .getMember ( [ "task" , "taskIf" , "tx" , "txIf" ] )
183
+ |
184
+ result = taskMethod .getParameter ( [ 0 , 1 ] ) .getParameter ( 0 )
185
+ or
186
+ result = taskMethod .getParameter ( 0 ) .getMember ( "cnd" ) .getParameter ( 0 )
187
+ )
188
+ }
189
+
190
+ /** Gets a `pg-promise` object which supports querying (database, connection, or task). */
191
+ API:: Node pgpObject ( ) { result = [ pgpDatabase ( ) , pgpConnection ( ) , pgpTask ( ) ] }
192
+
193
+ private string pgpQueryMethodName ( ) {
194
+ result =
195
+ [
196
+ "any" , "each" , "many" , "manyOrNone" , "map" , "multi" , "multiResult" , "none" , "one" ,
197
+ "oneOrNone" , "query" , "result"
198
+ ]
199
+ }
200
+
201
+ /** A call that executes a SQL query via `pg-promise`. */
202
+ private class PgPromiseQueryCall extends DatabaseAccess , DataFlow:: MethodCallNode {
203
+ PgPromiseQueryCall ( ) { this = pgpObject ( ) .getMember ( pgpQueryMethodName ( ) ) .getACall ( ) }
204
+
205
+ /** Gets an argument interpreted as a SQL string, not including raw interpolation variables. */
206
+ private DataFlow:: Node getADirectQueryArgument ( ) {
207
+ result = getArgument ( 0 )
208
+ or
209
+ result = getOptionArgument ( 0 , "text" )
210
+ }
211
+
212
+ /**
213
+ * Gets an interpolation parameter whose value is interpreted literally, or is not escaped appropriately for its context.
214
+ *
215
+ * For example, the following are raw placeholders: $1:raw, $1^, ${prop}:raw, $(prop)^
216
+ */
217
+ private string getARawParameterName ( ) {
218
+ exists ( string sqlString , string placeholderRegexp , string regexp |
219
+ placeholderRegexp = "\\$(\\d+|[{(\\[/]\\w+[})\\]/])" and // For example: $1 or ${prop}
220
+ sqlString = getADirectQueryArgument ( ) .getStringValue ( )
221
+ |
222
+ // Match $1:raw or ${prop}:raw
223
+ regexp = placeholderRegexp + "(:raw|\\^)" and
224
+ result =
225
+ sqlString
226
+ .regexpFind ( regexp , _, _)
227
+ .regexpCapture ( regexp , 1 )
228
+ .regexpReplaceAll ( "[^\\w\\d]" , "" )
229
+ or
230
+ // Match $1:value or ${prop}:value unless enclosed by single quotes (:value prevents breaking out of single quotes)
231
+ regexp = placeholderRegexp + "(:value|\\#)" and
232
+ result =
233
+ sqlString
234
+ .regexpReplaceAll ( "'[^']*'" , "''" )
235
+ .regexpFind ( regexp , _, _)
236
+ .regexpCapture ( regexp , 1 )
237
+ .regexpReplaceAll ( "[^\\w\\d]" , "" )
238
+ )
239
+ }
240
+
241
+ /** Gets the argument holding the values to plug into placeholders. */
242
+ private DataFlow:: Node getValues ( ) {
243
+ result = getArgument ( 1 )
244
+ or
245
+ result = getOptionArgument ( 0 , "values" )
246
+ }
247
+
248
+ /** Gets a value that is plugged into a raw placeholder variable, making it a sink for SQL injection. */
249
+ private DataFlow:: Node getARawValue ( ) {
250
+ result = getValues ( ) and getARawParameterName ( ) = "1" // Special case: if the argument is not an array or object, it's just plugged into $1
251
+ or
252
+ exists ( DataFlow:: SourceNode values | values = getValues ( ) .getALocalSource ( ) |
253
+ result = values .getAPropertyWrite ( getARawParameterName ( ) ) .getRhs ( )
254
+ or
255
+ // Array literals do not have PropWrites with property names so handle them separately,
256
+ // and also translate to 0-based indexing.
257
+ result = values .( DataFlow:: ArrayCreationNode ) .getElement ( getARawParameterName ( ) .toInt ( ) - 1 )
258
+ )
259
+ }
260
+
261
+ override DataFlow:: Node getAQueryArgument ( ) {
262
+ result = getADirectQueryArgument ( )
263
+ or
264
+ result = getARawValue ( )
265
+ }
266
+ }
267
+
268
+ /** An expression that is interpreted as SQL by `pg-promise`. */
269
+ class PgPromiseQueryString extends SQL:: SqlString {
270
+ PgPromiseQueryString ( ) { this = any ( PgPromiseQueryCall qc ) .getAQueryArgument ( ) .asExpr ( ) }
271
+ }
151
272
}
152
273
153
274
/**
0 commit comments