ftpd: security tightened up:
PORT is not allowed on !IPv4 PORT must have IP == peer's IP upload is allowed only into regular files function old new delta ftpd_main 1815 2019 +204 handle_upload_common 260 306 +46 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 250/0) Total: 250 bytes
This commit is contained in:
parent
7a6766428e
commit
f2160b6a09
@ -66,9 +66,9 @@
|
|||||||
/* Convert a constant to 3-digit string, packed into uint32_t */
|
/* Convert a constant to 3-digit string, packed into uint32_t */
|
||||||
enum {
|
enum {
|
||||||
/* Shift for Nth decimal digit */
|
/* Shift for Nth decimal digit */
|
||||||
SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
|
|
||||||
SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
|
|
||||||
SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
|
SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
|
||||||
|
SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
|
||||||
|
SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
|
||||||
};
|
};
|
||||||
#define STRNUM32(s) (uint32_t)(0 \
|
#define STRNUM32(s) (uint32_t)(0 \
|
||||||
| (('0' + ((s) / 1 % 10)) << SHIFT0) \
|
| (('0' + ((s) / 1 % 10)) << SHIFT0) \
|
||||||
@ -76,47 +76,6 @@ enum {
|
|||||||
| (('0' + ((s) / 100 % 10)) << SHIFT2) \
|
| (('0' + ((s) / 100 % 10)) << SHIFT2) \
|
||||||
)
|
)
|
||||||
|
|
||||||
enum {
|
|
||||||
OPT_l = (1 << 0),
|
|
||||||
OPT_1 = (1 << 1),
|
|
||||||
OPT_v = (1 << 2),
|
|
||||||
OPT_S = (1 << 3),
|
|
||||||
OPT_w = (1 << 4),
|
|
||||||
|
|
||||||
#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
|
|
||||||
#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
|
|
||||||
const_ALLO = mk_const4('A', 'L', 'L', 'O'),
|
|
||||||
const_APPE = mk_const4('A', 'P', 'P', 'E'),
|
|
||||||
const_CDUP = mk_const4('C', 'D', 'U', 'P'),
|
|
||||||
const_CWD = mk_const3('C', 'W', 'D'),
|
|
||||||
const_DELE = mk_const4('D', 'E', 'L', 'E'),
|
|
||||||
const_EPSV = mk_const4('E', 'P', 'S', 'V'),
|
|
||||||
const_HELP = mk_const4('H', 'E', 'L', 'P'),
|
|
||||||
const_LIST = mk_const4('L', 'I', 'S', 'T'),
|
|
||||||
const_MKD = mk_const3('M', 'K', 'D'),
|
|
||||||
const_MODE = mk_const4('M', 'O', 'D', 'E'),
|
|
||||||
const_NLST = mk_const4('N', 'L', 'S', 'T'),
|
|
||||||
const_NOOP = mk_const4('N', 'O', 'O', 'P'),
|
|
||||||
const_PASS = mk_const4('P', 'A', 'S', 'S'),
|
|
||||||
const_PASV = mk_const4('P', 'A', 'S', 'V'),
|
|
||||||
const_PORT = mk_const4('P', 'O', 'R', 'T'),
|
|
||||||
const_PWD = mk_const3('P', 'W', 'D'),
|
|
||||||
const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
|
|
||||||
const_REST = mk_const4('R', 'E', 'S', 'T'),
|
|
||||||
const_RETR = mk_const4('R', 'E', 'T', 'R'),
|
|
||||||
const_RMD = mk_const3('R', 'M', 'D'),
|
|
||||||
const_RNFR = mk_const4('R', 'N', 'F', 'R'),
|
|
||||||
const_RNTO = mk_const4('R', 'N', 'T', 'O'),
|
|
||||||
const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
|
|
||||||
const_STAT = mk_const4('S', 'T', 'A', 'T'),
|
|
||||||
const_STOR = mk_const4('S', 'T', 'O', 'R'),
|
|
||||||
const_STOU = mk_const4('S', 'T', 'O', 'U'),
|
|
||||||
const_STRU = mk_const4('S', 'T', 'R', 'U'),
|
|
||||||
const_SYST = mk_const4('S', 'Y', 'S', 'T'),
|
|
||||||
const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
|
|
||||||
const_USER = mk_const4('U', 'S', 'E', 'R'),
|
|
||||||
};
|
|
||||||
|
|
||||||
struct globals {
|
struct globals {
|
||||||
char *p_control_line_buf;
|
char *p_control_line_buf;
|
||||||
len_and_sockaddr *local_addr;
|
len_and_sockaddr *local_addr;
|
||||||
@ -182,21 +141,16 @@ replace_char(char *str, char from, char to)
|
|||||||
return p - str;
|
return p - str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* NB: status_str is char[4] packed into uint32_t */
|
||||||
static void
|
static void
|
||||||
cmdio_write(uint32_t status_str, const char *str)
|
cmdio_write(uint32_t status_str, const char *str)
|
||||||
{
|
{
|
||||||
union {
|
|
||||||
char buf[4];
|
|
||||||
uint32_t v32;
|
|
||||||
} u;
|
|
||||||
char *response;
|
char *response;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
u.v32 = status_str;
|
|
||||||
|
|
||||||
/* FTP allegedly uses telnet protocol for command link.
|
/* FTP allegedly uses telnet protocol for command link.
|
||||||
* In telnet, 0xff is an escape char, and needs to be escaped: */
|
* In telnet, 0xff is an escape char, and needs to be escaped: */
|
||||||
response = escape_text(u.buf, str, (0xff << 8) + '\r');
|
response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
|
||||||
|
|
||||||
/* ?! does FTP send embedded LFs as NULs? wow */
|
/* ?! does FTP send embedded LFs as NULs? wow */
|
||||||
len = replace_char(response, '\n', '\0');
|
len = replace_char(response, '\n', '\0');
|
||||||
@ -444,46 +398,58 @@ handle_epsv(void)
|
|||||||
static void
|
static void
|
||||||
handle_port(void)
|
handle_port(void)
|
||||||
{
|
{
|
||||||
unsigned short port;
|
unsigned port, port_hi;
|
||||||
char *raw, *port_part;
|
char *raw, *comma;
|
||||||
len_and_sockaddr *lsa;
|
socklen_t peer_ipv4_len;
|
||||||
|
struct sockaddr_in peer_ipv4;
|
||||||
|
struct in_addr port_ipv4_sin_addr;
|
||||||
|
|
||||||
port_pasv_cleanup();
|
port_pasv_cleanup();
|
||||||
|
|
||||||
raw = G.ftp_arg;
|
raw = G.ftp_arg;
|
||||||
|
|
||||||
/* Buglets:
|
/* PORT command format makes sense only over IPv4 */
|
||||||
* xatou16 will accept wrong input,
|
if (!raw || G.local_addr->u.sa.sa_family != AF_INET) {
|
||||||
* xatou16 will exit instead of generating error to peer
|
|
||||||
*/
|
|
||||||
|
|
||||||
port_part = strrchr(raw, ',');
|
|
||||||
if (port_part == NULL)
|
|
||||||
goto bail;
|
|
||||||
port = xatou16(&port_part[1]);
|
|
||||||
*port_part = '\0';
|
|
||||||
|
|
||||||
port_part = strrchr(raw, ',');
|
|
||||||
if (port_part == NULL)
|
|
||||||
goto bail;
|
|
||||||
port |= xatou16(&port_part[1]) << 8;
|
|
||||||
*port_part = '\0';
|
|
||||||
|
|
||||||
replace_char(raw, ',', '.');
|
|
||||||
lsa = xdotted2sockaddr(raw, port);
|
|
||||||
|
|
||||||
if (lsa == NULL) {
|
|
||||||
bail:
|
bail:
|
||||||
cmdio_write_error(FTP_BADCMD);
|
cmdio_write_error(FTP_BADCMD);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Should we verify that lsa matches getpeername(STDIN)?
|
comma = strrchr(raw, ',');
|
||||||
* Otherwise peer can make us open data connections
|
if (comma == NULL)
|
||||||
* to other hosts (security problem!)
|
goto bail;
|
||||||
*/
|
*comma = '\0';
|
||||||
|
port = bb_strtou(&comma[1], NULL, 10);
|
||||||
|
if (errno || port > 0xff)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
G.port_addr = lsa;
|
comma = strrchr(raw, ',');
|
||||||
|
if (comma == NULL)
|
||||||
|
goto bail;
|
||||||
|
*comma = '\0';
|
||||||
|
port_hi = bb_strtou(&comma[1], NULL, 10);
|
||||||
|
if (errno || port_hi > 0xff)
|
||||||
|
goto bail;
|
||||||
|
port |= port_hi << 8;
|
||||||
|
|
||||||
|
replace_char(raw, ',', '.');
|
||||||
|
|
||||||
|
/* We are verifying that PORT's IP matches getpeername().
|
||||||
|
* Otherwise peer can make us open data connections
|
||||||
|
* to other hosts (security problem!)
|
||||||
|
* This code would be too simplistic:
|
||||||
|
* lsa = xdotted2sockaddr(raw, port);
|
||||||
|
* if (lsa == NULL) goto bail;
|
||||||
|
*/
|
||||||
|
if (!inet_aton(raw, &port_ipv4_sin_addr))
|
||||||
|
goto bail;
|
||||||
|
peer_ipv4_len = sizeof(peer_ipv4);
|
||||||
|
if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
|
||||||
|
goto bail;
|
||||||
|
if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
G.port_addr = xdotted2sockaddr(raw, port);
|
||||||
cmdio_write_ok(FTP_PORTOK);
|
cmdio_write_ok(FTP_PORTOK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -612,6 +578,8 @@ handle_dir_common(int opts)
|
|||||||
if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
|
if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
|
||||||
return; /* port_or_pasv_was_seen emitted error response */
|
return; /* port_or_pasv_was_seen emitted error response */
|
||||||
|
|
||||||
|
/* -n prevents user/groupname display,
|
||||||
|
* which can be problematic in chroot */
|
||||||
ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
|
ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
|
||||||
ls_fp = fdopen(ls_fd, "r");
|
ls_fp = fdopen(ls_fd, "r");
|
||||||
if (!ls_fp) /* never happens. paranoia */
|
if (!ls_fp) /* never happens. paranoia */
|
||||||
@ -749,11 +717,12 @@ handle_rnto(void)
|
|||||||
static void
|
static void
|
||||||
handle_upload_common(int is_append, int is_unique)
|
handle_upload_common(int is_append, int is_unique)
|
||||||
{
|
{
|
||||||
char *tempname = NULL;
|
struct stat statbuf;
|
||||||
|
char *tempname;
|
||||||
off_t bytes_transferred;
|
off_t bytes_transferred;
|
||||||
|
off_t offset;
|
||||||
int local_file_fd;
|
int local_file_fd;
|
||||||
int remote_fd;
|
int remote_fd;
|
||||||
off_t offset;
|
|
||||||
|
|
||||||
offset = G.restart_pos;
|
offset = G.restart_pos;
|
||||||
G.restart_pos = 0;
|
G.restart_pos = 0;
|
||||||
@ -761,6 +730,7 @@ handle_upload_common(int is_append, int is_unique)
|
|||||||
if (!port_or_pasv_was_seen())
|
if (!port_or_pasv_was_seen())
|
||||||
return; /* port_or_pasv_was_seen emitted error response */
|
return; /* port_or_pasv_was_seen emitted error response */
|
||||||
|
|
||||||
|
tempname = NULL;
|
||||||
local_file_fd = -1;
|
local_file_fd = -1;
|
||||||
if (is_unique) {
|
if (is_unique) {
|
||||||
tempname = xstrdup(" FILE: uniq.XXXXXX");
|
tempname = xstrdup(" FILE: uniq.XXXXXX");
|
||||||
@ -773,13 +743,17 @@ handle_upload_common(int is_append, int is_unique)
|
|||||||
flags = O_WRONLY | O_CREAT;
|
flags = O_WRONLY | O_CREAT;
|
||||||
local_file_fd = open(G.ftp_arg, flags, 0666);
|
local_file_fd = open(G.ftp_arg, flags, 0666);
|
||||||
}
|
}
|
||||||
if (local_file_fd < 0) {
|
|
||||||
|
if (local_file_fd < 0
|
||||||
|
|| fstat(local_file_fd, &statbuf) != 0
|
||||||
|
|| !S_ISREG(statbuf.st_mode)
|
||||||
|
) {
|
||||||
cmdio_write_error(FTP_UPLOADFAIL);
|
cmdio_write_error(FTP_UPLOADFAIL);
|
||||||
|
if (local_file_fd >= 0)
|
||||||
|
goto close_local_and_bail;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: paranoia: fstat it, refuse to do anything if it's not a regular file */
|
|
||||||
|
|
||||||
if (offset)
|
if (offset)
|
||||||
xlseek(local_file_fd, offset, SEEK_SET);
|
xlseek(local_file_fd, offset, SEEK_SET);
|
||||||
|
|
||||||
@ -787,7 +761,7 @@ handle_upload_common(int is_append, int is_unique)
|
|||||||
free(tempname);
|
free(tempname);
|
||||||
|
|
||||||
if (remote_fd < 0)
|
if (remote_fd < 0)
|
||||||
goto bail;
|
goto close_local_and_bail;
|
||||||
|
|
||||||
/* TODO: if we'll implement timeout, this will need more clever handling.
|
/* TODO: if we'll implement timeout, this will need more clever handling.
|
||||||
* Perhaps alarm(N) + checking that current position on local_file_fd
|
* Perhaps alarm(N) + checking that current position on local_file_fd
|
||||||
@ -800,7 +774,8 @@ handle_upload_common(int is_append, int is_unique)
|
|||||||
cmdio_write_error(FTP_BADSENDFILE);
|
cmdio_write_error(FTP_BADSENDFILE);
|
||||||
else
|
else
|
||||||
cmdio_write_ok(FTP_TRANSFEROK);
|
cmdio_write_ok(FTP_TRANSFEROK);
|
||||||
bail:
|
|
||||||
|
close_local_and_bail:
|
||||||
close(local_file_fd);
|
close(local_file_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -838,17 +813,18 @@ cmdio_get_cmd_and_arg(void)
|
|||||||
if (!cmd)
|
if (!cmd)
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
|
/* Trailing '\n' is already stripped, strip '\r' */
|
||||||
len = strlen(cmd) - 1;
|
len = strlen(cmd) - 1;
|
||||||
while (len >= 0 && cmd[len] == '\r') {
|
while ((ssize_t)len >= 0 && cmd[len] == '\r') {
|
||||||
cmd[len] = '\0';
|
cmd[len] = '\0';
|
||||||
len--;
|
len--;
|
||||||
}
|
}
|
||||||
|
|
||||||
G.ftp_arg = strchr(cmd, ' ');
|
G.ftp_arg = strchr(cmd, ' ');
|
||||||
if (G.ftp_arg != NULL) {
|
if (G.ftp_arg != NULL)
|
||||||
*G.ftp_arg = '\0';
|
*G.ftp_arg++ = '\0';
|
||||||
G.ftp_arg++;
|
|
||||||
}
|
/* Uppercase and pack into uint32_t first word of the command */
|
||||||
cmdval = 0;
|
cmdval = 0;
|
||||||
while (*cmd)
|
while (*cmd)
|
||||||
cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
|
cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
|
||||||
@ -856,6 +832,47 @@ cmdio_get_cmd_and_arg(void)
|
|||||||
return cmdval;
|
return cmdval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
|
||||||
|
#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
|
||||||
|
enum {
|
||||||
|
const_ALLO = mk_const4('A', 'L', 'L', 'O'),
|
||||||
|
const_APPE = mk_const4('A', 'P', 'P', 'E'),
|
||||||
|
const_CDUP = mk_const4('C', 'D', 'U', 'P'),
|
||||||
|
const_CWD = mk_const3('C', 'W', 'D'),
|
||||||
|
const_DELE = mk_const4('D', 'E', 'L', 'E'),
|
||||||
|
const_EPSV = mk_const4('E', 'P', 'S', 'V'),
|
||||||
|
const_HELP = mk_const4('H', 'E', 'L', 'P'),
|
||||||
|
const_LIST = mk_const4('L', 'I', 'S', 'T'),
|
||||||
|
const_MKD = mk_const3('M', 'K', 'D'),
|
||||||
|
const_MODE = mk_const4('M', 'O', 'D', 'E'),
|
||||||
|
const_NLST = mk_const4('N', 'L', 'S', 'T'),
|
||||||
|
const_NOOP = mk_const4('N', 'O', 'O', 'P'),
|
||||||
|
const_PASS = mk_const4('P', 'A', 'S', 'S'),
|
||||||
|
const_PASV = mk_const4('P', 'A', 'S', 'V'),
|
||||||
|
const_PORT = mk_const4('P', 'O', 'R', 'T'),
|
||||||
|
const_PWD = mk_const3('P', 'W', 'D'),
|
||||||
|
const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
|
||||||
|
const_REST = mk_const4('R', 'E', 'S', 'T'),
|
||||||
|
const_RETR = mk_const4('R', 'E', 'T', 'R'),
|
||||||
|
const_RMD = mk_const3('R', 'M', 'D'),
|
||||||
|
const_RNFR = mk_const4('R', 'N', 'F', 'R'),
|
||||||
|
const_RNTO = mk_const4('R', 'N', 'T', 'O'),
|
||||||
|
const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
|
||||||
|
const_STAT = mk_const4('S', 'T', 'A', 'T'),
|
||||||
|
const_STOR = mk_const4('S', 'T', 'O', 'R'),
|
||||||
|
const_STOU = mk_const4('S', 'T', 'O', 'U'),
|
||||||
|
const_STRU = mk_const4('S', 'T', 'R', 'U'),
|
||||||
|
const_SYST = mk_const4('S', 'Y', 'S', 'T'),
|
||||||
|
const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
|
||||||
|
const_USER = mk_const4('U', 'S', 'E', 'R'),
|
||||||
|
|
||||||
|
OPT_l = (1 << 0),
|
||||||
|
OPT_1 = (1 << 1),
|
||||||
|
OPT_v = (1 << 2),
|
||||||
|
OPT_S = (1 << 3),
|
||||||
|
OPT_w = (1 << 4),
|
||||||
|
};
|
||||||
|
|
||||||
int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
|
int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
|
||||||
int ftpd_main(int argc, char **argv)
|
int ftpd_main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
@ -865,6 +882,7 @@ int ftpd_main(int argc, char **argv)
|
|||||||
|
|
||||||
if (opts & (OPT_l|OPT_1)) {
|
if (opts & (OPT_l|OPT_1)) {
|
||||||
/* Our secret backdoor to ls */
|
/* Our secret backdoor to ls */
|
||||||
|
/* TODO: pass -n too? */
|
||||||
xchdir(argv[2]);
|
xchdir(argv[2]);
|
||||||
argv[2] = (char*)"--";
|
argv[2] = (char*)"--";
|
||||||
return ls_main(argc, argv);
|
return ls_main(argc, argv);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user