“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.
LinkLister.java “/links”
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.
LinkLister.java “/links-v2”
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
”.