综述 

Boa 作为一种轻巧实用的 WEB 服务器广泛应用于嵌入式设备上, 
但 Boa 对实现动态网页的 CGI  的支持上仍存在一些缺陷, 
本文描述了 Boa 对 CGI 的 Status/Location 的支持的缺陷及其修正方法.

版本: 所有版本 (0.94.13)
缺陷: BOA 解析 CGI 应答头时不能完整处理 Status 和 Location
缺陷描述:
CGI/1.1 标准规定, CGI 脚本可以通过 Status 设置 HTTP 应答状态(如, Status: 500 Internal Error) 和 
Location 进行地址重定向 (如, Location: www.xxx.com), 而不管它们在应答头中的位置.
Boa 支持 Stauts 和 Location 两种应答头, 但它的实现仅能正确处理 Stauts 和 Location 在应答第一行的
CGI 应答. 这将给 CGI 程序的移植带来很多不便, 进而影响 Boa 作为Web Server 的功能的发挥.

影响功能:
ASP/PHP/JSP/Perl/... 等的 header, redirect, ... 等都会应用到 Stauts/Location 进行设置应答状态和
地址重定向. Boa 的该实现将影响 CGI 脚本正常功能的使用.

缺陷功能对比(对Status/Location的支持程序):
Apache 1.3.x/2.x         IIS 4.x/5.x/6.X        Boa 0.9x                 thttpd                 mini-httpd
完全支持                        完全支持                * 部分支持                 完全支持               完全支持

缺陷分析

缺陷分析

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: ", 如下所示
 
 
  1. 23
  2. 24         int process_cgi_header(request * req)
  3. 25         {
  4. 26             char *buf;
  5. 27             char *c;
  6. 28
  7. 29             if (req->cgi_status != CGI_DONE)
  8. 30                 req->cgi_status = CGI_BUFFER;
  9. 31
  10. 32             buf = req->header_line;
  11. 33
  12. 34             c = strstr(buf, "\n\r\n");
  13. 35             if (c == NULL) {
  14. 36                 c = strstr(buf, "\n\n");
  15. 37                 if (c == NULL) {
  16. 38                     log_error_time();
  17. 39                     fputs("cgi_header: unable to find LFLF\n", stderr);
  18. 40         #ifdef FASCIST_LOGGING
  19. 41                     log_error_time();
  20. 42                     fprintf(stderr, "\"%s\"\n", buf);
  21. 43         #endif
  22. 44                     send_r_bad_gateway(req);
  23. 45                     return 0;
  24. 46                 }
  25. 47             }
  26. 48             if (req->simple) {
  27. 49                 if (*(c + 1) == '\r')
  28. 50                     req->header_line = c + 2;
  29. 51                 else
  30. 52                     req->header_line = c + 1;
  31. 53                 return 1;
  32. 54             }
  33. 55             if (!strncasecmp(buf, "Status: ", 8)) {
  34. 56                 req->header_line--;
  35. 57                 memcpy(req->header_line, "HTTP/1.0 ", 9);
  36. 58             } else if (!strncasecmp(buf, "Location: ", 10)) { /* got a location header */
  37. 59         #ifdef FASCIST_LOGGING
  38. 60
  39. 61                 log_error_time();
  40. 62                 fprintf(stderr, "%s:%d - found Location header \"%s\"\n",
  41. 63                         __FILE__, __LINE__, buf + 10);
  42. 64         #endif
  43. 65
  44. 66
  45. 67                 if (buf[10] == '/') {   /* virtual path */
  46. 68                     log_error_time();
  47. 69                     fprintf(stderr,
  48. 70                             "server does not support internal redirection: " \
  49. 71                             "\"%s\"\n", buf + 10);
  50. 72                     send_r_bad_request(req);
  51. 73
  52. 74                     /*
  53. 75                      * We (I, Jon) have declined to support absolute-path parsing
  54. 76                      * because I see it as a major security hole.
  55. 77                      * Location: /etc/passwd or Location: /etc/shadow is not funny.
  56. 78                      *
  57. 79                      * Also, the below code is borked.
  58. 80                      * request_uri could contain /cgi-bin/bob/extra_path
  59. 81                      */
  60. 82
  61. 83                     /*
  62. 84                        strcpy(req->request_uri, buf + 10);
  63. 85                        return internal_redirect(req);
  64. 86                      */
  65. 87                 } else {                /* URL */
  66. 88                     char *c2;
  67. 89                     c2 = strchr(buf + 10, '\n');
  68. 90                     /* c2 cannot ever equal NULL here because we already have found one */
  69. 91
  70. 92                     --c2;
  71. 93                     while (*c2 == '\r')
  72. 94                         --c2;
  73. 95                     ++c2;
  74. 96                     /* c2 now points to a '\r' or the '\n' */
  75. 97                     *c2++ = '\0';       /* end header */
  76. 98
  77. 99                     /* first next header, or is at req->header_end */
  78. 100                    while ((*c2 == '\n' || *c2 == '\r') && c2 < req->header_end)
  79. 101                        ++c2;
  80. 102                    if (c2 == req->header_end)
  81. 103                        send_r_moved_temp(req, buf + 10, "");
  82. 104                    else
  83. 105                        send_r_moved_temp(req, buf + 10, c2);
  84. 106                }
  85. 107                req->status = DONE;
  86. 108                return 1;
  87. 109            } else {                    /* not location and not status */
  88. 110                char *dest;
  89. 111                int howmuch;
  90. 112                send_r_request_ok(req); /* does not terminate */
  91. 113                /* got to do special things because
  92. 114                   a) we have a single buffer divided into 2 pieces
  93. 115                   b) we need to merge those pieces
  94. 116                   Easiest way is to memmove the cgi data backward until
  95. 117                   it touches the buffered data, then reset the cgi data pointers
  96. 118                 */
  97. 119                dest = req->buffer + req->buffer_end;
  98. 120                if (req->method == M_HEAD) {
  99. 121                    if (*(c + 1) == '\r')
  100. 122                        req->header_end = c + 2;
  101. 123                    else
  102. 124                        req->header_end = c + 1;
  103. 125                    req->cgi_status = CGI_DONE;
  104. 126                }
  105. 127                howmuch = req->header_end - req->header_line;
  106. 128
  107. 129                if (dest + howmuch > req->buffer + BUFFER_SIZE) {
  108. 130                    /* big problem */
  109. 131                    log_error_time();
  110. 130             fprintf(stderr, "Too much data to move! Aborting! %s %d\n",
  111. 131                    __FILE__, __LINE__);
  112. 132              /* reset buffer pointers because we already called
  113. 133                 send_r_request_ok... */
  114. 134              req->buffer_start = req->buffer_end = 0;
  115. 135              send_r_error(req);
  116. 136              return 0;
  117. 137          }
  118. 138          memmove(dest, req->header_line, howmuch);
  119. 139          req->buffer_end += howmuch;
  120. 140          req->header_line = req->buffer + req->buffer_end;
  121. 141          req->header_end = req->header_line;
  122. 142          req_flush(req);
  123. 143          if (req->method == M_HEAD)
  124. 144              return 0;
  125. 145      }
  126. 146      return 1;
  127. 147  }
  128. 148
  129. 149

