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
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. It also creates
char* buf and
size_t bufsize these start being set to
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
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
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) 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
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.