Return-Path: Delivered-To: apmail-new-httpd-archive@apache.org Received: (qmail 86507 invoked by uid 500); 28 Jul 2000 17:10:54 -0000 Mailing-List: contact new-httpd-help@apache.org; run by ezmlm Precedence: bulk X-No-Archive: yes Reply-To: new-httpd@apache.org list-help: list-unsubscribe: list-post: Delivered-To: mailing list new-httpd@apache.org Received: (qmail 86472 invoked from network); 28 Jul 2000 17:10:50 -0000 From: "William A. Rowe, Jr." To: Subject: [Patch 1.3.13] O(n^2) vulnerability in mod_isapi? Date: Fri, 28 Jul 2000 12:09:56 -0500 Message-ID: <002001bff8b6$cb53b420$345985d0@corecomm.net> MIME-Version: 1.0 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 7bit X-Priority: 3 (Normal) X-MSMail-Priority: Normal X-Mailer: Microsoft Outlook 8.5, Build 4.71.2173.0 X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2919.6600 Importance: Normal X-Spam-Rating: locus.apache.org 1.6.2 0/1000/N Folks, here are the changes from the 2.0 tree (which is -not- complete just yet) that apply to 1.3... It's a nearly identical patch as the one Dean noted should be a filter... but then again, 1.3.x will never see filters :) I added the termarg to indicate which string header processing stopped at. I may be misunderstanding the vulnerability that was cited, so if anyone has further info/comments/suggestions, I'm game. Please do so quickly, I'm out of town from Sat evening on through I don't know just when, so if we like this, collectively, someone else may have to apply. The function needs to be added to apachecore.def. Consider it proof of concept till someone confirms this was the source of the O(n^2) attack, and then I'll bash it if I know that's the concept by noon Saturday. Otherwise whomever advocates needs to test it thoroughly. If I'm lucky, I get back Wednesday night :( Bill Index: include/util_script.h =================================================================== RCS file: /home/cvs/apache-1.3/src/include/util_script.h,v retrieving revision 1.36 diff -u -r1.36 util_script.h --- include/util_script.h 1999/01/08 17:54:40 1.36 +++ include/util_script.h 2000/07/28 16:59:26 @@ -80,6 +80,10 @@ API_EXPORT(int) ap_scan_script_header_err_core(request_rec *r, char *buffer, int (*getsfunc) (char *, int, void *), void *getsfunc_data); +API_EXPORT_NONSTD(int) ap_scan_script_header_err_strs(request_rec *r, + char *buffer, + const char **termch, + int *termarg, ...); API_EXPORT(void) ap_send_size(size_t size, request_rec *r); API_EXPORT(int) ap_call_exec(request_rec *r, child_info *pinfo, char *argv0, char **env, int shellcmd); Index: main/util_script.c =================================================================== RCS file: /home/cvs/apache-1.3/src/main/util_script.c,v retrieving revision 1.151 diff -u -r1.151 util_script.c --- main/util_script.c 2000/03/03 05:59:11 1.151 +++ main/util_script.c 2000/07/28 16:59:29 @@ -649,6 +649,62 @@ return ap_scan_script_header_err_core(r, buffer, getsfunc_BUFF, fb); } +struct vastrs { + va_list args; + int arg; + const char *curpos; +}; + +static int getsfunc_STRING(char *w, int len, void *pvastrs) +{ + struct vastrs *strs = (struct vastrs*) pvastrs; + char *p; + int t; + + if (!strs->curpos || !*strs->curpos) + return 0; + p = strchr(strs->curpos, '\n'); + if (p) + ++p; + else + p = strchr(strs->curpos, '\0'); + t = p - strs->curpos; + if (t > len) + t = len; + strncpy (w, strs->curpos, t); + if (!strs->curpos[t]) { + ++strs->arg; + strs->curpos = va_arg(strs->args, const char *); + } + else + strs->curpos += t; + return t; +} + +/* ap_scan_script_header_err_strs() accepts additional const char* args... + * each is treated as one or more header lines, and the first non-header + * character is returned to **termarg, **termch. (The first optional + * (char*) arg is counted as 0.) + */ +API_EXPORT_NONSTD(int) ap_scan_script_header_err_strs(request_rec *r, + char *buffer, + const char **termch, + int *termarg, ...) +{ + struct vastrs strs; + int res; + + va_start(strs.args, buffer); + strs.arg = 0; + strs.curpos = va_arg(strs.args, char*); + res = ap_scan_script_header_err_core(r, buffer, getsfunc_STRING, (void *) &strs); + if (termch) + *termch = strs.curpos; + if (termarg) + *termarg = strs.arg; + va_end(strs.args); + return res; +} API_EXPORT(void) ap_send_size(size_t size, request_rec *r) { Index: os/win32/mod_isapi.c =================================================================== RCS file: /home/cvs/apache-1.3/src/os/win32/mod_isapi.c,v retrieving revision 1.18 diff -u -r1.18 mod_isapi.c --- os/win32/mod_isapi.c 1999/08/31 15:22:40 1.18 +++ os/win32/mod_isapi.c 2000/07/28 16:59:44 @@ -402,111 +402,23 @@ return TRUE; case HSE_REQ_SEND_RESPONSE_HEADER: - r->status_line = lpvBuffer ? lpvBuffer : ap_pstrdup(r->pool, "200 OK"); - sscanf(r->status_line, "%d", &r->status); - cid->ecb->dwHttpStatusCode = r->status; - - /* Now fill in the HTTP headers, and the rest of it. Ick. - * lpdwDataType contains a string that has headers (in MIME - * format), a blank like, then (possibly) data. We need - * to parse it. - * - * Easy case first: + /* Now fill in the HTTP headers, and the rest of it. + * lpdwDataType may contain the status, or we respond 200 OK. + * lpdwDataType may contain a string that has formatted headers + * a blank line, then (possibly) data. Uses the util_script parser. */ - if (!lpdwDataType) { - ap_send_http_header(r); - return TRUE; - } - - /* Make a copy - don't disturb the original */ - data = ap_pstrdup(r->pool, (char *)lpdwDataType); - - /* We *should* break before this while loop ends */ - while (*data) { - char *value, *lf = strchr(data, '\n'); - int p; - -#ifdef RELAX_HEADER_RULE - if (lf) - *lf = '\0'; -#else - if (!lf) { /* Huh? Invalid data, I think */ - ap_log_rerror(APLOG_MARK, APLOG_ERR, r, - "ISA sent invalid headers: %s", r->filename); - SetLastError(ERROR); /* XXX: Find right error */ - return FALSE; - } - - /* Get rid of \n and \r */ - *lf = '\0'; -#endif - p = strlen(data); - if (p > 0 && data[p-1] == '\r') data[p-1] = '\0'; - - /* End of headers */ - if (*data == '\0') { -#ifdef RELAX_HEADER_RULE - if (lf) -#endif - data = lf + 1; /* Reset data */ - break; - } - - if (!(value = strchr(data, ':'))) { - SetLastError(ERROR); /* XXX: Find right error */ - ap_log_rerror(APLOG_MARK, APLOG_ERR, r, - "ISA sent invalid headers", r->filename); - return FALSE; - } - - *value++ = '\0'; - while (*value && ap_isspace(*value)) ++value; - - /* Check all the special-case headers. Similar to what - * ap_scan_script_header_err() does (see that function for - * more detail) - */ + cid->status = ap_scan_script_header_err_strs(r, NULL, &data, NULL, + lpvBuffer ? lpvBuffer : "200 OK", + lpdwDataType ? (char*)lpdwDataType : "\n", NULL); + cid->ecb->dwHttpStatusCode = r->status; - if (!strcasecmp(data, "Content-Type")) { - char *tmp; - /* Nuke trailing whitespace */ - - char *endp = value + strlen(value) - 1; - while (endp > value && ap_isspace(*endp)) *endp-- = '\0'; - - tmp = ap_pstrdup (r->pool, value); - ap_str_tolower(tmp); - r->content_type = tmp; - } - else if (!strcasecmp(data, "Content-Length")) { - ap_table_set(r->headers_out, data, value); - } - else if (!strcasecmp(data, "Transfer-Encoding")) { - ap_table_set(r->headers_out, data, value); - } - else if (!strcasecmp(data, "Set-Cookie")) { - ap_table_add(r->err_headers_out, data, value); - } - else { - ap_table_merge(r->err_headers_out, data, value); - } - - /* Reset data */ -#ifdef RELAX_HEADER_RULE - if (!lf) { - data += p; - break; - } -#endif - data = lf + 1; - } - - /* All the headers should be set now */ + /* All the headers should be set now */ ap_send_http_header(r); /* Any data left should now be sent directly */ - ap_rputs(data, r); + if (data && *data) + ap_rputs(data, r); return TRUE;