@@ -4,9 +4,9 @@ import java
44private import semmle.code.java.frameworks.spring.SpringController
55private import semmle.code.java.frameworks.MyBatis
66private import semmle.code.java.frameworks.Jdbc
7- private import semmle.code.java.dataflow.DataFlow
87private import semmle.code.java.dataflow.ExternalFlow
98private import semmle.code.java.dispatch.VirtualDispatch
9+ private import semmle.code.java.dataflow.TaintTracking
1010
1111/** A method that is not protected from CSRF by default. */
1212abstract class CsrfUnprotectedMethod extends Method { }
@@ -66,24 +66,46 @@ private class PreparedStatementDatabaseUpdateMethod extends DatabaseUpdateMethod
6666 }
6767}
6868
69- /** A method that updates a SQL database. */
70- private class SqlDatabaseUpdateMethod extends DatabaseUpdateMethod {
71- SqlDatabaseUpdateMethod ( ) {
72- // TODO: constrain to only insert/update/delete for `execute%` methods; need to track the sql expression into the execute call.
69+ /** A method found via the sql-injection models which may update a SQL database. */
70+ private class SqlInjectionMethod extends DatabaseUpdateMethod {
71+ SqlInjectionMethod ( ) {
7372 exists ( DataFlow:: Node n | this = n .asExpr ( ) .( Argument ) .getCall ( ) .getCallee ( ) |
7473 sinkNode ( n , "sql-injection" ) and
75- this .getName ( )
76- .regexpMatch ( ".*(?i)(delete|insert|update|save|persist|merge|replicate|execute).*" )
74+ this .getName ( ) .regexpMatch ( ".*(?i)(delete|insert|update|save|persist|merge|replicate).*" )
75+ or
76+ // do not include `executeQuery` since it is typically used with a select statement
77+ this .hasName ( "execute" )
78+ )
79+ }
80+ }
81+
82+ /**
83+ * A taint-tracking configuration for reasoning about SQL queries that update a database.
84+ */
85+ module SqlExecuteConfig implements DataFlow:: ConfigSig {
86+ predicate isSource ( DataFlow:: Node source ) {
87+ exists ( StringLiteral sl | source .asExpr ( ) = sl |
88+ sl .getValue ( ) .regexpMatch ( "^(?i)(insert|update|delete).*" )
89+ )
90+ }
91+
92+ predicate isSink ( DataFlow:: Node sink ) {
93+ exists ( Method m | m = sink .asExpr ( ) .( Argument ) .getCall ( ) .getCallee ( ) |
94+ m instanceof SqlInjectionMethod and
95+ m .hasName ( "execute" )
7796 )
7897 }
7998}
8099
100+ /** Tracks flow from SQL queries that update a database to the argument of an execute method call. */
101+ module SqlExecuteFlow = TaintTracking:: Global< SqlExecuteConfig > ;
102+
81103module CallGraph {
82- newtype TPathNode =
104+ newtype TCallPathNode =
83105 TMethod ( Method m ) or
84106 TCall ( Call c )
85107
86- class PathNode extends TPathNode {
108+ class CallPathNode extends TCallPathNode {
87109 Method asMethod ( ) { this = TMethod ( result ) }
88110
89111 Call asCall ( ) { this = TCall ( result ) }
@@ -94,16 +116,16 @@ module CallGraph {
94116 result = this .asCall ( ) .toString ( )
95117 }
96118
97- private PathNode getACallee ( ) {
119+ private CallPathNode getACallee ( ) {
98120 [ viableCallable ( this .asCall ( ) ) , this .asCall ( ) .getCallee ( ) ] = result .asMethod ( )
99121 }
100122
101- PathNode getASuccessor ( ) {
123+ CallPathNode getASuccessor ( ) {
102124 this .asMethod ( ) = result .asCall ( ) .getEnclosingCallable ( )
103125 or
104126 result = this .getACallee ( ) and
105127 (
106- exists ( PathNode p |
128+ exists ( CallPathNode p |
107129 p = this .getACallee ( ) and
108130 p .asMethod ( ) instanceof DatabaseUpdateMethod
109131 )
@@ -119,15 +141,25 @@ module CallGraph {
119141 }
120142 }
121143
122- predicate edges ( PathNode pred , PathNode succ ) { pred .getASuccessor ( ) = succ }
144+ predicate edges ( CallPathNode pred , CallPathNode succ ) { pred .getASuccessor ( ) = succ }
123145}
124146
125147import CallGraph
126148
127149/** Holds if `src` is an unprotected request handler that reaches a state-changing `sink`. */
128- predicate unprotectedStateChange ( PathNode src , PathNode sink , PathNode sinkPred ) {
150+ predicate unprotectedStateChange ( CallPathNode src , CallPathNode sink , CallPathNode sinkPred ) {
129151 src .asMethod ( ) instanceof CsrfUnprotectedMethod and
130152 sink .asMethod ( ) instanceof DatabaseUpdateMethod and
131153 sinkPred .getASuccessor ( ) = sink and
132- src .getASuccessor + ( ) = sinkPred
154+ src .getASuccessor + ( ) = sinkPred and
155+ if
156+ sink .asMethod ( ) instanceof SqlInjectionMethod and
157+ sink .asMethod ( ) .hasName ( "execute" )
158+ then
159+ exists ( SqlExecuteFlow:: PathNode executeSrc , SqlExecuteFlow:: PathNode executeSink |
160+ SqlExecuteFlow:: flowPath ( executeSrc , executeSink )
161+ |
162+ sinkPred .asCall ( ) = executeSink .getNode ( ) .asExpr ( ) .( Argument ) .getCall ( )
163+ )
164+ else any ( )
133165}
0 commit comments