Wednesday, 24 September 2014

CSAW 2014: xorcise challenge

Last weekend our CTF team participated in CSAW. One of the challenges was particularly interesting, and this blog post gives a (somewhat) detailed overview on how to solve it. The source code and binary executable were given. This write-up is also available on the awesome write-ups repository.

Length checking vulnerabilities: Part One

By inspecting the source code we see the binary is a service which listens on port 24001. New clients are handled in int process_connection(int sockfd). This function reads one single packet, which is expected to be in the following format:
struct cipher_data {
    /** Header: Unencrypted and unauthenticated */
    uint8_t length; /** Length of the bytes array */
    uint8_t key[8];
    /** Payload: Encrypted and (partly) authenticated */
    uint8_t bytes[128];
};
As the comments indicate, the header is sent unencrypted. In particular this header includes an 8-byte key (which is chosen by the client). Roughly speaking the bytes array is XORed with the key in blocks of 8 bytes. The function doing this decryption is decipher(data, output), which will be investigated in more detail later on (because it contains a vulnerability). The length field the header contains the actual size of the bytes array. This must be smaller or equal to 128. An attempt is made to assure the given length is valid:
cipher_data encrypted;
ssize_t bytes_read = recv(sockfd, (uint8_t *)&encrypted, sizeof(encrypted), 0);
if (encrypted.length > bytes_read)
    return -1;
However this check is flawed. Variable bytes_read is the length of the packet including the header, while the program will treat the length field as the size of the bytes array. This means that as an attacker we can force the length to be bigger than 128.
The second vulnerability is in decipher(data, output). This function decrypts the bytes array using the key. Somewhat simplified we have the following code:
uint32_t decipher(cipher_data *data, uint8_t *output)
{
    uint8_t buf[MAX_BLOCKS * BLOCK_SIZE];    
    uint32_t loop, block_index;

    memcpy(buf, data->bytes, sizeof(buf));

    if ((data->length / BLOCK_SIZE) > MAX_BLOCKS)
        data->length = BLOCK_SIZE * MAX_BLOCKS;

    // Block-decryption loop
    for (loop = 0; loop < data->length; loop += 8)
        for (block_index = 0; block_index < 8; ++block_index)
            buf[loop+block_index] ^= (0x8F ^ data->key[block_index]);

    memcpy(output, buf, sizeof(buf));
}
Note that data->bytes is copied to a local buffer, and this local buffer is then processed. The first if-test is an attempt to assure that data->length is not bigger than the local buffer. However this checked is flawed because in (data->length / BLOCK_SIZE) the intermediate result will be rounded down. In particular, the following value will pass the length check:
data->length = BLOCK_SIZE * MAX_BLOCKS + (BLOCK_SIZE - 1) = 135
And due to the first length check vulnerability, we know that data->length can indeed contain such values. The consequence of both vulnerabilities means that we can force the block-decryption loop to decrypt an extra block of 8-bytes. Since the local buffer is too small for this, we are capable of modifying local variables placed after the buffer. In particular we can overwrite loop and block_index.

Length checking vulnerabilities: Part Two?

Interestingly, decipher(data, output) is the only function that uses the length field in the header. All other functions are coded in such a way that knowing this length is not required. This causes another peculiar observation: when setting length to zero, no decryption takes place, and the packet is processed as-is. This observation is not required to solve the challenge though (but it does make it easier to construct packets).

Authentication and Commands

The above two vulnerabilities are sufficient to exploit the challenge. However we had some problems doing this, and instead opted for another approach. Our approach relies on additional functionality of the challenge: letting it execute commands. We can execute three commands: (1) getting the current server time; (2) reading an arbitrary file; (3) executing a system command. The latter two commands required the packet to be authenticated.

The client must provide a checksum equal to H(H(password||key) || H(data||password)) for the packet to be authenticated. Here password is a secret loaded at startup, and key and data are chosen by the client. The function H() seems to be a custom (and insecure?) hash function.

Exploitation

We use both length-check vulnerabilities to read the secret password. Once we have this, we can execute arbitrary system commands on the server, and exploitation becomes trivial.
In the decipher(data, output) function we overwrite the return address. This is done by first overwriting the local variable loop, such that the next xor-decrypt operation will point to the return address. We prevent other variables from being overwritten by XORing with zero. Recall that we can XOR 8 bytes in total, and in particular we XOR these bytes with the key in the header of the packet.
Where will we point the return address to? First observe that, because of the last mempcy call, the eax register contains a pointer to the local buffer when returning from the function. Hence the content pointed to by eax is under our control. If we now exploit the binary for interesting gadgets, we spot the following:
.text:08049290                 mov     eax, [ebp+packet]
.text:08049293                 add     eax, 8
.text:08049296                 sub     esp, 8
.text:08049299                 push    eax             ; filename
.text:0804929A                 push    [ebp+fd]        ; fd
.text:0804929D                 call    read_file
This is the code that calls read_file(sockfd, name). When returning from decipher the ebp register is restored, hence it correctly points to the local variables. However, variable packet is not yet initialized by the program, hence we can't use it. Instead we redirect code to push eax; push [ebp+fd]. Since eax points to the beginning of the buffer this code will successfully be executed. Putting the stringpassword.txt in the start of the buffer will now make the service print out the password pass123. We can now send authenticated commands and execute arbitrary shell commands. The flag was in the fileflag.txt in the same directory as password.txt (i.e. the current working directory).
Remark: the problem we encountered when directly calling system(cmd) was that it would overwrite the local buffer used in decipher (since it was saved on the stack). Recall that the pointer to this buffer was stored in eax. Luckily, in the read_file function, the stack is used in such a way so this didn't occur.