修正方法

CGI 应答头包括多行, 我们必须对其进行逐行分析, 并作出正确的应答.
下面是修改好的源程序, 即将原来的 82-136 (即相当下文#else, #endif内部分) 替换成如下代码:

  1. #if 1
  2. while(1) {
  3. int         len;
  4. char *        pnext = NULL;
  5. char *         ptmp = NULL;
  6. /* not find HTTP header tailer */
  7. if (NULL == (pnext=strchr(buf, '\n')))        /* has no '\n' */
  8. break;
  9. /* the length of this line,
  10. * include '\n'
  11. */
  12. len = pnext - buf + 1;
  13. if (!strncasecmp(buf, "Location: ", 10)) {        /* got a location header */
  14. /* not the first one
  15. * exchange this line to the first line
  16. */
  17. if (buf != req->header_line)
  18. {
  19. if (NULL == (ptmp=(char *)malloc(len)))
  20. {
  21. log_error_time();
  22. perror("malloc");
  23. send_r_error(req);
  24. return 0;
  25. }
  26. /* move Status: to line header */
  27. memcpy(ptmp, buf, len);
  28. memmove(req->header_line+len, req->header_line, buf-req->header_line);
  29. memcpy(req->header_line, ptmp, len);
  30. free(ptmp);
  31. }
  32. /* force pointer header */
  33. buf = req->header_line;
  34. #ifdef FASCIST_LOGGING
  35. log_error_time();
  36. fprintf(stderr, "%s:%d - found Location header \"%s\"\n",
  37. __FILE__, __LINE__, buf + 10);
  38. #endif
  39. if (buf[10] == '/') {   /* virtual path */
  40. log_error_time();
  41. fprintf(stderr,
  42. "server does not support internal redirection: " \
  43. "\"%s\"\n", buf + 10);
  44. send_r_bad_request(req);
  45. /*
  46. * We (I, Jon) have declined to support absolute-path parsing
  47. * because I see it as a major security hole.
  48. * Location: /etc/passwd or Location: /etc/shadow is not funny.
  49. *
  50. * Also, the below code is borked.
  51. * request_uri could contain /cgi-bin/bob/extra_path
  52. */
  53. /*
  54. strcpy(req->request_uri, buf + 10);
  55. return internal_redirect(req);
  56. */
  57. } else {                /* URL */
  58. char *c2;
  59. c2 = strchr(buf + 10, '\n');
  60. /* c2 cannot ever equal NULL here because we already have found one */
  61. --c2;
  62. while (*c2 == '\r')
  63. --c2;
  64. ++c2;
  65. /* c2 now points to a '\r' or the '\n' */
  66. *c2++ = '\0';       /* end header */
  67. /* first next header, or is at req->header_end */
  68. while ((*c2 == '\n' || *c2 == '\r') && c2 < req->header_end)
  69. ++c2;
  70. if (c2 == req->header_end)
  71. send_r_moved_temp(req, buf + 10, "");
  72. else
  73. send_r_moved_temp(req, buf + 10, c2);
  74. }
  75. req->status = DONE;
  76. return 1;
  77. }  else if (!strncasecmp(buf, "Status: ", 8)) {
  78. /* not the first one
  79. * exchange this line to the first line
  80. */
  81. if (buf != req->header_line)
  82. {
  83. if (NULL == (ptmp=(char *)malloc(len)))
  84. {
  85. log_error_time();
  86. perror("malloc");
  87. send_r_error(req);
  88. return 0;
  89. }
  90. /* move Status: to line header */
  91. memcpy(ptmp, buf, len);
  92. memmove(req->header_line+len, req->header_line, buf-req->header_line);
  93. memcpy(req->header_line, ptmp, len);
  94. free(ptmp);
  95. }
  96. req->header_line--;
  97. memcpy(req->header_line, "HTTP/1.0 ", 9);
  98. return 1;
  99. }
  100. /* pointer to next line */
  101. buf = pnext + 1;
  102. /* reach the end of HTTP header */
  103. if ('\0' == buf[0] || '\n' == buf[0] || '\r' == buf[0])
  104. break;
  105. }
  106. if (1) {        /* always done */
  107. #else
  108. if (!strncasecmp(buf, "Status: ", 8)) {
  109. req->header_line--;
  110. memcpy(req->header_line, "HTTP/1.0 ", 9);
  111. } else if (!strncasecmp(buf, "Location: ", 10)) { /* got a location header */
  112. #ifdef FASCIST_LOGGING
  113. log_error_time();
  114. fprintf(stderr, "%s:%d - found Location header \"%s\"\n",
  115. __FILE__, __LINE__, buf + 10);
  116. #endif
  117. if (buf[10] == '/') {   /* virtual path */
  118. log_error_time();
  119. fprintf(stderr,
  120. "server does not support internal redirection: " \
  121. "\"%s\"\n", buf + 10);
  122. send_r_bad_request(req);
  123. /*
  124. * We (I, Jon) have declined to support absolute-path parsing
  125. * because I see it as a major security hole.
  126. * Location: /etc/passwd or Location: /etc/shadow is not funny.
  127. *
  128. * Also, the below code is borked.
  129. * request_uri could contain /cgi-bin/bob/extra_path
  130. */
  131. /*
  132. strcpy(req->request_uri, buf + 10);
  133. return internal_redirect(req);
  134. */
  135. } else {                /* URL */
  136. char *c2;
  137. c2 = strchr(buf + 10, '\n');
  138. /* c2 cannot ever equal NULL here because we already have found one */
  139. --c2;
  140. while (*c2 == '\r')
  141. --c2;
  142. ++c2;
  143. /* c2 now points to a '\r' or the '\n' */
  144. *c2++ = '\0';       /* end header */
  145. /* first next header, or is at req->header_end */
  146. while ((*c2 == '\n' || *c2 == '\r') && c2 < req->header_end)
  147. ++c2;
  148. if (c2 == req->header_end)
  149. send_r_moved_temp(req, buf + 10, "");
  150. else
  151. send_r_moved_temp(req, buf + 10, c2);
  152. }
  153. req->status = DONE;
  154. return 1;
  155. } else {                    /* not location and not status */
  156. #endif

转载连接:http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=824840

Boa Web Server 缺陷报告及其修正方法的更多相关文章

  1. 简易web server之python实现

    网络编程一项基本功是socket编程,包括TCP socket,UDP socket的客户端.服务器端编程. 应用层的各路协议如http,smtp,telnet,ftp等都依赖于传输层的TCP或者UD ...

  2. Tomcat建立多个应用(Web Server),多个主机,多个站点的方法

    https://blog.csdn.net/chungle2011/article/details/52317433 http://piperzero.iteye.com/blog/1475773 转 ...

  3. 咏南中间件当作WEB SERVER使用方法

    咏南中间件当作WEB SERVER使用方法 1)开启咏南中间件 2)浏览器打开http://localhost:5566/web?page=echo.html

  4. [Windows Server 2008] IIS配置伪静态方法(Web.config模式的IIS rewrite)

    ★ 欢迎来到[护卫神·V课堂],网站地址:http://v.huweishen.com★ 护卫神·V课堂 是护卫神旗下专业提供服务器教学视频的网站,每周更新视频.★ 本节我们将带领大家:安装伪静态(w ...

  5. VisualSVN Server的配置和使用方法(转)

    1.为什么要用VisualSVN Server,而不用Subversion? 回答: 因为如果直接使用Subversion,那么在Windows 系统上,要想让它随系统启动,就要封装SVN Serve ...

  6. Web Server PROPFIND Method internal IP Discosure

    Title:Web Server PROPFIND Method internal IP Discosure  --2012-11-09 09:47 Nessus扫描出来一个安全缺陷,Web Serv ...

  7. The Web server is configured to not list the contents of this directory.

    部署一个ASP.NET MVC网站至一个全新的服务器Windows Server 2008 R2, 数据为MS SQL Server 2014 64bit Expression版本. 运行时,它第一次 ...

  8. 【转】推荐介绍几款小巧的Web Server程序

    原博地址:http://blog.csdn.net/heiyeshuwu/article/details/1753900 偶然看到几个小巧有趣的Web Server程序,觉得有必要拿来分享一下,让大家 ...

  9. VisualSVN Server的配置和使用方法 图文

    转载 http://www.jb51.net/article/17365.htm VisualSVN Server是免费的,而VisualSVN是收费的.VisualSVN是SVN的客户端,和Visu ...

随机推荐

  1. 由孙悟空的七十二变看Java设计模式:装饰者模式

    目录 应用场景 代码示例 改进代码 装饰者模式 定义 意图 主要解决问题 何时使用 优缺点 孙悟空的七十二变 应用场景 京东.天猫双十一,情人节商品大促销,各种商品有不同的促销活动 满减:满200减5 ...

  2. Day02_15_方法重载

    方法重载 1.什么是方法重载? * 方法重载又被称为 OverLoad,是指在同一个类中,具有相同方法名的不同方法,各个方法虽然方法名相同,但是各自的形式参数不同. 2.什么时候考虑使用方法重载? * ...

  3. Day10_48_Map集合中的常用方法

    Map集合中的常用方法 * 常用方法 - 注意 Map集合中的key是无序不可重复的set集合,如果添加数据时,key值重复了,后面添加的重复数据也是可以添加成功的,但是会覆盖前面相同的数据. 1. ...

  4. kubernetes之pod拓扑分布约束

    在日常使用 kubernetes 的过程中中,很多时候我们并没有过多的关心 pod 的到底调度在哪里,只是通过多副本的测试,来提高的我们的业务的可用性,但是当多个相同业务 pod 在分布在相同节点时, ...

  5. 一文抽丝剥茧带你掌握复杂Gremlin查询的调试方法

    摘要:Gremlin是图数据库查询使用最普遍的基础查询语言.Gremlin的图灵完备性,使其能够编写非常复杂的查询语句.对于复杂的问题,我们该如何编写一个复杂的查询?以及我们该如何理解已有的复杂查询? ...

  6. Android最新敲诈者病毒分析及解锁(11月版)

    一.样本信息 文件名称:久秒名片赞,(无需积分s)(2)(1)(1).apk 文件大小:1497829字节 文件类型:application/jar 病毒类型:Android.CtLocker 样本包 ...

  7. CVE-2014-3153分析和利用

    本文是结合参考资料对CVE-2014-3153的分析,当然各位看官可以看最后的资料,他们写的比我好. 在看CVE-2014-3153之前我们用参考资料4中例子来熟悉下这类漏洞是如何产生的: /** * ...

  8. hdu4908 中位数子串

    题意:       给你N个数字组成的数列,然后问你这里面有多少个是以M为中位数的子序列. 思路:       首先分四中简单的情况求        (1) 就是只有他自己的那种情况 那么sum+1 ...

  9. POJ3422简单费用流

    题意:      给一个n*n的矩阵,从左上角走到右下角,的最大收益,可以走k次,每个格子的价值只能取一次,但是可以走多次. 思路:       比较简单的一个费用流题目,直接拆点,拆开的点之间连接两 ...

  10. jquery里面.length和.size()有什么区别

    区别: 1.针对标签对象元素,比如数html页面有多少个段落元素<p></p>,那么此时的$("p").size()==$("p").l ...