CodeQL for Java
.. rst-class:: setup
For this example you need to set up CodeQL for Visual Studio Code and download the CodeQL database for Apache Struts from GitHub.
.. rst-class:: agenda
- Global taint tracking
- Sanitizers
- Path queries
- Data flow models
- In April 2018, Man Yue Mo, a security researcher at Semmle, reported 5 remote code execution (RCE) vulnerabilities (CVE-2018-11776) in Apache Struts.
- These vulnerabilities were caused by untrusted, unsanitized data being evaluated as an OGNL (Object Graph Navigation Library) expression, allowing malicious users to perform remote code execution.
- Conceptually, this is a global taint tracking problem - does untrusted remote input flow to a method call which evaluates OGNL?
Note
More details on the CVE can be found here: https://securitylab.github.com/research/apache-struts-CVE-2018-11776 and https://github.com/github/securitylab/tree/main/CodeQL_Queries/java/Apache_Struts_CVE-2018-11776
More details on OGNL can be found here: https://commons.apache.org/proper/commons-ognl/
.. rst-class:: java-data-flow-code-example
.. literalinclude:: ../query-examples/java/global-data-flow-java-1.ql :language: ql
We want to look for method calls where the method name is getNamespace(), and the declaring type of the method is a class called ActionProxy.
import semmle.code.java.security.Security
module TaintedOGNLConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
exists(Method m |
m.getName() = "getNamespace" and
m.getDeclaringType().getName() = "ActionProxy" and
source.asExpr() = m.getAReference()
)
}
...
}Note
We first define what it means to be a source of tainted data for this particular problem. In this case, we are interested in the value returned by calls to getNamespace().
Fill in the definition of isSink.
Hint: We want to find the first argument of calls to the method compileAndExecute.
import semmle.code.java.security.Security
module TaintedOGNLConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) {
/* Fill me in */
}
...
}Note
The second part is to define what it means to be a sink for this particular problem. The queries from an :doc:`Introduction to data flow <data-flow-java>` will be useful for this exercise.
Find a method access to compileAndExecute, and mark the first argument.
import semmle.code.java.security.Security
module TaintedOGNLConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
ma.getMethod().getName() = "compileAndExecute" and
ma.getArgument(0) = sink.asExpr()
)
}
...
}A sanitizer allows us to prevent flow through a particular node in the graph. For example, flows that go via ValueStackShadowMap are not particularly interesting, because it is a class that is rarely used in practice. We can exclude them like so:
module TaintedOGNLConfig implements DataFlow::ConfigSig {
predicate isBarrier(DataFlow::Node nd) {
nd.getEnclosingCallable()
.getDeclaringType()
.getName() = "ValueStackShadowMap"
}
...
}Add an additional taint step that (heuristically) taints a local variable if it is a pointer, and it is passed to a function in a parameter position that taints it.
module TaintedOGNLConfig implements DataFlow::ConfigSig {
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(Field f, RefType t |
node1.asExpr() = f.getAnAssignedValue() and
node2.asExpr() = f.getAnAccess() and
node1.asExpr().getEnclosingCallable().getDeclaringType() = t and
node2.asExpr().getEnclosingCallable().getDeclaringType() = t
)
}
...
}.. rst-class:: end-slide