Changes with Apache 1.3.25
+ *) Disallow anything but whitespace on the request line after the
+ HTTP/x.y protocol string. That prevents arbitrary user input
+ from ending up in the access_log and error_log. Also, special
+ characters (especially control characters) are escaped in the
+ log file now, to make a clear distinction between client-supplied
+ strings (with special characters) and server-side strings.
+ [Martin Kraemer]
+
*) Get rid of DEFAULT_XFERLOG as it is not used anywhere. It was
preserved by the build system, printed with "httpd -V", but
apart from that completely ignored: the default transfer log
API_EXPORT(char *) ap_escape_html(pool *p, const char *s);
API_EXPORT(char *) ap_construct_server(pool *p, const char *hostname,
unsigned port, const request_rec *r);
+API_EXPORT(char *) ap_escape_logitem(pool *p, const char *str);
API_EXPORT(char *) ap_escape_shell_cmd(pool *p, const char *s);
API_EXPORT(int) ap_count_dirs(const char *path);
#define T_ESCAPE_PATH_SEGMENT (0x02)
#define T_OS_ESCAPE_PATH (0x04)
#define T_HTTP_TOKEN_STOP (0x08)
+#define T_ESCAPE_LOGITEM (0x10)
int main(int argc, char *argv[])
{
printf(
"/* this file is automatically generated by gen_test_char, do not edit */\n"
-"#define T_ESCAPE_SHELL_CMD (%u)\n"
-"#define T_ESCAPE_PATH_SEGMENT (%u)\n"
-"#define T_OS_ESCAPE_PATH (%u)\n"
-"#define T_HTTP_TOKEN_STOP (%u)\n"
-"\n"
-"static const unsigned char test_char_table[256] = {\n"
-" 0,",
+"#define T_ESCAPE_SHELL_CMD 0x%02x /* chars with special meaning in the shell */\n"
+"#define T_ESCAPE_PATH_SEGMENT 0x%02x /* find path segment, as defined in RFC1808 */\n"
+"#define T_OS_ESCAPE_PATH 0x%02x /* escape characters in a path or uri */\n"
+"#define T_HTTP_TOKEN_STOP 0x%02x /* find http tokens, as defined in RFC2616 */\n"
+"#define T_ESCAPE_LOGITEM 0x%02x /* filter what should go in the log file */\n"
+"\n",
T_ESCAPE_SHELL_CMD,
T_ESCAPE_PATH_SEGMENT,
T_OS_ESCAPE_PATH,
- T_HTTP_TOKEN_STOP);
+ T_HTTP_TOKEN_STOP,
+ T_ESCAPE_LOGITEM
+ );
/* we explicitly dealt with NUL above
* in case some strchr() do bogosity with it */
+ printf("static const unsigned char test_char_table[256] = {\n"
+ " 0x00, "); /* print initial item */
+
for (c = 1; c < 256; ++c) {
flags = 0;
- if (c % 20 == 0)
- printf("\n ");
/* escape_shell_cmd */
#if defined(WIN32) || defined(OS2)
if (ap_iscntrl(c) || strchr(" \t()<>@,;:\\/[]?={}", c)) {
flags |= T_HTTP_TOKEN_STOP;
}
- printf("%u%c", flags, (c < 255) ? ',' : ' ');
+ /* For logging, escape all control characters,
+ * double quotes (because they delimit the request in the log file)
+ * backslashes (because we use backslash for escaping)
+ * and 8-bit chars with the high bit set
+ */
+ if (!ap_isprint(c) || c == '"' || c == '\\' || ap_iscntrl(c)) {
+ flags |= T_ESCAPE_LOGITEM;
+ }
+ printf("0x%02x%s", flags, (c < 255) ? ", " : " ");
+
+ if ((c % 8) == 7)
+ printf(" /*0x%02x...0x%02x*/\n ", c-7, c);
}
printf("\n};\n");
const char *uri;
conn_rec *conn = r->connection;
unsigned int major = 1, minor = 0; /* Assume HTTP/1.0 if non-"HTTP" protocol */
- int len;
+ int len, n;
/* Read past empty lines until we get a real request line,
* a read error, the connection closes (EOF), or we timeout.
r->assbackwards = (ll[0] == '\0');
r->protocol = ap_pstrdup(r->pool, ll[0] ? ll : "HTTP/0.9");
- if (2 == sscanf(r->protocol, "HTTP/%u.%u", &major, &minor)
+ if (2 == sscanf(r->protocol, "HTTP/%u.%u%n", &major, &minor, &n)
&& minor < HTTP_VERSION(1,0)) /* don't allow HTTP/0.1000 */
r->proto_num = HTTP_VERSION(major, minor);
else
r->proto_num = HTTP_VERSION(1,0);
+ /* Check for a valid protocol, and disallow everything but whitespace
+ * after the protocol string */
+ while (ap_isspace(r->protocol[n]))
+ ++n;
+ if (r->protocol[n] != '\0') {
+ r->status = HTTP_BAD_REQUEST;
+ r->proto_num = HTTP_VERSION(1,0);
+ r->protocol = ap_pstrdup(r->pool, "HTTP/1.0");
+ ap_table_setn(r->notes, "error-notes",
+ "The request line contained invalid characters "
+ "following the protocol string.<P>\n");
+ return 0;
+ }
+
return 1;
}
ap_log_transaction(r);
return r;
}
+ else if (r->status == HTTP_BAD_REQUEST) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+ "request failed: erroneous characters after protocol string: %s",
+ ap_escape_logitem(r->pool, r->the_request));
+ ap_send_error_response(r, 0);
+ ap_log_transaction(r);
+ return r;
+ }
return NULL;
}
if (!r->assbackwards) {
return (strncasecmp(&line[lidx], tok, tlen) == 0);
}
+
+/* escape a string for logging */
+API_EXPORT(char *) ap_escape_logitem(pool *p, const char *str)
+{
+ char *ret;
+ unsigned char *d;
+ const unsigned char *s;
+
+ if (str == NULL)
+ return NULL;
+
+ ret = ap_palloc(p, 4 * strlen(str) + 1); /* Be safe */
+ d = (unsigned char *)ret;
+ s = (const unsigned char *)str;
+ for (; *s; ++s) {
+
+ if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) {
+ *d++ = '\\';
+ switch(*s) {
+ case '\b':
+ *d++ = 'b';
+ break;
+ case '\n':
+ *d++ = 'n';
+ break;
+ case '\r':
+ *d++ = 'r';
+ break;
+ case '\t':
+ *d++ = 't';
+ break;
+ case '\v':
+ *d++ = 'v';
+ break;
+ case '\\':
+ case '"':
+ *d++ = *s;
+ break;
+ default:
+ c2x(*s, d);
+ *d = 'x';
+ d += 3;
+ }
+ }
+ else
+ *d++ = *s;
+ }
+ *d = '\0';
+
+ return ret;
+}
+
+
API_EXPORT(char *) ap_escape_shell_cmd(pool *p, const char *str)
{
char *cmd;
static const char *log_remote_host(request_rec *r, char *a)
{
- return ap_get_remote_host(r->connection, r->per_dir_config,
- REMOTE_NAME);
+ return ap_escape_logitem(r->pool, ap_get_remote_host(r->connection, r->per_dir_config,
+ REMOTE_NAME));
}
static const char *log_remote_address(request_rec *r, char *a)
static const char *log_remote_logname(request_rec *r, char *a)
{
- return ap_get_remote_logname(r);
+ return ap_escape_logitem(r->pool, ap_get_remote_logname(r));
}
static const char *log_remote_user(request_rec *r, char *a)
else if (strlen(rvalue) == 0) {
rvalue = "\"\"";
}
+ else
+ rvalue = ap_escape_logitem(r->pool, rvalue);
return rvalue;
}
* (note the truncation before the protocol string for HTTP/0.9 requests)
* (note also that r->the_request contains the unmodified request)
*/
- return (r->parsed_uri.password) ? ap_pstrcat(r->pool, r->method, " ",
+ return ap_escape_logitem(r->pool,
+ (r->parsed_uri.password) ? ap_pstrcat(r->pool, r->method, " ",
ap_unparse_uri_components(r->pool, &r->parsed_uri, 0),
r->assbackwards ? NULL : " ", r->protocol, NULL)
- : r->the_request;
+ : r->the_request
+ );
}
static const char *log_request_file(request_rec *r, char *a)
}
static const char *log_request_uri(request_rec *r, char *a)
{
- return r->uri;
+ return ap_escape_logitem(r->pool, r->uri);
}
static const char *log_request_method(request_rec *r, char *a)
{
- return r->method;
+ return ap_escape_logitem(r->pool, r->method);
}
static const char *log_request_protocol(request_rec *r, char *a)
{
- return r->protocol;
+ return ap_escape_logitem(r->pool, r->protocol);
}
static const char *log_request_query(request_rec *r, char *a)
{
- return (r->args != NULL) ? ap_pstrcat(r->pool, "?", r->args, NULL)
+ return (r->args != NULL) ? ap_pstrcat(r->pool, "?",
+ ap_escape_logitem(r->pool, r->args), NULL)
: "";
}
static const char *log_status(request_rec *r, char *a)
static const char *log_header_in(request_rec *r, char *a)
{
- return ap_table_get(r->headers_in, a);
+ return ap_escape_logitem(r->pool, ap_table_get(r->headers_in, a));
}
static const char *log_header_out(request_rec *r, char *a)
{
return ap_psprintf(r->pool, "%ld", (long) getpid());
}
+
static const char *log_connection_status(request_rec *r, char *a)
{
if (r->connection->aborted)
return "-";
}
+
/*****************************************************************
*
* Parsing the log format string
ap_each_byterange
ap_error_log2stderr
ap_escape_html
+ap_escape_logitem
ap_escape_path_segment
ap_escape_quotes
ap_escape_shell_cmd