
Um workshop de CodeQL abordando CVE-2021-21380
Neste workshop, usaremos raciocínio sintático e semântico para encontrar uma injeção de SQL no componente de avaliação da plataforma XWiki documentada pela CVE-2021-21380
Complete esta seção antes do workshop, se possível.
Instale o Visual Studio Code.
Instale a extensão CodeQL para Visual Studio Code.
Você não precisa instalar o CLI do CodeQL: a extensão cuidará disso para você.
Clone este repositório:
git clone https://github.com/advanced-security/codeql-workshop-cve-2021-21380.git
xwiki-platform-CVE-2021-21380.zip na exibição do Explorer e selecione o comando CodeQL: Set Current Database.Cmd/Ctrl+Shift+P), pesquise e execute o comando CodeQL: Install Pack Dependencies.github na caixa para filtrar a lista.cve-2021-21380-exercises, cve-2021-21380-exercises-tests, cve-2021-21380-solutions e cve-2021-21380-solutions-tests.O workshop é dividido em vários exercícios que apresentam o suporte da linguagem QL para Java e termina com uma consulta final para encontrar a injeção de SQL conhecida. Nestes exercícios, você aprenderá:
Neste workshop, procuraremos por vulnerabilidades de injeção de SQL conhecidas no componente da API de avaliações do XWiki Platform. Essas vulnerabilidades podem ocorrer em aplicações quando informações controladas por um usuário externo chegam ao código da aplicação que constrói e executa uma consulta SQL de forma insegura.
A injeção de SQL conhecida discutida neste workshop é revisada em GHSA-79rg-7mv3-jrr5 no GitHub Advisory Database. Para encontrar a injeção de SQL e possíveis variantes, vamos resolver os seguintes subproblemas:
Nos primeiros exercícios, raciocinaremos sobre informações sintáticas, usando a Árvore Sintática Abstrata (AST), para identificar:
Encontre todos os métodos com o nome getAverageRating e o tipo declarante no programa completando a consulta exercise1.ql
java fornece uma classe Method para raciocinar sobre métodos em um programa.Method fornece os predicados membros getName e hasName para raciocinar sobre o nome de um método.Method fornece o membro getDeclaringType para raciocinar sobre o tipo que declara o método.Uma solução pode ser encontrada na consulta exercise1.ql
Uma solução para o exercício 1 retorna uma lista de métodos. Alguns são definidos em uma interface chamada RatingsManager e outros são definidos nas classes AbstractRatingsManager e RatingsScriptService.
A partir das informações retornadas pela consulta e da documentação de componentes do XWiki podemos deduzir que:
Role, e uma implementação anotada com Component.ScriptService é tornado acessível a páginas wiki por meio de scripts.Encontre todas as classes anotadas com a anotação Component completando a consulta exercise2.ql.
Observe que o nome totalmente qualificado do tipo da anotação é org.xwiki.component.annotation.Component.
this no /predicado característico/.Class fornece um método getAnAnnotation para obter anotações associadas.Annotation fornece o predicado membro getType para raciocinar sobre seu tipo.Type fornece os predicados membros getName e hasName para raciocinar sobre o nome de um tipo.RefType, que representa classes e interfaces, fornece os predicados membros getQualifiedName e hasQualifiedName para raciocinar sobre o nome totalmente qualificado do reftype.Uma solução pode ser encontrada na consulta exercise2.ql
Encontre todos os componentes que implementam a interface ScriptService completando a consulta excercise3.ql
Class fornece o predicado membro getASuperType para raciocinar sobre os supertipos de uma classe, ou seja, os tipos que ela extends ou implements.Uma solução pode ser encontrada na consulta exercise3.ql
Neste ponto, identificamos sintaticamente os métodos que podem ser chamados por um usuário e cujos parâmetros consideraremos como fontes de dados não confiáveis mais adiante no workshop.
No próximo exercício, vamos investigar e identificar possíveis sinks. A partir dos resultados do exercício 1, podemos deduzir que uma das implementações chama o método getAverageRatingFromQuery. Usando uma consulta semelhante, podemos encontrar os tipos declarantes de getAverageRatingFromQuery, o que nos permite estabelecer que a implementação na classe AbstractRatingsManager constrói uma instrução SQL que é passada para o método search.
O método search é implementado em uma dependência e, portanto, sua implementação não está disponível. Nos próximos exercícios, usaremos as informações de tipo disponíveis para identificar essa chamada ao método search e seu tipo declarante.
Encontre todas as chamadas de método para o método search e identifique seu tipo declarante completando a consulta exercise4.ql
MethodCall nos fornece os meios para raciocinar sobre chamadas de método.MethodAcccess fornece o predicado membro getMethod para raciocinar sobre o alvo de uma chamada de método.Uma solução pode ser encontrada na consulta exercise4.ql
Encontre todas as chamadas de método para métodos declarados pela interface XWikiStorageInterface (com o nome qualificado com.xpn.xwiki.store.XWikiStoreInterface) completando a consulta exercise5.ql
A palavra-chave instanceof pode ser usada para declarar que um valor pertence ao conjunto de valores representados por um tipo.
Por exemplo, para identificar todas as chamadas a métodos de interface:
import java
from MethodCall ma
where ma.getMethod.getDeclaringType() instanceof Interface
select ma
Uma solução pode ser encontrada na consulta exercise5.ql
Neste ponto, descrevemos sintaticamente possíveis fontes e sinks usando QL. Para determinar se as informações fluem entre esses pontos no programa, vamos analisar semanticamente o programa usando taint tracking. As bibliotecas padrão das linguagens que suportamos fornecem dois mecanismos de fluxo de dados:
DataFlow suporta fluxo de informações com preservação de valores.TaintTracking suporta fluxo de informações mesmo que os valores sejam modificados.Este último é de interesse, pois em vulnerabilidades de injeção, como injeção de SQL, é comum que dados não confiáveis se tornem parte de uma instrução maior sobre a qual uma ação é executada.
Neste workshop, vamos reutilizar uma configuração existente de taint tracking para injeção de SQL e estendê-la com nossas fontes e sinks modelados para encontrar a injeção de SQL. Para entender como podemos estender a configuração, começamos observando a definição da configuração abaixo.
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 o predicado isSource quanto o isSink usados pela configuração para identificar esses locais do programa usam a palavra-chave instanceof que vimos antes. Tanto a classe RemoteFlowSource quanto a QueryInjectionSink são classes abstract.
Este é um padrão comum que você encontrará nas bibliotecas padrão, e esse padrão nos permite estender o conjunto de valores representados pelas classes RemoteFlowSource e QueryInjectionSink.
Uma class QL que estende uma abstract class não refina o conjunto de valores representados pela superclasse, mas adiciona os valores representados pela subclasse à superclasse.
Nos próximos exercícios, vamos implementar essas subclasses e construir nossa consulta final.
Estenda o conjunto de valores de RemoteFlowSource com os parâmetros dos métodos públicos das classes de componente identificadas no exercício 3, completando a consulta exercise6.ql.
A palavra-chave instanceof pode ser usada para declarar que um valor pertence ao conjunto de valores representados por um tipo.
Por exemplo, para identificar todas as chamadas a métodos de interface:
import java
from MethodCall ma
where ma.getMethod.getDeclaringType() instanceof Interface
select ma
Uma solução pode ser encontrada na consulta exercise6.ql
Estenda o conjunto de valores de QueryInjectionSink com os argumentos das chamadas aos métodos da interface de armazenamento identificada no exercício 5, completando a consulta exercise7.ql
A fórmula exists permite a introdução de variáveis temporárias sobre as quais se pode raciocinar no escopo do exists.
O exemplo a seguir usa a expressão exists para reduzir o conjunto de métodos aos métodos que são chamados.
from Method m
where m.hasName("foo") and exists(MethodCall ma | ma.getMethod() = m)
select m
A classe MethodCall fornece o predicado membro getQualifer para raciocinar sobre o qualificador do acesso ao método.
Uma solução pode ser encontrada na consulta exercise7.ql
Combine suas soluções do exercício anterior em uma solução final completando a consulta exercise8.ql.
Uma solução pode ser encontrada na consulta exercise8.ql
XWikiStoreInterface. Expanda o sink para incluir usos diretos de implementações da interface. A classe Class fornece o predicado membro extendsOrImplements e a classe Method fornece o predicado membro overridesOrInstantiates que podem ser úteis.