1
0
mirror of https://github.com/moparisthebest/wget synced 2024-07-03 16:38:41 -04:00

[svn] Fix escape chars in server response vulnerability. Server response is

now quoted to escape non-ASCII characters.
This commit is contained in:
hniksic 2005-03-04 11:34:31 -08:00
parent 50d143f3fe
commit b3363d2abd
11 changed files with 219 additions and 36 deletions

View File

@ -1,3 +1,35 @@
2005-03-03 Hrvoje Niksic <hniksic@xemacs.org>
* retr.c (retrieve_url): Escape location header.
* http.c (print_server_response_1): Escape server response when
printing it.
(gethttp): Escape host name, status message, location header, and
content type.
(http_loop): Escape error message from server.
* host.c (lookup_host): Escape host name when printing it.
* ftp.c (getftp): Escape user name when printing it.
(getftp): Escape remote file and directory for printing.
(getftp): Escape server listing when printing it.
(ftp_retrieve_list): Escape link name and file name.
(ftp_retrieve_glob): Escape file name.
* ftp-basic.c (ftp_response): Escape server response when printing
it.
* cookies.c (parse_set_cookies): Escape the cookie field when
printing it.
(parse_set_cookies): Escape contents of remote header.
(cookie_handle_set_cookie): Escape host name and cookie domain.
* connect.c (connect_to_ip): Escape the host name.
* log.c (escnonprint): New function, used for printing strings
coming from the server that possibly contain non-ASCII characters.
(escnonprint_uri): Ditto.
2005-02-24 Hrvoje Niksic <hniksic@xemacs.org> 2005-02-24 Hrvoje Niksic <hniksic@xemacs.org>
* ftp.c (getftp): Ditto. * ftp.c (getftp): Ditto.

View File

@ -269,8 +269,8 @@ connect_to_ip (const ip_address *ip, int port, const char *print)
{ {
const char *txt_addr = pretty_print_address (ip); const char *txt_addr = pretty_print_address (ip);
if (print && 0 != strcmp (print, txt_addr)) if (print && 0 != strcmp (print, txt_addr))
logprintf (LOG_VERBOSE, logprintf (LOG_VERBOSE, _("Connecting to %s|%s|:%d... "),
_("Connecting to %s|%s|:%d... "), print, txt_addr, port); escnonprint (print), txt_addr, port);
else else
logprintf (LOG_VERBOSE, _("Connecting to %s:%d... "), txt_addr, port); logprintf (LOG_VERBOSE, _("Connecting to %s:%d... "), txt_addr, port);
} }

View File