Friday, 30 May 2014

ApBleed: Heartbleed over WPA1/2 Enterprise

Tl;dr: ApBleed is my proof-of-concept to test heartbleed against wireless networks. Patches welcome.

Once the heartbleed vulnerability in OpenSSL was made public, most focused on its applicability to web servers. Other targets such as SVN servers, VPNs, etc. were also mentioned. However, there was little public discussion about the impact of heartbleed on wireless networks.

Nevertheless, it was clear heartbleed was also exploitable against WPA1/2 enterprise networks, even if it wasn't discussed as publicly as other heartbleed stories. Normally enterprise networks use one of the many EAP methods inside an SSL tunnel to authenticate users. If OpenSSL is being used, this SSL tunnel might be vulnerable to heartbleed.

Of particular interest are networks like eduroam. Eduroam is a world-wide roaming access service between research institutions. This means that I can go to a different country, see an eduroam hotspot, and connect to it using the credentials of my home institution. What's interesting is how my credentials are checked. It's done by setting up an SSL tunnel to the RADIUS server of my own institution. The eduroam network will take care of the necessary packet routing. The image below illustrates this (taken from eduroam website):


Let's assume I'm a student at lsu.edu and currently visiting an institution of utk.edu. If I now connect to an eduroam hotspot, an SSL tunnel will be set up between my device and the RADIUS server of lsu.edu (i.e. my own institution). If my credentials are valid, lsu.edu will notify utk.edu that I should be allowed on the network.

Why is this interesting for an attacker? Because the SSL tunnel is set up before user authentication, and even before you are assigned an IP. The only thing known about you is your MAC address and your "home" institution (i.e. the realm defining your home institution). Of course, an attacker can spoof both the MAC address and the home institution. The attacker only has to be within range of an eduroam hotspot, and he or she can pick any eduroam hotspot at will. The eduroam network will then forward packets to the RADIUS server the attacker specified (i.e. the realm spoofed by the attacker). This is what allows a user to directly set up an SSL tunnel with the radius server of their home institution. However, this means we can anonymously connect to any institution we want!

The guys from eduroam quickly responded to this. Hours after heartbleed got public they posted a warning on their website. One day after heartbleed, with some help from the HostApd mailing list, they had a working proof of concept to test whether institutions were vulnerable. To quote eduroam:
Following up on the heartbleed vulnerability in OpenSSL: it is confirmed that EAP-authentication in RADIUS servers is vulnerable to the attack. It is therefore extremely important to upgrade OpenSSL and restart RADIUS services as soon as possible.
The attack is feasible from any public eduroam hotspot, not just your RADIUS peers.
Federation-level admins in Europe will receive notice from the eduroam OT with a list of vulnerable realms. [..]
While the tools are not publicly available for security reasons, we're providing the service of scanning a realm list for global federation admins on their request. [..]
Though I focus on eduroam, every networking using enterprise authentication with an EAP method inside an SSL tunnel might be vulnerable to heartbleed. Nevertheless, eduroam is more interesting than your ordinary network, because it allows you to anonymously connect to any institution which has joined the eduroam federation.

Few public proof-of-concepts were available (if any at all). I can think of three reasons for this. First, not everyone was aware enterprise authentication was vulnerable to heartbleed. Second, it's a little bit harder to make a working proof-of-concept against wireless networks. Third reason might have to do with the impact of public exploits. Because authentication (and hence the SSL tunnel) takes place before getting access to a network, it's not possible to consult a certificate revocation list (CRL) or query the certificate status online (with OCSP) [eduroam]. So revoking certificates is not possible.

However, recently proof-of-concepts are being released. So it's time to release my own proof-of-concept as well :) I modified wpa_supplicant so, once connected to an AP and ready to talk to the radius server, it opens a local socket. This means you can now connect to this socket and pretend it's the RADIUS server. The big advantage of this approach is that you can use any existing heartbleed tool to test the radius server. Simply let the heartbleed tool connect to the socket created by my proof-of-concept. The code has not been tested thoroughly, but worked in all my small experiments. Get a copy of my PoC (called ApBleed) at github. Patches are welcome!

Some example outputs of running my tool:


Using existing heartbleed tools to connect to the radius server: