Introduction
In this post, I will walk you through a journey that starts analyzing CVE-2022–22980 and extracting a pattern of this vulnerability to query a codebase to discover the true exploitation path.
While many vulnerability scanning tools are powerful in detecting vulnerable components in an application, some of them fail to prove whether the vulnerability is exploitable by a threat.
As a powerful ally for this task, there is CodeQL. It is a semantic code analysis engine open source from GitHub that lets query your codebase as it were data. Thus, it is a crucial tool that allows us to search for a pattern of vulnerability in the entire source code.
In the creation process, it is possible to refine the query until just exploitable paths are discovered, which will be demonstrated in this article.
Analysis of CVE-2022–22980
While reading some security vulnerabilities bulletins, CVE-2022–22980 caught my attention. It treats about a fix in an Expression Language injection in Spring Data MongoDB that could lead to remote code execution.
So, reviewing the announcement provided by VMWare some points were identified that could lead to a vulnerable scenario.
A repository query method is annotated with @Query or @AggregationThe annotated query or aggregation value/pipeline contains SpEL parts using the parameter placeholder syntax within the expression
The user supplied input is not sanitized by the application
Accomplishing these conditions and using the Spring Data MongoDB in both 3.4.0, 3.3.0 to 3.3.4 and older unsupported versions, the application is prone to vulnerability. Thus, the next step will turn these conditions into a pattern and build a CodeQL query to search the entire codebase.
Building a CodeQL query
The first step in building this query is to determine which user inputs are used by the application. So RemoteFlowSources
is a good point for this task. It is a Class defined by CodeQL that maps a data flow source of remote user input.
So in the first part of this query, we needed to do the imports and define that sources are equal to remote flow sources.
/**
*@kind path-problem
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.DataFlow
import DataFlow::PathGraphclass MyConf extends DataFlow::Configuration {
MyConf() { this = "MyConf" }
override predicate isSource(DataFlow::Node source) {
exists(RemoteFlowSource remote | source.asParameter() = remote.asParameter())
}
Once defined which are the sources is time to build a sink part of the query. In this part, the conditions of exploitability must be met. Thus, three variables were defined to conduct this phase. One for methods that have annotations assigned, the other for the names of the annotations, and the last one for finding access to methods with assigned annotations.
After defining the variables, it is necessary to perform conditional operations that find the correct sink. First, it was compared if the method annotation was equal to the annotation defined by us, then it was compared if the annotation was equal to both Query or Aggregation, then a search via regex in annotation for values that contain ?[0-9]
, finally it is defined that the access to the method must be equal to the method defined by us and the argument of this access must be equal to our sink.
override predicate isSink(DataFlow::Node sink) {
exists(Method c, Annotation ann, MethodAccess ma | ann = c.getAnAnnotation() and (ann.toString() = "Query" or ann.toString() = "Aggregation") and ann.getAValue().toString().regexpMatch(".*\\?[0-9].*")
and ma.getMethod() = c and ma.getAnArgument()
= sink.asExpr())
}
At the end of the query, the portion that connects all the points of our constructed query was defined. The final query looks like this:
/**
*@kind path-problem
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.DataFlow
import DataFlow::PathGraphclass MyConf extends DataFlow::Configuration {
MyConf() { this = "MyConf" }
override predicate isSource(DataFlow::Node source) {
exists(RemoteFlowSource remote | source.asParameter() = remote.asParameter())
}override predicate isSink(DataFlow::Node sink) {
exists(Method c, Annotation ann, MethodAccess ma | ann = c.getAnAnnotation() and (ann.toString() = "Query" or ann.toString() = "Aggregation") and ann.getAValue().toString().regexpMatch(".*\\?[0-9].*")
and ma.getMethod() = c and ma.getAnArgument()
= sink.asExpr())
}
}from MyConf conf, DataFlow::PathNode source, DataFlow::PathNode sink
where conf.hasFlowPath(source, sink)
select sink, source, sink, "SpEL Injection found! - CVE-2022-22980"
Executing the query in CodeQL
For this phase, we used the vulnerable public application designed for this security issue, which will serve as our codebase. Thus, the CodeQL database was created from the codebase that was defined.
codeql database create cveanalysis --language=java
Once the database is created, it’s time to import it into VSCode’s CodeQL extension to run our query.
VSCode after database import and query filled
After selecting the recently uploaded database, it is time to go to the VSCode workspace for CodeQL in the Java tab and edit the default template with our query. Finally can run our query in the codebase.
Results of query execution
It is the possible view that there is a real path exploitable for the vulnerability in this codebase. It is the possible view that there is a real exploitable path to the vulnerability in this codebase. The results give us the source, drain, and intermediate steps if any. In this case, there is a simple path between the source and sink.
source and sink
Validating the Exploitable Path
As identified in prior steps this vulnerability receives a parameter in its /test endpoint and passes it to the vulnerable function. As identified in the previous steps, this vulnerability receives a POST parameter on its /testendpoint and passes it to the vulnerable function. So by running the following command, the vulnerability is achieved:
cURL command with malicious payload
With this, we can constant that the file was created in the temporary directory and the remote code execution was achieved.
After and before code execution
Conclusion
It is also important to determine if a vulnerability is exploitable in your environment once this influence a Risk Management process.
More about CVE-2022–22980 on this previous post 👇