@@ -96,28 +96,40 @@ private module MySql {
9696 * Provides classes modelling the `pg` package.
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,126 @@ 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 ( ) { 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+ }
151272}
152273
153274/**
0 commit comments