@@ -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,10 +66,9 @@ 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
7574 // do not include `executeQuery` since it is typically used with a select statement
@@ -81,12 +80,33 @@ private class SqlDatabaseUpdateMethod extends DatabaseUpdateMethod {
8180 }
8281}
8382
83+ /**
84+ * A taint-tracking configuration for reasoning about SQL queries that update a database.
85+ */
86+ module SqlExecuteConfig implements DataFlow:: ConfigSig {
87+ predicate isSource ( DataFlow:: Node source ) {
88+ exists ( StringLiteral sl | source .asExpr ( ) = sl |
89+ sl .getValue ( ) .regexpMatch ( "^(?i)(insert|update|delete).*" )
90+ )
91+ }
92+
93+ predicate isSink ( DataFlow:: Node sink ) {
94+ exists ( Method m | m = sink .asExpr ( ) .( Argument ) .getCall ( ) .getCallee ( ) |
95+ m instanceof SqlInjectionMethod and
96+ m .hasName ( "execute" )
97+ )
98+ }
99+ }
100+
101+ /** Tracks flow from SQL queries that update a database to the argument of an execute method call. */
102+ module SqlExecuteFlow = TaintTracking:: Global< SqlExecuteConfig > ;
103+
84104module CallGraph {
85- newtype TPathNode =
105+ newtype TCallPathNode =
86106 TMethod ( Method m ) or
87107 TCall ( Call c )
88108
89- class PathNode extends TPathNode {
109+ class CallPathNode extends TCallPathNode {
90110 Method asMethod ( ) { this = TMethod ( result ) }
91111
92112 Call asCall ( ) { this = TCall ( result ) }
@@ -97,16 +117,16 @@ module CallGraph {
97117 result = this .asCall ( ) .toString ( )
98118 }
99119
100- private PathNode getACallee ( ) {
120+ private CallPathNode getACallee ( ) {
101121 [ viableCallable ( this .asCall ( ) ) , this .asCall ( ) .getCallee ( ) ] = result .asMethod ( )
102122 }
103123
104- PathNode getASuccessor ( ) {
124+ CallPathNode getASuccessor ( ) {
105125 this .asMethod ( ) = result .asCall ( ) .getEnclosingCallable ( )
106126 or
107127 result = this .getACallee ( ) and
108128 (
109- exists ( PathNode p |
129+ exists ( CallPathNode p |
110130 p = this .getACallee ( ) and
111131 p .asMethod ( ) instanceof DatabaseUpdateMethod
112132 )
@@ -122,15 +142,25 @@ module CallGraph {
122142 }
123143 }
124144
125- predicate edges ( PathNode pred , PathNode succ ) { pred .getASuccessor ( ) = succ }
145+ predicate edges ( CallPathNode pred , CallPathNode succ ) { pred .getASuccessor ( ) = succ }
126146}
127147
128148import CallGraph
129149
130150/** Holds if `src` is an unprotected request handler that reaches a state-changing `sink`. */
131- predicate unprotectedStateChange ( PathNode src , PathNode sink , PathNode sinkPred ) {
151+ predicate unprotectedStateChange ( CallPathNode src , CallPathNode sink , CallPathNode sinkPred ) {
132152 src .asMethod ( ) instanceof CsrfUnprotectedMethod and
133153 sink .asMethod ( ) instanceof DatabaseUpdateMethod and
134154 sinkPred .getASuccessor ( ) = sink and
135- src .getASuccessor + ( ) = sinkPred
155+ src .getASuccessor + ( ) = sinkPred and
156+ if
157+ sink .asMethod ( ) instanceof SqlInjectionMethod and
158+ sink .asMethod ( ) .hasName ( "execute" )
159+ then
160+ exists ( SqlExecuteFlow:: PathNode executeSrc , SqlExecuteFlow:: PathNode executeSink |
161+ SqlExecuteFlow:: flowPath ( executeSrc , executeSink )
162+ |
163+ sinkPred .asCall ( ) = executeSink .getNode ( ) .asExpr ( ) .( Argument ) .getCall ( )
164+ )
165+ else any ( )
136166}
0 commit comments