Execução remota de código com o XStream
1. Visão geral
Neste tutorial, vamos dissecar um ataque de execução remota de código contra a biblioteca de serialização XStream XML. Este exploit se enquadra na categoria de ataquesuntrusted deserialization.
Aprenderemos quando o XStream está vulnerável a esse ataque, como ele funciona e como evitá-lo.
2. Noções básicas do XStream
Antes de descrever o ataque, vamos revisar alguns fundamentos do XStream. XStream é uma biblioteca de serialização XML que converte entre tipos Java e XML. Considere uma classePerson simples:
public class Person {
private String first;
private String last;
// standard getters and setters
}
Vamos ver comoXStream can write some Person instance to XML:
XStream xstream = new XStream();
String xml = xstream.toXML(person);
Da mesma forma,XStream can read XML into an instance of Person:
XStream xstream = new XStream();
xstream.alias("person", Person.class);
String xml = "John Smith ";
Person person = (Person) xstream.fromXML(xml);
Em ambos os casos, o XStream usaJava reflection para traduzir o tipoPerson de e para XML. O ataque ocorre durante a leitura de XML. Ao ler XML, o XStream instancia classes Java usando reflexão.
As classes XStream instanciadas são determinadas pelos nomes dos elementos XML que analisa.
Como configuramos o XStream para reconhecer o tipoPerson, o XStream instancia um novoPerson quando analisa os elementos XML chamados “pessoa”.
Além dos tipos definidos pelo usuário, comoPerson, o XStream reconhece os principais tipos Java prontos para uso. Por exemplo, o XStream pode lerMap de XML:
String xml = ""
+ "";
XStream xStream = new XStream();
Map map = (Map) xStream.fromXML(xml);
Veremos como a capacidade do XStream de ler XML representando os principais tipos de Java será útil na exploração de execução remota de código.
3. Como o ataque funciona
Os ataques de execução remota de código ocorrem quando os invasores fornecem entrada, que é finalmente interpretada como código. Nesse caso, os invasores exploram a estratégia de desserialização do XStream, fornecendo código de ataque como XML.
Com a composição correta de classes, o XStream finalmente executa o código de ataque através da reflexão Java.
Vamos construir um exemplo de ataque.
3.1. Incluir código de ataque em umProcessBuilder
Nosso ataque visa iniciar um novo processo de calculadora de mesa. No macOS, é "/Applications/Calculator.app". No Windows, isso é "calc.exe". Para fazer isso, vamos enganar o XStream para que execute um novo processo usando umProcessBuilder. RecallJava code to start a new process:
new ProcessBuilder().command("executable-name-here").start();
Ao ler XML, o XStream chama apenas construtores e define campos. Portanto, o invasor não tem uma maneira direta de invocar o métodoProcessBuilder.start().
No entanto, invasores inteligentes podem usar a composição certa de classes para executar o métodoProcessBuilderstart().
O pesquisador de segurançaDinis Cruz shows us in their blog post como eles usam a interfaceComparable para invocar o código de ataque no construtor de cópia da coleção classificadaTreeSet. Vamos resumir a abordagem aqui.
3.2. Crie um proxy dinâmicoComparable
Lembre-se de que o invasor precisa criar umProcessBuildere invocar seu métodostart(). Para fazer isso, criaremos uma instância deComparable cujo métodocompare invoca o métodoProcessBuilder'sstart().
Felizmente,Java Dynamic Proxies nos permite criar uma instância deComparable dinamicamente.
Além disso, a classeEventHandler do Java fornece ao invasor uma implementaçãoInvocationHandler configurável. The attacker configures the EventHandler to invoke the ProcessBuilder‘s start() method.
Juntando esses componentes, temos uma representação XML XStream para o proxyComparable:
java.lang.Comparable
open
/Applications/Calculator.app
start
3.3. Forçar uma comparação usando o proxy dinâmicoComparable
Para forçar uma comparação com nosso proxyComparable, construiremos uma coleção classificada. Let’s build a TreeSet collection that compares two Comparable instances: a String and our proxy.
Usaremos o construtor de cópia deTreeSet para construir esta coleção. Por fim, temos a representação XML XStream para um novoTreeSet contendo nosso proxy e umString:
foo
java.lang.Comparable
open
/Applications/Calculator.app
start
Por fim, o ataque ocorre quando o XStream lê esse XML. Enquanto o desenvolvedor espera que o XStream leia umPerson, ele executa o ataque:
String sortedSortAttack = // XML from above
XStream xstream = new XStream();
Person person = (Person) xstream.fromXML(sortedSortAttack);
3.4. Resumo do Ataque
Vamos resumir as chamadas reflexivas que o XStream faz ao desserializar este XML
-
O XStream invoca o construtor de cópiaTreeSet com umCollection contendo umString “foo” e nosso proxyComparable.
-
O construtorTreeSet chama nosso métodocompareTo do proxyComparable para determinar a ordem dos itens no conjunto classificado.
-
Nosso proxy dinâmicoComparable delega todas as chamadas de método paraEventHandler.
-
OEventHandler é configurado para invocar o métodostart() doProcessBuilder que ele compõe.
-
OProcessBuilder bifurca um novo processo executando o comando que o invasor deseja executar.
4. Quando o XStream é vulnerável?
O XStream pode ser vulnerável a esse ataque de execução remota de código quando o invasor controla o XML que lê.
Por exemplo, considere uma API REST que aceite entrada XML. Se esta API REST usar o XStream para ler os corpos de solicitação XML, poderá estar vulnerável a um ataque de execução remota de código porque os invasores controlam o conteúdo do XML enviado à API.
Por outro lado, um aplicativo que usa apenas o XStream para ler XML confiável possui uma superfície de ataque muito menor.
Por exemplo, considere um aplicativo que use apenas o XStream para ler arquivos de configuração XML definidos por um administrador de aplicativos. Este aplicativo não está exposto à execução remota de código do XStream porque os invasores não estão no controle do XML que o aplicativo lê (o administrador está).
5. Protegendo o XStream contra ataques de execução remota de código
Felizmente, o XStream introduziu umsecurity framework na versão 1.4.7. Podemos usar a estrutura de segurança para fortalecer nosso exemplo contra ataques de execução remota de código. The security framework allows us to configure XStream with a whitelist of types it is allowed to instantiate.
Esta lista incluirá apenas tipos básicos e nossa classePerson:
XStream xstream = new XStream();
xstream.addPermission(NoTypePermission.NONE);
xstream.addPermission(NullPermission.NULL);
xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
xstream.allowTypes(new Class>[] { Person.class });
Além disso, os usuários do XStream podem considerar a proteção de seus sistemas usando um agente RASP (Runtime Application Self-Protection). RASP agents use bytecode instrumentation at run time to automatically detect and block attacks. Esta técnica é menos sujeita a erros do que construir manualmente uma lista branca de tipos.
6. Conclusão
Neste artigo, aprendemos como executar um ataque de execução remota de código em um aplicativo que usa o XStream para ler XML. Como ataques como esse existem, o XStream deve ser reforçado quando usado para ler XML de fontes não confiáveis.
A exploração existe porque o XStream usa reflexão para instanciar classes Java identificadas pelo XML do invasor.
Como sempre, o código dos exemplos pode ser encontradoover on GitHub.