In this workshop we will use syntactical and semantic reasoning to find a SQL injection in the XWiki platform's rating component documented by CVE-2021-21380
Please complete this section before the workshop, if possible.
-
Install Visual Studio Code.
-
Install the CodeQL extension for Visual Studio Code.
-
You do not need to install the CodeQL CLI: the extension will handle this for you.
-
Clone this repository:
git clone https://github.com/advanced-security/codeql-workshop-cve-2021-21380.git
- Import the CodeQL database to be used in the workshop:
- Right-click on the
xwiki-platform-CVE-2021-21380.zip
file in the Explorer view and select the commandCodeQL: Set Current Database
. - The database will show up in the CodeQL databases view reachable from the QL icon on the Activity Bar.
- Right-click on the
- Install the dependencies for analyzing Java code and to run the tests for the exercises and solutions.
- From the Command Palette (
Cmd/Ctrl+Shift+P
), search for and run the commandCodeQL: Install Pack Dependencies
. - At the top of your VS Code window, type
github
in the box to filter the list. - Check the box next to
cve-2021-21380-exercises
,cve-2021-21380-exercises-tests
,cve-2021-21380-solutions
, andcve-2021-21380-solutions-tests
. - Click OK/Enter.
- From the Command Palette (
- Validate everything works as expected by running the tests for the solutions.
- Open the view testing and press run tests (play icon is shown when hovering solutions) next to the solutions item in the tree.
The workshop is split into several exercises introducing the QL language support for Java and ends with a final query to find the known SQL injection. In these exercises you will learn:
- How to reason about syntactic information.
- How to reason about semantic information.
- Explore the QL language support for Java to express patterns.
- Explore how to reuse and extend existing modelling.
- Use multiple building blocks to compose the final query.
In this workshop we will look for known SQL injection vulnerabilities in the XWiki Platform's ratings API component. Such vulnerabilities can occur in applications when information that is controlled by an external user makes its way to application code that insecurely construct a SQL query and executes it.
The known SQL injection discussed in this workshop is reviewed in GHSA-79rg-7mv3-jrr5 in GitHub Advisory Database. To find the SQL injection, and possible variants, we are going to the following sub-problems:
- Identify the source of intrusted information and model it in QL.
- Identify the sink, the method executing SQL queries, and model it in QL.
- Combine the above solutions to determine if there is a flow of information between the source and the sink using taint tracking.
In the first few exercises we will reason about syntactic information, using the Abstract Syntax Tree (AST), to identify:
- the method described in the security advisory to build understanding of the vulnerability
- parameters that contain untrusted data, our sources
- method calls that accept SQL statements, our sinks
Find all methods with the name getAverageRating
and its declaring type in the program by completing the query exercise1.ql
Hints
- The
java
module provides a classMethod
to reason about methods in a program. - The class
Method
provides the member predicatesgetName
andhasName
to reason about the name of a method. - The class
Method
provides the membergetDeclaringType
to reason about the type that declares the method.
A solution can be found in the query exercise1.ql
A solution to exercise 1 returns a list of methods. Some of which are defined in an interface called RatingsManager
and some which are defined in the classes AbstractRatingsManager
and RatingsScriptService
.
From the information returned by the query and XWiki component documentation we can deduce that:
- XWiki uses a component oriented design to allow for extensions and customizations.
- The vulnerable method is part of a component.
- A component consist of an interface, annotated with
Role
, and an implementation annotated withComponent
. - A component that extends
ScriptService
are made accessible to wiki pages through scripting.
Find all the classes annotated with the annotation Component
by completing the query exercise2.ql.
Note that the fully qualified name of the annotation's type is org.xwiki.component.annotation.Component
.
Hints
- The /class domain type/, the intersection of its super types, can be accessed using the keyword
this
in the /characteristic predicate/. - The
Class
class provides a methodgetAnAnnotation
to get associated annotations. - The
Annotation
class provides thegetType
member predicate to reason about its type. - The
Type
class provides the member predicatesgetName
andhasName
to reason about the name of a type. - The
RefType
class, representing classes and interfaces, provides the member predicatesgetQualifiedName
andhasQualifiedName
to reason about the fully qualified name of the reftype.
A solution can be found in the query exercise2.ql
Find all the components that implement the ScriptService
interface by completing the query excercise3.ql
Hints
- The
Class
type provides the member predicategetASuperType
to reason about a class its super types, that is types itextends
orimplements
.
A solution can be found in the query exercise3.ql
At this point we have syntactically identified the methods that can be called by a user and whose parameters we will consider sources of untrusted data further on in the workshop.
In the next exercise we are going to investigate and identify possible sinks. From the results of exercise 1 we can deduce that one of the implementations calls the method getAverageRatingFromQuery
. Using a similar query we can find the declaring types of getAverageRatingFromQuery
which allows us to establish that the implementation in the class AbstractRatingsManager
constructs a SQL statement that is passed to the search
method.
The search
method is implemented in a dependency and thus its implementation is not available. In the next exercises we are going to use the available type information to identify this search
method call and its declaring type.
Find all methods calls to the method search
and identify its declaring type by completing the query exercise4.ql
Hints
- The
MethodAccess
type provide us with the means to reason about method calls. - The
MethodAcccess
type provides the member predicategetMethod
to reason about the target of a method call.
A solution can be found in the query exercise4.ql
Find all the method calls to methods declared by the interface XWikiStorageInterface
(with the qualified name com.xpn.xwiki.store.XWikiStoreInterface
) by completing the query exercise5.ql
Hints
-
The
instanceof
keyword can be used to state that a value belongs to the set of values represented by a type. For example, to identify all the calls to interface methods:import java from MethodAccess ma where ma.getMethod.getDeclaringType() instanceof Interface select ma
A solution can be found in the query exercise5.ql
At this point we have syntactically described possible sources and sink using QL. To determine if information flows between these points in the program we are going to semantically analyze the program using taint tracking. The standard libraries of the languages we support provide two data flow mechanism:
- The module
DataFlow
supports value preserving flow of information. - The module
TaintTracking
supports flow of information even if values are modified.
The latter is of interest, because in injection vulnerability such as SQL injection it is common for untrusted data to become part of a larger statement that is acted upon.
In this workshop we are going to reuse an existing SQL injection taint tracking configuration and extend it with our modelled sources and sinks to find the SQL injection. To understand how we can extend the configuration we start with looking at the definition of the configuration below.
class QueryInjectionFlowConfig extends TaintTracking::Configuration {
QueryInjectionFlowConfig() { this = "SqlInjectionLib::QueryInjectionFlowConfig" }
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink }
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
any(AdditionalQueryInjectionTaintStep s).step(node1, node2)
}
}
Both the isSource
and isSink
predicate used by the configuration to identify those program location use the instanceof
keyword that we have seen before. Both the RemoteFlowSource
and QueryInjectionSink
classes are abstract
classes.
This is a common pattern that you will encounter in the standard libraries and this pattern allow us to extend the set of values represented by the RemoteFlowSource
and QueryInjectionSink
classes.
A QL class
extending an abstract class
does not refine the set of value represented by the super class, but adds the values represented by the subclass to the superclass.
In the next exercises we are going to implement these subclasses and construct our final query.
Extend the RemoteFlowSource
's value set with the parameters of the public methods of the component classes identified in exercise 3 by completing the query exercise6.ql.
Hints
-
The
instanceof
keyword can be used to state that a value belongs to the set of values represented by a type. For example, to identify all the calls to interface methods:import java from MethodAccess ma where ma.getMethod.getDeclaringType() instanceof Interface select ma
A solution can be found in the query exercise6.ql
Extend the QueryInjectionSink
's value set with the arguments of calls to the methods of the storage interface identified in exercise 5 by completing the query exercise7.ql
Hints
-
The
exists
formula allows for the introduction of temporary variable that can be reasoned about in the scope of theexists
.The following example uses the
exists
expression to reduce the set of methods to methods that are called.from Method m where m.hasName("foo") and exists(MethodAccess ma | ma.getMethod() = m) select m
-
The
MethodAccess
class provides the member predicategetQualifer
to reason about the qualifier of the method access.
A solution can be found in the query exercise7.ql
Combine your solutions from the previous exercise into a final solution by completing by completing the query exercise8.ql.
A solution can be found in the query exercise8.ql
- The query includes a module to model parts of the XWiki framework. Refactor this into its own module file and use it in your query.
- We limited our source of untrusted data to script service components. Look at how to expand this with other sources.
- For the sink we limited ourselves to direct usage of the
XWikiStoreInterface
. Expand the sink to include direct uses of implementation of the interface. TheClass
class provides the member predicate extendsOrImplements and theMethod
class provides the member predicate overridesOrInstantiates that may be of help. - Use the definition of sources to find interesting uses of untrusted data with the query 'Untrusted data passed to external API '