@@ -93,31 +93,43 @@ private module MySql {
9393}
9494
9595/**
96- * Provides classes modelling the `pg` package .
96+ * Provides classes modelling the PostgreSQL packages, such as `pg` and `pg-promise` .
9797 */
9898private module Postgres {
99+ API:: Node pg ( ) {
100+ result = API:: moduleImport ( "pg" )
101+ or
102+ result = pgpMain ( ) .getMember ( "pg" )
103+ }
104+
99105 /** 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" ) }
101107
102108 /** Gets a freshly created Postgres client instance. */
103109 API:: Node client ( ) {
104110 result = newClient ( ) .getInstance ( )
105111 or
106112 // pool.connect(function(err, client) { ... })
107113 result = pool ( ) .getMember ( "connect" ) .getParameter ( 0 ) .getParameter ( 1 )
114+ or
115+ result = pgpConnection ( ) .getMember ( "client" )
108116 }
109117
110118 /** Gets a constructor that when invoked constructs a new connection pool. */
111119 API:: Node newPool ( ) {
112120 // new require('pg').Pool()
113- result = API :: moduleImport ( "pg" ) .getMember ( "Pool" )
121+ result = pg ( ) .getMember ( "Pool" )
114122 or
115123 // new require('pg-pool')
116124 result = API:: moduleImport ( "pg-pool" )
117125 }
118126
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+ }
121133
122134 /** A call to the Postgres `query` method. */
123135 private class QueryCall extends DatabaseAccess , DataFlow:: MethodCallNode {
@@ -137,17 +149,142 @@ private module Postgres {
137149
138150 Credentials ( ) {
139151 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
146159 )
147160 }
148161
149162 override string getCredentialsKind ( ) { result = kind }
150163 }
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+ }
151288}
152289
153290/**
0 commit comments