Module Name: src Committed By: mrg Date: Fri Apr 15 17:57:21 UTC 2016
Modified Files: src/libexec/httpd: CHANGES bozohttpd.8 bozohttpd.c bozohttpd.h cgi-bozo.c Log Message: updates and bozohttpd 20160415: o add search-word support for CGI o fix a security issue in CGI suffix handler support which would allow remote code execution, from s...@netbsd.org o -C option supports now CGI scripts only To generate a diff of this commit: cvs rdiff -u -r1.21 -r1.22 src/libexec/httpd/CHANGES cvs rdiff -u -r1.58 -r1.59 src/libexec/httpd/bozohttpd.8 cvs rdiff -u -r1.79 -r1.80 src/libexec/httpd/bozohttpd.c cvs rdiff -u -r1.44 -r1.45 src/libexec/httpd/bozohttpd.h cvs rdiff -u -r1.32 -r1.33 src/libexec/httpd/cgi-bozo.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/libexec/httpd/CHANGES diff -u src/libexec/httpd/CHANGES:1.21 src/libexec/httpd/CHANGES:1.22 --- src/libexec/httpd/CHANGES:1.21 Wed Oct 28 09:20:15 2015 +++ src/libexec/httpd/CHANGES Fri Apr 15 17:57:21 2016 @@ -1,5 +1,11 @@ $eterna: CHANGES,v 1.78 2011/11/18 01:25:11 mrg Exp $ +changes in bozohttpd 20160415: + o add search-word support for CGI + o fix a security issue in CGI suffix handler support which would + allow remote code execution, from s...@netbsd.org + o -C option supports now CGI scripts only + changes in bozohttpd 20151028: o add CGI support for ~user translation (-E switch) o add redirects to ~user translation Index: src/libexec/httpd/bozohttpd.8 diff -u src/libexec/httpd/bozohttpd.8:1.58 src/libexec/httpd/bozohttpd.8:1.59 --- src/libexec/httpd/bozohttpd.8:1.58 Sun Dec 27 10:21:35 2015 +++ src/libexec/httpd/bozohttpd.8 Fri Apr 15 17:57:21 2016 @@ -1,4 +1,4 @@ -.\" $NetBSD: bozohttpd.8,v 1.58 2015/12/27 10:21:35 mrg Exp $ +.\" $NetBSD: bozohttpd.8,v 1.59 2016/04/15 17:57:21 mrg Exp $ .\" .\" $eterna: bozohttpd.8,v 1.101 2011/11/18 01:25:11 mrg Exp $ .\" @@ -507,7 +507,7 @@ with PHP, one must use the option to specify a CGI handler for a particular file type. Typically this will be like: .Bd -literal -httpd -C .php /usr/pkg/bin/php /var/www +httpd -C .php /usr/pkg/bin/php-cgi /var/www .Ed .Sh SEE ALSO .Xr inetd.conf 5 , @@ -615,7 +615,8 @@ provided many fixes and enhancements for .Aq Mt s...@netbsd.org fixed memory leaks, various issues with userdir support, information disclosure issues, added support for using CGI handlers -with directory indexing and provided various other fixes. +with directory indexing, found several security issues and provided +various other fixes. .It .An Arnaud Lacombe .Aq Mt a...@netbsd.org Index: src/libexec/httpd/bozohttpd.c diff -u src/libexec/httpd/bozohttpd.c:1.79 src/libexec/httpd/bozohttpd.c:1.80 --- src/libexec/httpd/bozohttpd.c:1.79 Sat Jan 2 20:35:59 2016 +++ src/libexec/httpd/bozohttpd.c Fri Apr 15 17:57:21 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: bozohttpd.c,v 1.79 2016/01/02 20:35:59 elric Exp $ */ +/* $NetBSD: bozohttpd.c,v 1.80 2016/04/15 17:57:21 mrg Exp $ */ /* $eterna: bozohttpd.c,v 1.178 2011/11/18 09:21:15 mrg Exp $ */ @@ -109,7 +109,7 @@ #define INDEX_HTML "index.html" #endif #ifndef SERVER_SOFTWARE -#define SERVER_SOFTWARE "bozohttpd/20151231" +#define SERVER_SOFTWARE "bozohttpd/20160415" #endif #ifndef DIRECT_ACCESS_FILE #define DIRECT_ACCESS_FILE ".bzdirect" @@ -1288,19 +1288,17 @@ check_bzredirect(bozo_httpreq_t *request } /* this fixes the %HH hack that RFC2396 requires. */ -static int -fix_url_percent(bozo_httpreq_t *request) +int +bozo_decode_url_percent(bozo_httpreq_t *request, char *str) { bozohttpd_t *httpd = request->hr_httpd; - char *s, *t, buf[3], *url; + char *s, *t, buf[3]; char *end; /* if end is not-zero, we don't translate beyond that */ - url = request->hr_file; - - end = url + strlen(url); + end = str + strlen(str); /* fast forward to the first % */ - if ((s = strchr(url, '%')) == NULL) + if ((s = strchr(str, '%')) == NULL) return 0; t = s; @@ -1352,7 +1350,7 @@ fix_url_percent(bozo_httpreq_t *request) } while (*s); *t = '\0'; - debug((httpd, DEBUG_FAT, "fix_url_percent returns %s in url", + debug((httpd, DEBUG_FAT, "bozo_decode_url_percent returns `%s'", request->hr_file)); return 0; @@ -1383,7 +1381,7 @@ transform_request(bozo_httpreq_t *reques file = NULL; *isindex = 0; debug((httpd, DEBUG_FAT, "tf_req: file %s", request->hr_file)); - if (fix_url_percent(request)) { + if (bozo_decode_url_percent(request, request->hr_file)) { goto bad_done; } if (check_virtual(request)) { Index: src/libexec/httpd/bozohttpd.h diff -u src/libexec/httpd/bozohttpd.h:1.44 src/libexec/httpd/bozohttpd.h:1.45 --- src/libexec/httpd/bozohttpd.h:1.44 Sat Jan 2 18:40:13 2016 +++ src/libexec/httpd/bozohttpd.h Fri Apr 15 17:57:21 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: bozohttpd.h,v 1.44 2016/01/02 18:40:13 elric Exp $ */ +/* $NetBSD: bozohttpd.h,v 1.45 2016/04/15 17:57:21 mrg Exp $ */ /* $eterna: bozohttpd.h,v 1.39 2011/11/18 09:21:15 mrg Exp $ */ @@ -227,6 +227,7 @@ void bozo_print_header(bozo_httpreq_t *, const char *); char *bozo_escape_rfc3986(bozohttpd_t *httpd, const char *url, int absolute); char *bozo_escape_html(bozohttpd_t *httpd, const char *url); +int bozo_decode_url_percent(bozo_httpreq_t *, char *); /* these are similar to libc functions, no underscore here */ void bozowarn(bozohttpd_t *, const char *, ...) Index: src/libexec/httpd/cgi-bozo.c diff -u src/libexec/httpd/cgi-bozo.c:1.32 src/libexec/httpd/cgi-bozo.c:1.33 --- src/libexec/httpd/cgi-bozo.c:1.32 Thu Dec 31 04:39:16 2015 +++ src/libexec/httpd/cgi-bozo.c Fri Apr 15 17:57:21 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: cgi-bozo.c,v 1.32 2015/12/31 04:39:16 mrg Exp $ */ +/* $NetBSD: cgi-bozo.c,v 1.33 2016/04/15 17:57:21 mrg Exp $ */ /* $eterna: cgi-bozo.c,v 1.40 2011/11/18 09:21:15 mrg Exp $ */ @@ -212,6 +212,136 @@ append_index_html(bozohttpd_t *httpd, ch "append_index_html: url adjusted to `%s'", *url)); } +/* This function parse search-string according to section 4.4 of RFC3875 */ +static char ** +parse_search_string(bozo_httpreq_t *request, const char *query, size_t *args_len) +{ + struct bozohttpd_t *httpd = request->hr_httpd; + size_t i; + char *s, *str, **args; + + *args_len = 0; + + /* URI MUST not contain any unencoded '=' - RFC3875, section 4.4 */ + if (strchr(query, '=')) { + return NULL; + } + + str = bozostrdup(httpd, request, query); + + /* + * there's no more arguments than '+' chars in the query string as it's + * the separator + */ + *args_len = 1; + /* count '+' in str */ + for (s = str; (s = strchr(s, '+')); (*args_len)++); + + args = bozomalloc(httpd, sizeof(*args) * (*args_len + 1)); + + args[0] = str; + args[*args_len] = NULL; + for (s = str, i = 0; (s = strchr(s, '+'));) { + *s = '\0'; + s++; + args[i++] = s; + } + + /* + * check if search-strings are valid: + * + * RFC3875, section 4.4: + * + * search-string = search-word *( "+" search-word ) + * search-word = 1*schar + * schar = unreserved | escaped | xreserved + * xreserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "," | + * "$" + * + * section 2.3: + * + * hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | "a" | + * "b" | "c" | "d" | "e" | "f" + * escaped = "%" hex hex + * unreserved = alpha | digit | mark + * mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" + * + * section 2.2: + * + * alpha = lowalpha | hialpha + * lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | + * "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | + * "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | + * "y" | "z" + * hialpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | + * "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | + * "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | + * "Y" | "Z" + * digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | + * "8" | "9" + */ +#define UNRESERVED_CHAR "-_.!~*'()" +#define XRESERVED_CHAR ";/?:@&=,$" + + for (i = 0; i < *args_len; i++) { + s = args[i]; + /* search-word MUST have at least one schar */ + if (*s == '\0') + goto parse_err; + while(*s) { + /* check if it's unreserved */ + if (isalpha((int)*s) || isdigit((int)*s) || + strchr(UNRESERVED_CHAR, *s)) { + s++; + continue; + } + + /* check if it's escaped */ + if (*s == '%') { + if (s[1] == '\0' || s[2] == '\0') + goto parse_err; + if (!isxdigit((int)s[1]) || + !isxdigit((int)s[2])) + goto parse_err; + s += 3; + continue; + } + + /* check if it's xreserved */ + + if (strchr(XRESERVED_CHAR, *s)) { + s++; + continue; + } + + goto parse_err; + } + } + + /* decode percent encoding */ + for (i = 0; i < *args_len; i++) { + if (bozo_decode_url_percent(request, args[i])) + goto parse_err; + } + + /* allocate each arg separately */ + for (i = 0; i < *args_len; i++) + args[i] = bozostrdup(httpd, request, args[i]); + free(str); + + return args; + +parse_err: + + free (*args); + free (str); + *args = NULL; + *args_len = 0; + + return 0; + +} + void bozo_cgi_setbin(bozohttpd_t *httpd, const char *path) { @@ -249,9 +379,9 @@ bozo_process_cgi(bozo_httpreq_t *request bozoheaders_t *headp; const char *type, *clen, *info, *cgihandler; char *query, *s, *t, *path, *env, *command, *file, *url; - char **envp, **curenvp, *argv[4]; + char **envp, **curenvp, **argv, **search_string_argv = NULL; char *uri; - size_t len; + size_t i, len, search_string_argc = 0; ssize_t rbytes; pid_t pid; int envpsize, ix, nph; @@ -312,12 +442,25 @@ bozo_process_cgi(bozo_httpreq_t *request } else if (len - 1 == CGIBIN_PREFIX_LEN) /* url is "/cgi-bin/" */ append_index_html(httpd, &file); + /* RFC3875 sect. 4.4. - search-string support */ + if (query != NULL) { + search_string_argv = parse_search_string(request, query, + &search_string_argc); + } + + debug((httpd, DEBUG_NORMAL, "parse_search_string args no: %lu", + search_string_argc)); + for (i = 0; i < search_string_argc; i++) { + debug((httpd, DEBUG_FAT, + "search_string[%lu]: `%s'", i, search_string_argv[i])); + } + + argv = bozomalloc(httpd, sizeof(*argv) * (3 + search_string_argc)); + ix = 0; if (cgihandler) { command = file + 1; path = bozostrdup(httpd, request, cgihandler); - argv[ix++] = path; - /* argv[] = [ path, command, query, NULL ] */ } else { command = file + CGIBIN_PREFIX_LEN + 1; if ((s = strchr(command, '/')) != NULL) { @@ -329,12 +472,15 @@ bozo_process_cgi(bozo_httpreq_t *request strcpy(path, httpd->cgibin); strcat(path, "/"); strcat(path, command); - /* argv[] = [ command, query, NULL ] */ } - argv[ix++] = command; - argv[ix++] = query; - argv[ix++] = NULL; + argv[ix++] = path; + + /* copy search-string args */ + for (i = 0; i < search_string_argc; i++) + argv[ix++] = search_string_argv[i]; + + argv[ix++] = NULL; nph = strncmp(command, "nph-", 4) == 0; type = request->hr_content_type; @@ -400,8 +546,11 @@ bozo_process_cgi(bozo_httpreq_t *request bozo_setenv(httpd, "REQUEST_URI", uri, curenvp++); bozo_setenv(httpd, "DATE_GMT", bozo_http_date(date, sizeof(date)), curenvp++); + /* RFC3875 section 4.1.7 says that QUERY_STRING MUST be defined. */ if (query && *query) bozo_setenv(httpd, "QUERY_STRING", query, curenvp++); + else + bozo_setenv(httpd, "QUERY_STRING", "", curenvp++); if (info && *info) bozo_setenv(httpd, "PATH_INFO", info, curenvp++); if (type && *type) @@ -425,8 +574,13 @@ bozo_process_cgi(bozo_httpreq_t *request bozo_setenv(httpd, "REDIRECT_STATUS", "200", curenvp++); bozo_auth_cgi_setenv(request, &curenvp); - debug((httpd, DEBUG_FAT, "bozo_process_cgi: going exec %s, %s %s %s", - path, argv[0], strornull(argv[1]), strornull(argv[2]))); + debug((httpd, DEBUG_FAT, "bozo_process_cgi: going exec %s with args:", + path)); + + for (i = 0; argv[i] != NULL; i++) { + debug((httpd, DEBUG_FAT, "bozo_process_cgi: argv[%lu] = `%s'", + i, argv[i])); + } if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv) == -1) bozoerr(httpd, 1, "child socketpair failed: %s", @@ -461,6 +615,9 @@ bozo_process_cgi(bozo_httpreq_t *request free(query); free(file); free(url); + for (i = 0; i < search_string_argc; i++) + free(search_string_argv[i]); + free(search_string_argv); close(sv[1]); @@ -500,6 +657,10 @@ bozo_process_cgi(bozo_httpreq_t *request exit(0); out: + + for (i = 0; i < search_string_argc; i++) + free(search_string_argv[i]); + free(search_string_argv); free(query); free(file); free(url);