|
COMMAND middleman off-by-one bug SYSTEMS AFFECTED middleman-1.2 and prior PROBLEM qitest1 [http://bespin.org/~qitest1] found, credits to h2so4 : Middleman is a powerful proxy server with many features designed to make browsing the Internet a more pleasant experience. It can do much more than just proxying though; it can be used as a layer between any web server and client to filter HTTP requests, or act as a portal between an internal network and the Internet. It has an intuitive Web interface that provides an easy way of accessing and changing the proxy's configuration, there's no need to dig through any complicated configuration files. (quoted from its README.html) Vulnerability ============= The program is affected by an ebp corruption condition in the routine performing the dns lookup of the hostname provided in the request, because its own implementation of strncpy goes off by one. The bug could be easily exploited by a remote attacker, leading to a root shell if the daemon runs as root (it's really nice that the sample rc init file provided calls the daemon without dropping privileges, which is one of its features). If exploitation is not successful, it will result in a denial of service, because the program will die at all. Details ======= In src/misc.c: /* strncpy which always NULL terminates */ char *s_strncpy(char *d, char *s, size_t len) { char *dest = d; for (; len && (*dest = *s); s++, dest++, len--); *dest = '\0'; return d; } No doubt. It always NULL terminates. But even off by one. In src/networks.c near line 614 we find: /* perform a dns lookup, using cached response from a previous lookup if possible */ HOSTENT *net_dns(char *host) { time_t t; char *string, hst[128], buf[24]; HOSTENT *hostent; Looking to this code we could think the memory layout on the stack for this function is the following: hst[128] | string[4] | t[4] | ebp | eip V In src/networks.c near line 627 we find: s_strncpy(hst, host, 128); We know that s_strncpy puts its final 0x00 off by one. This fact, apparently, should not be a real problem, because the 0x00 byte should be put on the lsb of string. Where is the bug? Due to compilation (maybe optimization), those variables get allocated in a different way: (I dumped this from the program) ** net_dns(): &hst 0xbf7ff9f4 &string 0xbf7ff9d4 &t 0xbf7ff9d8 So the real memory layout is: string[4] | t[4] | hst[128] | ebp | eip V So we have ebp corruption. Let's have a memory dump: ** net_dns(): 0xbf7ff9f4 -> 0xbfffc0d3 (0) ** net_dns(): 0xbf7ff9f8 -> 0xbfffc0d3 (1) ** net_dns(): 0xbf7ff9fc -> 0xbfffc0d3 (2) [...] ** net_dns(): 0xbf7ffa70 -> 0xbfffc0d3 (31) ** net_dns(): 0xbf7ffa74 -> 0xbf7ffa00 (32) ** net_dns(): 0xbf7ffa78 -> 0x804db07 (33) At 0xbf7ffa78 we have 0x804db07: that's the eip. Indeed: (gdb) x 0x804db07 0x804db07 <net_connect+75>: 0xc483c289 net_connect() effectively calls net_dns() At 0xbf7ffa74 there is the corrupted ebp. At 0xbf7ffa00 + 4 the execution flow will search an eip: ** net_dns(): 0xbf7ffa04 -> 0xbfffc0d3 (4) We can control the data contained at that address, which can obviously be a pointer to our code. But there's no need to explain here how this kind of vulnerability can be exploited: it's quite trivial. I think this kind of problems should be seriously valued, because they are difficult to be detected and prevented. But they can be avoided by simply checking that the program never goes off by one, even if the buffer is not immediately before the ebp: indeed there could be a condition like that I described. SOLUTION Check freshmeat, apply the following patch : - --- middleman/src/misc.c 2002-10-19 19:07:24.000000000 +0200 +++ middleman-patched/src/misc.c 2003-01-10 11:29:08.000000000 +0100 @@ -27,17 +27,34 @@ #include <sys/types.h> #include "proto.h" - -/* - -strncpy which always NULL terminates - -*/ - -char *s_strncpy(char *d, char *s, size_t len) - -{ - - char *dest = d; - - - - for (; len && (*dest = *s); s++, dest++, len--); - - *dest = '\0'; + /* Adapted version of OpenBSD strlcpy */ +char * +s_strncpy(dst, src, siz) + char *dst; + char *src; + size_t siz; +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } - - return d; + return dst; } /*