Another Path to Exploiting CVE-2024-1212 in Progress Kemp LoadMaster
Intro
Rhino Labs discovered a pre-authentication command injection vulnerability in the Progress Kemp LoadMaster. LoadMaster is a load balancer product that comes in many different flavors and even has a free version. The flaw exists in the LoadMaster API. When an API request is received to either the ‘/access
’ or ‘/accessv2
’ endpoint, the embedded min-httpd server calls a script which in turn calls the access
binary with the HTTP request info. The vulnerability works even when the API is disabled.
Rhino Labs showed that attacker controlled data is read by the access
binary when sending an enableapi
command to the /access
endpoint. The attacker controlled data exists as the ‘username’ in the Authorization header. The username value is put into the REMOTE_USER
environment variable. The value stored in REMOTE_USER
is retrieved by the access binary and ends up as part of a string passed to a system()
call. The system call executes the validuser
binary and a carefully crafted payload allows us to inject commands into the bash shell.
We also found that the REMOTE_PASS
environment variable is exploitable in the same way here via the Authorization header.
This command execution is possible via any API command if the API is enabled. As Rhino Labs points out, When sending a GET request to the access API indicating the enableapi
command, the access binary skips checking whether the API is enabled first or not, and the Authorization header is checked right away.
APIv2
While investigating this vulnerability, I noticed that LoadMaster has two APIs, the v1 API indicated above, and a v2 API that functions via the /accessv2
endpoint and JSON data. The access binary still processes these requests, but a slightly different path is followed. The logic of the main function is largely duplicated as a new function and called if the APIv2 is requested. That function then performs the same checks as above, with the slight exception that it will decode the API and pass the values of the apiuser
and apipass
keys to the same system call. So, we have another path to the same exposure:
While we can still control the password variable, it’s no longer exploitable here. Somewhere along the path the password string gets converted to base64 before being passed through the system()
call, nullifying any injected quotes.
We can see below that the verify_perms
function calls validu()
with REMOTE_USER
and REMOTE_PASS
data in the APIv1 implementation; in the API v2 implementation the apiuser
and apipass
data is passed to validu()
from the APIv2 JSON.
Patch
The patch solves these flaws quite simply by examining the username and password strings in the Authorization header for single quotes. If they contain a single quote, the patched function will truncate them just before the first single quote. Decompiling the patched access
binary with Ghidra, we can see this:
Here we see the addition of the new function call for both username and password. The function loops over each character in the input string and if it is a single quote, it’s changed to a \0
, null terminating the string.
Another Way to Test: Emulation
Even though we’ve got x86 linux binaries, we can’t run them natively on another linux machine due to potential library and ABI issues. Regardless, we can extract the filesystem and use a chroot and qemu to emulate the environment. Once we’ve extracted the filesystem, we can mount the ext2 filesystem ourselves:
sudo mount -t ext2 -o loop,exec unpatched.ext2 mnt/
Now we can explore the filesystem and execute binaries.
This provides us with a quick offline method to test our assumptions around injection. For instance, as we mentioned, the access
binary is exploitable via the REMOTE_USER
parameter:
First, we’ve copied the qemu-x86_64-static binary into our mounted filesystem. We’re using that with the -E
flag to pass in a bunch of environment variables found via reversing access
, one of which is the injectable REMOTE_USER
. The whole thing is wrapped in chroot
so that symbolic links and relative paths work correctly. We give /bin/access
several flags which we’ve lifted straight from the CGI script that calls the binary
exec /bin/${0/*\//} -C $CLUST -F $FIPS -H $HW_VERSION
and from checking the ps
debugging feature in the LoadMaster UI. Pro tip: check ps while running another longer running debug command like top
or tcpdump
in order to see better results.
root 13333 0.0 0.0 6736 1640 ? S 15:54 0:00 /sbin/httpd -port 8080 -address 127.0.0.1
root 16733 0.0 0.0 6736 112 ? S 15:59 0:00 /sbin/httpd -port 8080 -address 127.0.0.1
bal 16734 0.0 0.0 12064 2192 ? S 15:59 0:00 /bin/access -C 0 -F 0 -H 3
bal 16741 0.2 0.0 11452 2192 ? S 15:59 0:00 /usr/bin/top -d1 -n10 -b -o%CPU
bal 16845 0.0 0.0 7140 1828 ? R 15:59 0:00 ps auxwww
While this doesn’t provide us the complete method to exploit externally, it is a nice quick method to try out different injection strings and test assumptions. We can also pass a -g <port>
parameter to qemu and then attach gdb to the process to get even closer to what’s happening.
Conclusion
This was a really cool find by Rhino Labs. Here I add one additional exploitation path and some additional ways to test for this vulnerability.
Tenable’s got you covered and can detect this vulnerability as part of your VM program with Tenable VM, Tenable SC, and Tenable Nessus. The direct check plugin for this vulnerability can be found at CVE-2024-1212. The plugin tests test both APIv1 and APIv2 paths for this command execution exposure.