本文于2016年4月完成,发布在个人博客网站上。 考虑个人博客因某种原因无法修复,于是在博客园安家,之前发布的文章逐步搬迁过来。


诡异的问题

分析AppScan扫描报告的时候,发现报告里提示“HTTP动词篡改导致的认证旁路”,一个名字很长,很怪异的问题。咨询度娘没有获取到必要的信息,于是只好按照AppScan报告里给出的重现步骤,实地操作来看看。

AppScan给出的复现步骤很简单,如下:

  1. 使用burpsuite拦截浏览器发出的HTTP请求,如下为样例:

     GET /index.jsp HTTP/1.1 Host: 127.0.0.1:8080 Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36 Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8 Connection: close
  2. 修改HTTP方法,比如把GET修改为BOGUS,然后点击Forward,把请求转发给Web服务器。

     BOGUS /index.jsp HTTP/1.1 Host: 127.0.0.1:8080 Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36 Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8 Connection: close
  3. 令人大跌眼镜的事情发生了,Web服务器居然返回了HTTP 200,同时正确的返回了页面;

顿时让人有点小郁闷。按照产品线要求,当前所在项目使用的是平台部门基于Apache Tomcat 7.0.x源码做过加固的版本,按理说不该存在类似的安全问题。

对照Tomcat加固要求,重新检查了$CATALINA_BASE/conf/web.xml,确认其中包含了如下配置,这说明已经屏蔽了不安全的HTTP方法,应该是没有问题才对。

            HEAD        PUT        DELETE        OPTIONS        TRACE        /*                    

那么为什么AppScan扫描报告中出现了前述问题,Jackie一时有些想不明白。

项目的技术特点

当前所在项目使用了Spring+Struts2+iBatis,从技术组合上可以说非常传统,但在技术应用上存在很大不同,比如:

  • 浏览器请求页面时,使用通用Action将请求重定向至对应的jsp页面;
  • 页面上使用jQuery提供的ajax对象来加载呈现需要的数据;
  • 为了实现项目国际化的需求,要求所有对jsp的请求都需要经过通用Action的转发;
  • 页面代码使用Struts2的s标签来提取国际化信息;

这样的应用方式其实有不得以的苦衷,项目刚成立的时候,组内全是新人,对Struts2仅有名义上的理解,没有人在项目中实际运用中使用Struts2,另外项目进度压的很紧,没时间给大家去了解和学习;而使用ajax来实现页面与Web服务之间通信,实现和使用都简单,于是一直沿用至今。

Struts2的配置

Struts2在web.xml中的配置如下:

    struts2    org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter    struts2    /*

简化后的struts.xml配置文件,内容如下:

                                                                                                                                  /error.jsp                                                        {1}.jsp            

通用Action类,简化后的MainAction代码如下

import com.opensymphony.xwork2.ActionSupport;public class MainAction extends ActionSupport {private static final long serialVersionUID = 928135783255954591L;@Overridepublic String execute() throws Exception {return ActionSupport.SUCCESS;}}

从配置上讲,中规中矩,并没有什么特别的地方。

分析过程访问jsp页面的流程

按照前述配置,当用户在浏览器地址栏里输入后缀为.jsp的url,敲击回车后,发生的事件如下:

  1. 由于在struts.xml文件中有如下配置,用户请求先被Struts2拦截;

     
  2. 由于在struts.xml中有如下配置,请求会被转发给MainAction来处理;

          {1}.jsp 
  3. 由于MainAction类的execute方法只返回了"success",于是请求又被重定向回了对应的jsp页面;

     @Override public String execute() throws Exception { return ActionSupport.SUCCESS; }
  4. 这时,对jsp页面的访问就由Tomcat接管了;

与9.0.x版本的Tomcat做对比

按照上面的分析,在第4步,如果Tomcat遇到了异常的请求方法,应该拒绝响应才是,不应当返回HTTP 200和页面。那么会不会是Tomcat实现存在问题?

平台发布加固后的Tomcat版本时并没有附带源码,于是从官网获取Tomcat发布的9.0.x版本做对比验证。执行前述的复现步骤,Tomcat 9.0.x版本给浏览器返回了如下响应:

HTTP Status 405 - JSPs only permit GET POST or HEADtype Status reportmessage JSPs only permit GET POST or HEADdescription The specified HTTP method is not allowed for the requested resource.

检查9.0.x版本Tomcat生成的servlet代码,发现在_jspService中增加了对HTTP方法有效性的校验,只允许jsp编译生成的servlet响应GET、POST或HEAD方法,如下。

  public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)      throws java.io.IOException, javax.servlet.ServletException {    final java.lang.String _jspx_method = request.getMethod();    if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method) && !javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {      response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSPs only permit GET POST or HEAD");      return;    }

生成上述代码的实现位于org.apache.jasper.compiler.Generator类中,如下所示

// Method checkif (!pageInfo.isErrorPage()) {    out.printil("final java.lang.String _jspx_method = request.getMethod();");    out.printin("if (!\"GET\".equals(_jspx_method) && !\"POST\".equals(_jspx_method) && !\"HEAD\".equals(_jspx_method) && ");    out.println("!javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {");    out.pushIndent();    out.printin("response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ");    out.println("\"" + Localizer.getMessage("jsp.error.servlet.invalid.method") + "\");");    out.printil("return;");    out.popIndent();    out.printil("}");    out.println();}

分析结论

这说明高版本的Tomcat已经意识到前述问题,并做了修复。

但此时还有一事不明,当请求HTTP请求中的方法不正确时,7.0.x版本的Tomcat依然可以正常返回页面信息。由于手上没有平台加固过的Tomcat的源码,这事情只好不了了之。

解决之道

显而易见,最直接的解决方法是更换Tomcat的版本,直接使用官网上发布的9.0.x版本。但官网发布的Tomcat没有经过安全加固,在项目中使用时需要自行做加固,难度不低,工作量不小。

所以不能换版本,得另想办法。

当前的诉求很简单,只允许Web服务器响应GET和POST请求,如果用户发起了其它请求,则清理会话、并跳转至错误页面。聪明的朋友一定想到了,使用J2EE标准中提供的Filter即可满足。

参考资料关于HTTP方法

  • HTTP方法详解
  • HTTP六种请求方法
  • HTTP请求方法对照表
  • HTTP协议的几种请求方法
  • HTTP请求的方法

其它

  • burpsuite,强大的渗透测试神器
  • switchysharp,Google Chrome浏览器上目前最好用的代理程序
  • Struts2,Struts2官网
  • Filtering Requests and Responses,关于Filter的介绍材料
  • tomcat on GitHub,托管在GitHub上的官方镜像仓库
  • tomcat 7.0.x SVN,7.0.x版本的官方SVN仓库

本文来自博客园,作者:jackieathome,转载请注明原文链接:https://www.cnblogs.com/jackieathome/p/17936492.html