
Un taller de CodeQL que cubre CVE-2021-21380
En este taller utilizaremos el razonamiento sintáctico y semántico para encontrar una inyección SQL en el componente de calificación de la plataforma XWiki documentado por CVE-2021-21380
Por favor, completa esta sección antes del taller, si es posible.
Instala Visual Studio Code.
Instala la extensión CodeQL para Visual Studio Code.
No necesitas instalar la CLI de CodeQL: la extensión se encargará de esto por ti.
Clona este repositorio:
git clone https://github.com/advanced-security/codeql-workshop-cve-2021-21380.git
xwiki-platform-CVE-2021-21380.zip en la vista del Explorador y selecciona el comando CodeQL: Set Current Database.Cmd/Ctrl+Mayús+P), busca y ejecuta el comando CodeQL: Install Pack Dependencies.github en la caja para filtrar la lista.cve-2021-21380-exercises, cve-2021-21380-exercises-tests, cve-2021-21380-solutions y cve-2021-21380-solutions-tests.El taller está dividido en varios ejercicios que introducen el soporte del lenguaje QL para Java y termina con una consulta final para encontrar la inyección SQL conocida. En estos ejercicios aprenderás:
En este taller buscaremos vulnerabilidades de inyección SQL conocidas en el componente de API de calificaciones de la Plataforma XWiki. Tales vulnerabilidades pueden ocurrir en aplicaciones cuando información controlada por un usuario externo llega al código de la aplicación que construye de manera insegura una consulta SQL y la ejecuta.
La inyección SQL conocida que se discute en este taller está revisada en GHSA-79rg-7mv3-jrr5 en la Base de Datos de Avisos de GitHub. Para encontrar la inyección SQL, y posibles variantes, abordaremos los siguientes subproblemas:
En los primeros ejercicios razonaremos sobre información sintáctica, usando el Árbol de Sintaxis Abstracta (AST), para identificar:
Encuentra todos los métodos con el nombre getAverageRating y su tipo declarante en el programa completando la consulta exercise1.ql
java proporciona una clase Method para razonar sobre métodos en un programa.Method proporciona los predicados miembro getName y hasName para razonar sobre el nombre de un método.Method proporciona el miembro getDeclaringType para razonar sobre el tipo que declara el método.Una solución se puede encontrar en la consulta exercise1.ql
Una solución al ejercicio 1 devuelve una lista de métodos. Algunos están definidos en una interfaz llamada RatingsManager y otros en las clases AbstractRatingsManager y RatingsScriptService.
De la información devuelta por la consulta y la documentación de componentes de XWiki podemos deducir que:
Role, y una implementación anotada con Component.ScriptService se hace accesible a las páginas wiki a través de scripting.Encuentra todas las clases anotadas con la anotación Component completando la consulta exercise2.ql. Ten en cuenta que el nombre completamente calificado del tipo de la anotación es org.xwiki.component.annotation.Component.
this en el predicado característico.Class proporciona un método getAnAnnotation para obtener las anotaciones asociadas.Annotation proporciona el predicado miembro getType para razonar sobre su tipo.Type proporciona los predicados miembro getName y hasName para razonar sobre el nombre de un tipo.RefType, que representa clases e interfaces, proporciona los predicados miembro getQualifiedName y hasQualifiedName para razonar sobre el nombre completamente calificado del reftype.Una solución se puede encontrar en la consulta exercise2.ql
Encuentra todos los componentes que implementan la interfaz ScriptService completando la consulta excercise3.ql
Class proporciona el predicado miembro getASuperType para razonar sobre los supertipos de una clase, es decir, los tipos que extiende o implementa.Una solución se puede encontrar en la consulta exercise3.ql
En este punto hemos identificado sintácticamente los métodos que pueden ser llamados por un usuario y cuyos parámetros consideraremos fuentes de datos no confiables más adelante en el taller.
En el siguiente ejercicio vamos a investigar e identificar posibles sumideros. De los resultados del ejercicio 1 podemos deducir que una de las implementaciones llama al método getAverageRatingFromQuery. Usando una consulta similar podemos encontrar los tipos declarantes de getAverageRatingFromQuery, lo que nos permite establecer que la implementación en la clase AbstractRatingsManager construye una sentencia SQL que se pasa al método search.
El método search está implementado en una dependencia y, por lo tanto, su implementación no está disponible. En los siguientes ejercicios vamos a usar la información de tipo disponible para identificar esta llamada al método search y su tipo declarante.
Encuentra todas las llamadas a métodos al método search e identifica su tipo declarante completando la consulta exercise4.ql
MethodCall nos proporciona los medios para razonar sobre llamadas a métodos.MethodAccess proporciona el predicado miembro getMethod para razonar sobre el destino de una llamada a método.Una solución se puede encontrar en la consulta exercise4.ql
Encuentra todas las llamadas a métodos declarados por la interfaz XWikiStorageInterface (con el nombre calificado com.xpn.xwiki.store.XWikiStoreInterface) completando la consulta exercise5.ql
La palabra clave instanceof se puede usar para afirmar que un valor pertenece al conjunto de valores representados por un tipo.
Por ejemplo, para identificar todas las llamadas a métodos de interfaz:
import java
from MethodCall ma
where ma.getMethod.getDeclaringType() instanceof Interface
select ma
Una solución se puede encontrar en la consulta exercise5.ql
En este punto hemos descrito sintácticamente posibles fuentes y sumideros usando QL. Para determinar si la información fluye entre estos puntos en el programa, vamos a analizar semánticamente el programa usando seguimiento de contaminación. Las bibliotecas estándar de los lenguajes que soportamos proporcionan dos mecanismos de flujo de datos:
DataFlow soporta flujo de información que preserva los valores.TaintTracking soporta flujo de información incluso si los valores son modificados.Este último es de interés, porque en vulnerabilidades de inyección como la inyección SQL es común que los datos no confiables se conviertan en parte de una sentencia más grande sobre la que se actúa.
En este taller vamos a reutilizar una configuración existente de seguimiento de contaminación para inyección SQL y extenderla con nuestras fuentes y sumideros modelados para encontrar la inyección SQL. Para entender cómo podemos extender la configuración, comenzamos mirando la definición de la configuración a continuación.
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)
}
}
Tanto el predicado isSource como isSink utilizados por la configuración para identificar esas ubicaciones del programa usan la palabra clave instanceof que hemos visto antes. Tanto la clase RemoteFlowSource como QueryInjectionSink son clases abstract. Este es un patrón común que encontrarás en las bibliotecas estándar y este patrón nos permite extender el conjunto de valores representados por las clases RemoteFlowSource y QueryInjectionSink. Una class de QL que extiende una abstract class no refina el conjunto de valores representados por la superclase, sino que agrega los valores representados por la subclase a la superclase.
En los siguientes ejercicios vamos a implementar estas subclases y construir nuestra consulta final.
Extiende el conjunto de valores de RemoteFlowSource con los parámetros de los métodos públicos de las clases de componentes identificadas en el ejercicio 3 completando la consulta exercise6.ql.
La palabra clave instanceof se puede usar para afirmar que un valor pertenece al conjunto de valores representados por un tipo.
Por ejemplo, para identificar todas las llamadas a métodos de interfaz:
import java
from MethodCall ma
where ma.getMethod.getDeclaringType() instanceof Interface
select ma
Una solución se puede encontrar en la consulta exercise6.ql
Extiende el conjunto de valores de QueryInjectionSink con los argumentos de las llamadas a los métodos de la interfaz de almacenamiento identificados en el ejercicio 5 completando la consulta exercise7.ql
La fórmula exists permite la introducción de variables temporales sobre las que se puede razonar en el ámbito del exists.
El siguiente ejemplo usa la expresión exists para reducir el conjunto de métodos a aquellos que son llamados.
from Method m
where m.hasName("foo") and exists(MethodCall ma | ma.getMethod() = m)
select m
La clase MethodCall proporciona el predicado miembro getQualifier para razonar sobre el calificador del acceso al método.
Una solución se puede encontrar en la consulta exercise7.ql
Combina tus soluciones de los ejercicios anteriores en una solución final completando la consulta exercise8.ql.
Una solución se puede encontrar en la consulta exercise8.ql
XWikiStoreInterface. Expande el sumidero para incluir usos directos de implementaciones de la interfaz. La clase Class proporciona el predicado miembro extendsOrImplements y la clase Method proporciona el predicado miembro overridesOrInstantiates que pueden ser de ayuda.