缺陷分析 CGI 应用程序进行应答时, 可以 HTTP 头进行有限的控制. 如,设置客户端不缓存页面可用下面的 C 脚本, HTTP/1.0: printf("Pragma: no-cache\n"); 或 HTTP/1.1: printf("Cache-Control: no-cache; no-store\n"); 如果, 同时还需要告诉浏览器进行设置 Cookie 和控制相应状态(200 OK) 或地址重定向, 那么就必须输出多行 http 头控制语句, CGI 支持两个解析头 "Status: " 和 "Loction: ", 即协议规定, Web 服务器支持解析头时能使用 "Status: " 进行应答状态控制, 使用 "Location: " 进行地址重定向, 并为应答添加状态头 "HTTP/1.0 302 Moved Temporarily\n" 或 "HTTP/1.1 302 Found\n". 而不管它们在 CGI 应答头的什么位置. 分析 Boa Source Code: cgi_header.c Line 82-136 容易发现, Boa 只解析 CGI 应答的第一行, 是否为 "Status: ", "Location: ", 如下所示 23
24 int process_cgi_header(request * req)
25 {
26 char *buf;
27 char *c;
28
29 if (req->cgi_status != CGI_DONE)
30 req->cgi_status = CGI_BUFFER;
31
32 buf = req->header_line;
33
34 c = strstr(buf, "\n\r\n");
35 if (c == NULL) {
36 c = strstr(buf, "\n\n");
37 if (c == NULL) {
38 log_error_time();
39 fputs("cgi_header: unable to find LFLF\n", stderr);
40 #ifdef FASCIST_LOGGING
41 log_error_time();
42 fprintf(stderr, "\"%s\"\n", buf);
43 #endif
44 send_r_bad_gateway(req);
45 return 0;
46 }
47 }
48 if (req->simple) {
49 if (*(c + 1) == '\r')
50 req->header_line = c + 2;
51 else
52 req->header_line = c + 1;
53 return 1;
54 }
55 if (!strncasecmp(buf, "Status: ", 8)) {
56 req->header_line--;
57 memcpy(req->header_line, "HTTP/1.0 ", 9);
58 } else if (!strncasecmp(buf, "Location: ", 10)) { /* got a location header */
59 #ifdef FASCIST_LOGGING
60
61 log_error_time();
62 fprintf(stderr, "%s:%d - found Location header \"%s\"\n",
63 __FILE__, __LINE__, buf + 10);
64 #endif
65
66
67 if (buf[10] == '/') { /* virtual path */
68 log_error_time();
69 fprintf(stderr,
70 "server does not support internal redirection: " \
71 "\"%s\"\n", buf + 10);
72 send_r_bad_request(req);
73
74 /*
75 * We (I, Jon) have declined to support absolute-path parsing
76 * because I see it as a major security hole.
77 * Location: /etc/passwd or Location: /etc/shadow is not funny.
78 *
79 * Also, the below code is borked.
80 * request_uri could contain /cgi-bin/bob/extra_path
81 */
82
83 /*
84 strcpy(req->request_uri, buf + 10);
85 return internal_redirect(req);
86 */
87 } else { /* URL */
88 char *c2;
89 c2 = strchr(buf + 10, '\n');
90 /* c2 cannot ever equal NULL here because we already have found one */
91
92 --c2;
93 while (*c2 == '\r')
94 --c2;
95 ++c2;
96 /* c2 now points to a '\r' or the '\n' */
97 *c2++ = '\0'; /* end header */
98
99 /* first next header, or is at req->header_end */
100 while ((*c2 == '\n' || *c2 == '\r') && c2 < req->header_end)
101 ++c2;
102 if (c2 == req->header_end)
103 send_r_moved_temp(req, buf + 10, "");
104 else
105 send_r_moved_temp(req, buf + 10, c2);
106 }
107 req->status = DONE;
108 return 1;
109 } else { /* not location and not status */
110 char *dest;
111 int howmuch;
112 send_r_request_ok(req); /* does not terminate */
113 /* got to do special things because
114 a) we have a single buffer divided into 2 pieces
115 b) we need to merge those pieces
116 Easiest way is to memmove the cgi data backward until
117 it touches the buffered data, then reset the cgi data pointers
118 */
119 dest = req->buffer + req->buffer_end;
120 if (req->method == M_HEAD) {
121 if (*(c + 1) == '\r')
122 req->header_end = c + 2;
123 else
124 req->header_end = c + 1;
125 req->cgi_status = CGI_DONE;
126 }
127 howmuch = req->header_end - req->header_line;
128
129 if (dest + howmuch > req->buffer + BUFFER_SIZE) {
130 /* big problem */
131 log_error_time();
130 fprintf(stderr, "Too much data to move! Aborting! %s %d\n",
131 __FILE__, __LINE__);
132 /* reset buffer pointers because we already called
133 send_r_request_ok... */
134 req->buffer_start = req->buffer_end = 0;
135 send_r_error(req);
136 return 0;
137 }
138 memmove(dest, req->header_line, howmuch);
139 req->buffer_end += howmuch;
140 req->header_line = req->buffer + req->buffer_end;
141 req->header_end = req->header_line;
142 req_flush(req);
143 if (req->method == M_HEAD)
144 return 0;
145 }
146 return 1;
147 }
148
149
修正方法 CGI 应答头包括多行, 我们必须对其进行逐行分析, 并作出正确的应答. 下面是修改好的源程序, 即将原来的 82-136 (即相当下文#else, #endif内部分) 替换成如下代码: | #if 1
while(1) {
int len;
char * pnext = NULL;
char * ptmp = NULL;
/* not find HTTP header tailer */
if (NULL == (pnext=strchr(buf, '\n'))) /* has no '\n' */
break;
/* the length of this line,
* include '\n'
*/
len = pnext - buf + 1;
if (!strncasecmp(buf, "Location: ", 10)) { /* got a location header */
/* not the first one
* exchange this line to the first line
*/
if (buf != req->header_line)
{
if (NULL == (ptmp=(char *)malloc(len)))
{
log_error_time();
perror("malloc");
send_r_error(req);
return 0;
}
/* move Status: to line header */
memcpy(ptmp, buf, len);
memmove(req->header_line+len, req->header_line, buf-req->header_line);
memcpy(req->header_line, ptmp, len);
free(ptmp);
}
/* force pointer header */
buf = req->header_line;
#ifdef FASCIST_LOGGING
log_error_time();
fprintf(stderr, "%s:%d - found Location header \"%s\"\n",
__FILE__, __LINE__, buf + 10);
#endif
if (buf[10] == '/') { /* virtual path */
log_error_time();
fprintf(stderr,
"server does not support internal redirection: " \
"\"%s\"\n", buf + 10);
send_r_bad_request(req);
/*
* We (I, Jon) have declined to support absolute-path parsing
* because I see it as a major security hole.
* Location: /etc/passwd or Location: /etc/shadow is not funny.
*
* Also, the below code is borked.
* request_uri could contain /cgi-bin/bob/extra_path
*/
/*
strcpy(req->request_uri, buf + 10);
return internal_redirect(req);
*/
} else { /* URL */
char *c2;
c2 = strchr(buf + 10, '\n');
/* c2 cannot ever equal NULL here because we already have found one */
--c2;
while (*c2 == '\r')
--c2;
++c2;
/* c2 now points to a '\r' or the '\n' */
*c2++ = '\0'; /* end header */
/* first next header, or is at req->header_end */
while ((*c2 == '\n' || *c2 == '\r') && c2 < req->header_end)
++c2;
if (c2 == req->header_end)
send_r_moved_temp(req, buf + 10, "");
else
send_r_moved_temp(req, buf + 10, c2);
}
req->status = DONE;
return 1;
} else if (!strncasecmp(buf, "Status: ", 8)) {
/* not the first one
* exchange this line to the first line
*/
if (buf != req->header_line)
{
if (NULL == (ptmp=(char *)malloc(len)))
{
log_error_time();
perror("malloc");
send_r_error(req);
return 0;
}
/* move Status: to line header */
memcpy(ptmp, buf, len);
memmove(req->header_line+len, req->header_line, buf-req->header_line);
memcpy(req->header_line, ptmp, len);
free(ptmp);
}
req->header_line--;
memcpy(req->header_line, "HTTP/1.0 ", 9);
return 1;
}
/* pointer to next line */
buf = pnext + 1;
/* reach the end of HTTP header */
if ('\0' == buf[0] || '\n' == buf[0] || '\r' == buf[0])
break;
}
if (1) { /* always done */
#else
if (!strncasecmp(buf, "Status: ", 8)) {
req->header_line--;
memcpy(req->header_line, "HTTP/1.0 ", 9);
} else if (!strncasecmp(buf, "Location: ", 10)) { /* got a location header */
#ifdef FASCIST_LOGGING
log_error_time();
fprintf(stderr, "%s:%d - found Location header \"%s\"\n",
__FILE__, __LINE__, buf + 10);
#endif
if (buf[10] == '/') { /* virtual path */
log_error_time();
fprintf(stderr,
"server does not support internal redirection: " \
"\"%s\"\n", buf + 10);
send_r_bad_request(req);
/*
* We (I, Jon) have declined to support absolute-path parsing
* because I see it as a major security hole.
* Location: /etc/passwd or Location: /etc/shadow is not funny.
*
* Also, the below code is borked.
* request_uri could contain /cgi-bin/bob/extra_path
*/
/*
strcpy(req->request_uri, buf + 10);
return internal_redirect(req);
*/
} else { /* URL */
char *c2;
c2 = strchr(buf + 10, '\n');
/* c2 cannot ever equal NULL here because we already have found one */
--c2;
while (*c2 == '\r')
--c2;
++c2;
/* c2 now points to a '\r' or the '\n' */
*c2++ = '\0'; /* end header */
/* first next header, or is at req->header_end */
while ((*c2 == '\n' || *c2 == '\r') && c2 < req->header_end)
++c2;
if (c2 == req->header_end)
send_r_moved_temp(req, buf + 10, "");
else
send_r_moved_temp(req, buf + 10, c2);
}
req->status = DONE;
return 1;
} else { /* not location and not status */
#endif
|