@ -616,7 +616,8 @@ parse_set_cookies (const char *sc,
char *name; char *name;
BOUNDED_TO_ALLOCA (name_b, name_e, name); BOUNDED_TO_ALLOCA (name_b, name_e, name);
logprintf (LOG_NOTQUIET, logprintf (LOG_NOTQUIET,
_("Error in Set-Cookie, field `%s'"), name); _("Error in Set-Cookie, field `%s'"),
escnonprint (name));
} }
state = S_ERROR; state = S_ERROR;
break; break;
@ -640,7 +641,7 @@ parse_set_cookies (const char *sc,
if (!silent) if (!silent)
logprintf (LOG_NOTQUIET, logprintf (LOG_NOTQUIET,
_("Syntax error in Set-Cookie: %s at position %d.\n"), _("Syntax error in Set-Cookie: %s at position %d.\n"),
sc, p - sc); escnonprint (sc), p - sc);
return NULL; return NULL;
} }
@ -862,7 +863,7 @@ cookie_handle_set_cookie (struct cookie_jar *jar,
{ {
logprintf (LOG_NOTQUIET, logprintf (LOG_NOTQUIET,
"Cookie coming from %s attempted to set domain to %s\n", "Cookie coming from %s attempted to set domain to %s\n",
host, cookie->domain); escnonprint (host), escnonprint (cookie->domain));
xfree (cookie->domain); xfree (cookie->domain);
goto copy_domain; goto copy_domain;
} }

View File

@ -67,9 +67,9 @@ ftp_response (int fd, char **ret_line)
if (!line) if (!line)
return FTPRERR; return FTPRERR;
if (opt.server_response) if (opt.server_response)
logputs (LOG_NOTQUIET, line); logputs (LOG_NOTQUIET, escnonprint (line));
else else
DEBUGP (("%s", line)); DEBUGP (("%s", escnonprint (line)));
if (ISDIGIT (line[0]) && ISDIGIT (line[1]) && ISDIGIT (line[2]) if (ISDIGIT (line[0]) && ISDIGIT (line[1]) && ISDIGIT (line[2])
&& line[3] == ' ') && line[3] == ' ')
{ {

View File

@ -292,7 +292,7 @@ getftp (struct url *u, wgint *len, wgint restval, ccon *con)
con->csock = -1; con->csock = -1;
/* Second: Login with proper USER/PASS sequence. */ /* Second: Login with proper USER/PASS sequence. */
logprintf (LOG_VERBOSE, _("Logging in as %s ... "), user); logprintf (LOG_VERBOSE, _("Logging in as %s ... "), escnonprint (user));
if (opt.server_response) if (opt.server_response)
logputs (LOG_ALWAYS, "\n"); logputs (LOG_ALWAYS, "\n");
err = ftp_login (csock, logname, passwd); err = ftp_login (csock, logname, passwd);
@ -551,7 +551,7 @@ Error in server response, closing control connection.\n"));
} }
if (!opt.server_response) if (!opt.server_response)
logprintf (LOG_VERBOSE, "==> CWD %s ... ", target); logprintf (LOG_VERBOSE, "==> CWD %s ... ", escnonprint (target));
err = ftp_cwd (csock, target); err = ftp_cwd (csock, target);
/* FTPRERR, WRITEFAILED, FTPNSFOD */ /* FTPRERR, WRITEFAILED, FTPNSFOD */
switch (err) switch (err)
@ -575,7 +575,7 @@ Error in server response, closing control connection.\n"));
case FTPNSFOD: case FTPNSFOD:
logputs (LOG_VERBOSE, "\n"); logputs (LOG_VERBOSE, "\n");
logprintf (LOG_NOTQUIET, _("No such directory `%s'.\n\n"), logprintf (LOG_NOTQUIET, _("No such directory `%s'.\n\n"),
u->dir); escnonprint (u->dir));
fd_close (csock); fd_close (csock);
con->csock = -1; con->csock = -1;
return err; return err;
@ -599,7 +599,7 @@ Error in server response, closing control connection.\n"));
if (opt.verbose) if (opt.verbose)
{ {
if (!opt.server_response) if (!opt.server_response)
logprintf (LOG_VERBOSE, "==> SIZE %s ... ", u->file); logprintf (LOG_VERBOSE, "==> SIZE %s ... ", escnonprint (u->file));
} }
err = ftp_size (csock, u->file, len); err = ftp_size (csock, u->file, len);
@ -760,7 +760,8 @@ Error in server response, closing control connection.\n"));
if (restval && (cmd & DO_RETR)) if (restval && (cmd & DO_RETR))
{ {
if (!opt.server_response) if (!opt.server_response)
logprintf (LOG_VERBOSE, "==> REST %s ... ", number_to_static_string (restval)); logprintf (LOG_VERBOSE, "==> REST %s ... ",
number_to_static_string (restval));
err = ftp_rest (csock, restval); err = ftp_rest (csock, restval);
/* FTPRERR, WRITEFAILED, FTPRESTFAIL */ /* FTPRERR, WRITEFAILED, FTPRESTFAIL */
@ -822,7 +823,7 @@ Error in server response, closing control connection.\n"));
{ {
if (restval) if (restval)
logputs (LOG_VERBOSE, "\n"); logputs (LOG_VERBOSE, "\n");
logprintf (LOG_VERBOSE, "==> RETR %s ... ", u->file); logprintf (LOG_VERBOSE, "==> RETR %s ... ", escnonprint (u->file));
} }
} }
@ -852,7 +853,8 @@ Error in server response, closing control connection.\n"));
break; break;
case FTPNSFOD: case FTPNSFOD:
logputs (LOG_VERBOSE, "\n"); logputs (LOG_VERBOSE, "\n");
logprintf (LOG_NOTQUIET, _("No such file `%s'.\n\n"), u->file); logprintf (LOG_NOTQUIET, _("No such file `%s'.\n\n"),
escnonprint (u->file));
fd_close (dtsock); fd_close (dtsock);
fd_close (local_sock); fd_close (local_sock);
return err; return err;
@ -1114,7 +1116,7 @@ Error in server response, closing control connection.\n"));
no-buffering on opt.lfile. */ no-buffering on opt.lfile. */
while ((line = read_whole_line (fp))) while ((line = read_whole_line (fp)))
{ {
logprintf (LOG_ALWAYS, "%s\n", line); logprintf (LOG_ALWAYS, "%s\n", escnonprint (line));
xfree (line); xfree (line);
} }
fclose (fp); fclose (fp);
@ -1536,19 +1538,18 @@ The sizes do not match (local %s) -- retrieving.\n\n"),
{ {
logprintf (LOG_VERBOSE, _("\ logprintf (LOG_VERBOSE, _("\
Already have correct symlink %s -> %s\n\n"), Already have correct symlink %s -> %s\n\n"),
con->target, f->linkto); con->target, escnonprint (f->linkto));
dlthis = 0; dlthis = 0;
break; break;
} }
} }
} }
logprintf (LOG_VERBOSE, _("Creating symlink %s -> %s\n"), logprintf (LOG_VERBOSE, _("Creating symlink %s -> %s\n"),
con->target, f->linkto); con->target, escnonprint (f->linkto));
/* Unlink before creating symlink! */ /* Unlink before creating symlink! */
unlink (con->target); unlink (con->target);
if (symlink (f->linkto, con->target) == -1) if (symlink (f->linkto, con->target) == -1)
logprintf (LOG_NOTQUIET, "symlink: %s\n", logprintf (LOG_NOTQUIET, "symlink: %s\n", strerror (errno));
strerror (errno));
logputs (LOG_VERBOSE, "\n"); logputs (LOG_VERBOSE, "\n");
} /* have f->linkto */ } /* have f->linkto */
#else /* not HAVE_SYMLINK */ #else /* not HAVE_SYMLINK */
@ -1566,7 +1567,7 @@ Already have correct symlink %s -> %s\n\n"),
case FT_DIRECTORY: case FT_DIRECTORY:
if (!opt.recursive) if (!opt.recursive)
logprintf (LOG_NOTQUIET, _("Skipping directory `%s'.\n"), logprintf (LOG_NOTQUIET, _("Skipping directory `%s'.\n"),
f->name); escnonprint (f->name));
break; break;
case FT_PLAINFILE: case FT_PLAINFILE:
/* Call the retrieve loop. */ /* Call the retrieve loop. */
@ -1575,7 +1576,7 @@ Already have correct symlink %s -> %s\n\n"),
break; break;
case FT_UNKNOWN: case FT_UNKNOWN:
logprintf (LOG_NOTQUIET, _("%s: unknown/unsupported file type.\n"), logprintf (LOG_NOTQUIET, _("%s: unknown/unsupported file type.\n"),
f->name); escnonprint (f->name));
break; break;
} /* switch */ } /* switch */
@ -1680,7 +1681,8 @@ ftp_retrieve_dirs (struct url *u, struct fileinfo *f, ccon *con)
if (!accdir (newdir, ALLABS)) if (!accdir (newdir, ALLABS))
{ {
logprintf (LOG_VERBOSE, _("\ logprintf (LOG_VERBOSE, _("\
Not descending to `%s' as it is excluded/not-included.\n"), newdir); Not descending to `%s' as it is excluded/not-included.\n"),
escnonprint (newdir));
continue; continue;
} }
@ -1743,7 +1745,8 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
{ {
if (f->type != FT_DIRECTORY && !acceptable (f->name)) if (f->type != FT_DIRECTORY && !acceptable (f->name))
{ {
logprintf (LOG_VERBOSE, _("Rejecting `%s'.\n"), f->name); logprintf (LOG_VERBOSE, _("Rejecting `%s'.\n"),
escnonprint (f->name));
f = delelement (f, &start); f = delelement (f, &start);
} }
else else
@ -1756,7 +1759,8 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
{ {
if (has_insecure_name_p (f->name)) if (has_insecure_name_p (f->name))
{ {
logprintf (LOG_VERBOSE, _("Rejecting `%s'.\n"), f->name); logprintf (LOG_VERBOSE, _("Rejecting `%s'.\n"),
escnonprint (f->name));
f = delelement (f, &start); f = delelement (f, &start);
} }
else else
@ -1802,7 +1806,8 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
/* No luck. */ /* No luck. */
/* #### This message SUCKS. We should see what was the /* #### This message SUCKS. We should see what was the
reason that nothing was retrieved. */ reason that nothing was retrieved. */
logprintf (LOG_VERBOSE, _("No matches on pattern `%s'.\n"), u->file); logprintf (LOG_VERBOSE, _("No matches on pattern `%s'.\n"),
escnonprint (u->file));
} }
else /* GETONE or GETALL */ else /* GETONE or GETALL */
{ {

View File

@ -723,7 +723,7 @@ lookup_host (const char *host, int flags)
/* No luck with the cache; resolve HOST. */ /* No luck with the cache; resolve HOST. */
if (!silent && !numeric_address) if (!silent && !numeric_address)
logprintf (LOG_VERBOSE, _("Resolving %s... "), host); logprintf (LOG_VERBOSE, _("Resolving %s... "), escnonprint (host));
#ifdef ENABLE_IPV6 #ifdef ENABLE_IPV6
{ {

View File

@ -687,7 +687,7 @@ print_server_response_1 (const char *prefix, const char *b, const char *e)
if (b < e && e[-1] == '\r') if (b < e && e[-1] == '\r')
--e; --e;
BOUNDED_TO_ALLOCA (b, e, ln); BOUNDED_TO_ALLOCA (b, e, ln);
logprintf (LOG_VERBOSE, "%s%s\n", prefix, ln); logprintf (LOG_VERBOSE, "%s%s\n", prefix, escnonprint (ln));
} }
/* Print the server response, line by line, omitting the trailing CR /* Print the server response, line by line, omitting the trailing CR
@ -1306,7 +1306,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
sock = pconn.socket; sock = pconn.socket;
using_ssl = pconn.ssl; using_ssl = pconn.ssl;
logprintf (LOG_VERBOSE, _("Reusing existing connection to %s:%d.\n"), logprintf (LOG_VERBOSE, _("Reusing existing connection to %s:%d.\n"),
pconn.host, pconn.port); escnonprint (pconn.host), pconn.port);
DEBUGP (("Reusing fd %d.\n", sock)); DEBUGP (("Reusing fd %d.\n", sock));
} }
} }
@ -1377,11 +1377,11 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
{ {
failed_tunnel: failed_tunnel:
logprintf (LOG_NOTQUIET, _("Proxy tunneling failed: %s"), logprintf (LOG_NOTQUIET, _("Proxy tunneling failed: %s"),
message ? message : "?"); message ? escnonprint (message) : "?");
xfree_null (message); xfree_null (message);
return CONSSLERR; return CONSSLERR;
} }
xfree (message); xfree_null (message);
/* SOCK is now *really* connected to u->host, so update CONN /* SOCK is now *really* connected to u->host, so update CONN
to reflect this. That way register_persistent will to reflect this. That way register_persistent will
@ -1458,7 +1458,8 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
message = NULL; message = NULL;
statcode = response_status (resp, &message); statcode = response_status (resp, &message);
if (!opt.server_response) if (!opt.server_response)
logprintf (LOG_VERBOSE, "%2d %s\n", statcode, message ? message : ""); logprintf (LOG_VERBOSE, "%2d %s\n", statcode,
message ? escnonprint (message) : "");
else else
{ {
logprintf (LOG_VERBOSE, "\n"); logprintf (LOG_VERBOSE, "\n");
@ -1604,7 +1605,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
{ {
logprintf (LOG_VERBOSE, logprintf (LOG_VERBOSE,
_("Location: %s%s\n"), _("Location: %s%s\n"),
hs->newloc ? hs->newloc : _("unspecified"), hs->newloc ? escnonprint_uri (hs->newloc) : _("unspecified"),
hs->newloc ? _(" [following]") : ""); hs->newloc ? _(" [following]") : "");
if (keep_alive) if (keep_alive)
skip_short_body (sock, contlen); skip_short_body (sock, contlen);
@ -1691,7 +1692,7 @@ gethttp (struct url *u, struct http_stat *hs, int *dt, struct url *proxy)
logputs (LOG_VERBOSE, logputs (LOG_VERBOSE,
opt.ignore_length ? _("ignored") : _("unspecified")); opt.ignore_length ? _("ignored") : _("unspecified"));
if (type) if (type)
logprintf (LOG_VERBOSE, " [%s]\n", type); logprintf (LOG_VERBOSE, " [%s]\n", escnonprint (type));
else else
logputs (LOG_VERBOSE, "\n"); logputs (LOG_VERBOSE, "\n");
} }
@ -2105,7 +2106,7 @@ File `%s' already there, will not retrieve.\n"), *hstat.local_file);
xfree (hurl); xfree (hurl);
} }
logprintf (LOG_NOTQUIET, _("%s ERROR %d: %s.\n"), logprintf (LOG_NOTQUIET, _("%s ERROR %d: %s.\n"),
tms, hstat.statcode, hstat.error); tms, hstat.statcode, escnonprint (hstat.error));
logputs (LOG_VERBOSE, "\n"); logputs (LOG_VERBOSE, "\n");
free_hstat (&hstat); free_hstat (&hstat);
xfree_null (dummy); xfree_null (dummy);
@ -2190,7 +2191,8 @@ The sizes do not match (local %s) -- retrieving.\n"),
if (opt.spider) if (opt.spider)
{ {
logprintf (LOG_NOTQUIET, "%d %s\n\n", hstat.statcode, hstat.error); logprintf (LOG_NOTQUIET, "%d %s\n\n", hstat.statcode,
escnonprint (hstat.error));
xfree_null (dummy); xfree_null (dummy);
return RETROK; return RETROK;
} }

134
src/log.c
View File

@ -615,6 +615,140 @@ log_dump_context (void)
fflush (fp); fflush (fp);
} }
/* String escape functions. */
/* Return the number of non-printable characters in SOURCE.
Non-printable characters are determined as per safe-ctype.h,
i.e. the non-printable characters of the "C" locale. This code is
meant to be used to protect the user from binary characters in
(normally ASCII) server messages. */
static int
count_nonprint (const char *source)
{
const char *p;
int cnt;
for (p = source, cnt = 0; *p; p++)
if (!ISPRINT (*p))
++cnt;
return cnt;
}
/* Copy SOURCE to DEST, escaping non-printable characters. If FOR_URI
is 0, they are escaped as \ooo; otherwise, they are escaped as
%xx.
DEST must point to a location with sufficient room to store an
encoded version of SOURCE. */
static void
copy_and_escape (const char *source, char *dest, int for_uri)
{
const char *from;
char *to;
/* Copy the string, escaping non-printable chars. */
if (!for_uri)
{
for (from = source, to = dest; *from; from++)
if (ISPRINT (*from))
*to++ = *from;
else
{
const unsigned char c = *from;
*to++ = '\\';
*to++ = '0' + (c >> 6);
*to++ = '0' + ((c >> 3) & 7);
*to++ = '0' + (c & 7);
}
}
else
{
for (from = source, to = dest; *from; from++)
if (ISPRINT (*from))
*to++ = *from;
else
{
const unsigned char c = *from;
*to++ = '%';
*to++ = XNUM_TO_DIGIT (c >> 4);
*to++ = XNUM_TO_DIGIT (c & 0xf);
}
}
*to = '\0';
}
#define RING_SIZE 3
struct ringel {
char *buffer;
int size;
};
static const char *
escnonprint_internal (const char *str, int for_uri)
{
static struct ringel ring[RING_SIZE]; /* ring data */
static int ringpos; /* current ring position */
int nprcnt = count_nonprint (str);
if (nprcnt == 0)
/* If there are no non-printable chars in STR, don't bother
copying anything, just return STR. */
return str;
{
/* Set up a pointer to the current ring position, so we can write
simply r->X instead of ring[ringpos].X. */
struct ringel *r = ring + ringpos;
/* Every non-printable character is replaced with "\ooo",
i.e. with three *additional* chars (two in URI-mode). Size
must also include the length of the original string and an
additional char for the terminating \0. */
int needed_size = strlen (str) + 1 + for_uri ? (2 * nprcnt) : (3 * nprcnt);
/* If the current buffer is uninitialized or too small,
(re)allocate it. */
if (r->buffer == NULL || r->size < needed_size)
r->buffer = xrealloc (r->buffer, needed_size);
copy_and_escape (str, r->buffer, for_uri);
ringpos = (ringpos + 1) % RING_SIZE;
return r->buffer;
}
}
/* Return a pointer to a static copy of STR with the non-printable
characters escaped as \ooo. If there are no non-printable
characters in STR, STR is returned.
NOTE: since this function can return a pointer to static data, be
careful to copy its result before calling it again. However, to be
more useful with printf, it maintains an internal ring of static
buffers to return. Currently the ring size is 3, which means you
can print up to three values in the same printf; if more is needed,
bump RING_SIZE. */
const char *
escnonprint (const char *str)
{
return escnonprint_internal (str, 0);
}
/* Return a pointer to a static copy of STR with the non-printable
characters escaped as %XX. If there are no non-printable
characters in STR, STR is returned.
This function returns a pointer to static data which will be
overwritten by subsequent calls -- see escnonprint for details. */
const char *
escnonprint_uri (const char *str)
{
return escnonprint_internal (str, 1);
}
/* When SIGHUP or SIGUSR1 are received, the output is redirected /* When SIGHUP or SIGUSR1 are received, the output is redirected
elsewhere. Such redirection is only allowed once. */ elsewhere. Such redirection is only allowed once. */
enum { RR_NONE, RR_REQUESTED, RR_DONE } redirect_request = RR_NONE; enum { RR_NONE, RR_REQUESTED, RR_DONE } redirect_request = RR_NONE;

View File

@ -52,4 +52,7 @@ void log_init PARAMS ((const char *, int));
void log_close PARAMS ((void)); void log_close PARAMS ((void));
void log_request_redirect_output PARAMS ((const char *)); void log_request_redirect_output PARAMS ((const char *));
const char *escnonprint PARAMS ((const char *));
const char *escnonprint_uri PARAMS ((const char *));
#endif /* LOG_H */ #endif /* LOG_H */

View File

@ -699,7 +699,7 @@ retrieve_url (const char *origurl, char **file, char **newloc,
newloc_parsed = url_parse (mynewloc, &up_error_code); newloc_parsed = url_parse (mynewloc, &up_error_code);
if (!newloc_parsed) if (!newloc_parsed)
{ {
logprintf (LOG_NOTQUIET, "%s: %s.\n", mynewloc, logprintf (LOG_NOTQUIET, "%s: %s.\n", escnonprint_uri (mynewloc),
url_error (up_error_code)); url_error (up_error_code));
url_free (u); url_free (u);
xfree (url); xfree (url);

View File

@ -31,9 +31,13 @@
#include "config.h" #include "config.h"
#define _GNU_SOURCE /* to get iswblank */
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <wchar.h> #include <wchar.h>
#include <wctype.h>
#include "wget.h" #include "wget.h"
@ -182,12 +186,14 @@ string_destroy (struct string_t *str)
memset (str, 0, sizeof (*str)); memset (str, 0, sizeof (*str));
} }
#if 0 /* unused */
static void static void
string_append_delim (struct string_t *dst) string_append_delim (struct string_t *dst)
{ {
assert_valid_string (dst); assert_valid_string (dst);
string_cat (dst, line_delim, line_delim_len); string_cat (dst, line_delim, line_delim_len);
} }
#endif
static int static int
is_line_delim (const wchar_t *wsz) is_line_delim (const wchar_t *wsz)