Vulnado Parte 3

2021-03-30 16:37:00 +0000

“Vulnado, Ownado? WTF!? - Parte 3”

Carai borracha ta ficando mais extenso do que eu pensava..😅

Seguindo essa saga, se você foi uma pessoa curiosa e olhou direitinho nas requests no seu navegador. É possível perceber que nossa aplicação nos apresenta todos os resultados na porta 1337, porém as requests da “API”, são realizadas através da porta 8080.

SSRF

LinksController.java
public class LinksController {
  @RequestMapping(value = "/links", produces = "application/json")
  List<String> links(@RequestParam String url) throws IOException{
    return LinkLister.getLinks(url);
  }
  @RequestMapping(value = "/links-v2", produces = "application/json")
  List<String> linksV2(@RequestParam String url) throws BadRequest{
    return LinkLister.getLinksV2(url);
  }
}

Aqui podemos perceber que temos duas implementações de um endpoint /links & /links2. E que o parâmetro “url” é requerido e se formos analisar as suas respectivas implementações, vamos chegar no seguinte código.

public class LinkLister {
  public static List<String> getLinks(String url) throws IOException {
    List<String> result = new ArrayList<String>();
    Document doc = Jsoup.connect(url).get();
    Elements links = doc.select("a");
    for (Element link : links) {
      result.add(link.absUrl("href"));
    }
    return result;
  }

Esse trecho de código nos diz o seguinte:

  • Vou pegar o parâmetro url, se não existir taca exception na tela!
  • Se existir vou tentar me conetar nessa URL.
  • O Jsoup possui algumas funções e nesse caso está utilizando o “<a href
  • Executa um ForLoop caso haja mais de um link com a URL absoluta.
  • E por último e não menos importante retorna o resultado.

Tendo isso em vista podemos tentar um “SSRF” e tentar realizar uma requisição para algum endpoint interno. Porém como estamos executando em um docker o interesante é ver qual é o IP que contém o site interno.



Agora que temos o IP interno basta tentar acessa-lo via URL…
Em uma aplicação normal, o mais prudente a se tentar é tentar o acessar o localhost. Sempre com algum bypass na manga.



 public static List<String> getLinksV2(String url) throws BadRequest {
    try {
      URL aUrl= new URL(url);
      String host = aUrl.getHost();
      System.out.println(host);
      if (host.startsWith("172.") || host.startsWith("192.168") || host.startsWith("10.")){
        throw new BadRequest("Use of Private IP");
      } else {
        return getLinks(url);
      }
    } catch(Exception e) {
      throw new BadRequest(e.getMessage());
    }
  }

Agora temos uma restrição de IP, onde se o host começar com “172, 192.168 ou 10”, iremos ser bloqueados, então nesse caso é simples. Basta achar algum bypass que não comece com nenhumn desses IPs da block list e mesmo assim ainda consigamos acessar o Ip interno do container.



Uma vez que foi entendido o que o código faz e o que ele espera de input, bastou procurar um bypass, convertendo o IP para decimal.

RCE

Agora a cereja do bolo, e não vai ser diferente aqui ao invés de mostrar a shell, irei mostrar o porque foi possível a execução de código remotamente. 😎

CowController.java

public class CowController {
    @RequestMapping(value = "/cowsay")
    String cowsay(@RequestParam(defaultValue = "I love Linux!") String input) {
        return Cowsay.run(input);
    }
}

Esse trecho do controller nos diz que, existe um endpoint “/cowsay” e que o valor padrão é “I love Linux!”, agora vamos analisar a implementação do Cowsay.run.

public class Cowsay {
  public static String run(String input) {
    ProcessBuilder processBuilder = new ProcessBuilder();
    String cmd = "/usr/games/cowsay '" + input + "'";
    System.out.println(cmd);
    processBuilder.command("bash", "-c", cmd);

Parece simples certo? é só mandar qualquer comando que será interpretado pela aplicação.. Mas não é bem assim não, para este caso irei utilizar o Terminal + Curl para realizar a PoC, pois no navegador o desenho não aparece certinho. =/



Beleza agora que temos o formato de resposta, podemos analisar o trecho de código acima para identificar o ponto de falha.

Seguindo o mesmo princípio dos outros códigos, o input do usuário é concatenado com o comando “/usr/games/cowsay” e logo após isso o resultado é printado na tela. Porém existem algumas particularidades que serão explicadas em seguida.



Dica zika

Quando se está realizando um code review e você tiver a possibilidade de visualizar os logs, pode ter certeza que vai ser o seu melhor aliado. Pois vai te auxiliar a entender como a aplicação está tratando e interpretando seu payload.
Se for possível executar a aplicação localmente também será de grande valia para entender seu funcionamento local antes de atacar o seu alvo.

O que aconteceu aqui foi o seguinte:
A aplicação não só concatena o seu input, ela adiciona as aspas duplas e simples para fazer que o seu input seja interpretado de maneira correta pela aplicação. Outro ponto importante é que o resultado não aparecerá na tela se você não forçar o echo e comentar o resto do payload com #.

O payload final ficou assim:
http://127.0.0.1:8080/cowsay?input=%22pwned%27;echo%20%24%28cat%20/etc/passwd)%20%23”.