240 - The Syslog Special
Qualys at it again this time with a skipped initialization code path leading to a small allocation and a buffer overflow deep in glibc’s syslog
.
The normal flow of this function is relatively simple. It initializes a few variables: starting with bufs
the default buffer that will be used as a char[1024]
. It also creates char* buf
and size_t bufsize
these start being set to NULL
and 0
respectively. Then the code attempt to write just the standard syslog header/prefix to bufs
. If the entire header was able to fit inside of bufs
then it tries to write the actual message into the rest of bufs
. Again is just checks if message was able to fit, if it was it sets buf
to bufs
. Regardless of the success of that write it will also update bufsize
at this point now that it knows the length of the header + log message.
The next block of code checks if buf
is still NULL
which indicates at one of the two attempted writes it failed and couldn’t write the entire message indicating that it must need to allocate a larger buffer. It will attempt to allocate a bufsize + 1
buffer, and that is where everything goes wrong. The only time bufsize
is updated is once it knows the header + log message length value. If the first write of only the header fails, it won’t fall into the conditional block that ultimately sets bufsize
. This means when it tries to do this allocation, bufsize
will still be 0
.
The trick to reaching this case is that there must be a way to make the syslog header be more than the 1024 bytes of the initial buffer. Normally it’ll be much smaller than this, however if syslog
is used without explicitly opening a log and setting a LogTag
the tag used will effectively be the basename(argv[0])
which is entirely attacker controlled.
Qualys was able to exploit this issue against su
on multiple distributions and they do document the exploitation strategy within the post.
Episode Correction: During the episode I believe we might have stated this error path didn’t really work at all, which is perhaps not a fair assessment since it does work when the failure is because of the message taking it over the buffer length. It only fails in this insecure way when the header specifically. I also think I might have mentioned an integer overflow, not exactly sure what I was thinking about there but this gets you a small allocation without any overflow.
Format string bugs, you’d think we’d be done with them by now, but Shielder here documents one in ASUS routers.
I feel like the blame for this one might just come down to a developer not recognizing that syslog
itself supports printf
-style format specifiers on its own. The code within the logmessage_normal
function will first resolve all the format specifiers into a buffer using a vsnprintf
call, and this it will call syslog
with the resultant buffer.
This means that any user-input that gets “safely” printed into the buffer to be logged will be interpreted again when it passes through syslog
. And of course, as we already spoiled one can indeed included data that gets logged through teh rc_service
field of the JSON that gets parsed from the web by do_set_iperf3_srv_cgi/do_set_iperf3_cli_cgi
.
As the binary was not compiled as position independent executable there were corruption targets that were not randomized by the base ASLR of the system.. They were able to use the format string attack to target the SystemCmd
global variable that is used by sys_script
and passed into system()
. Then by triggering any function that used sys_system
their command would be executed.
Unfortunately, in their emulated device the inital format string bug was accessible, in practice on actual devices however this would be impossible to reach, but its still a fun bug and I appreciate their look at exploiting this from a data-only perspective instead of the traditional format string attacker strategy of overwritting a GOT entry and ROP.