本文于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方法,应该是没有问题才对。

<security-constraint>
<web-resource-collection>
<http-method>HEAD</http-method>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>OPTIONS</http-method>
<http-method>TRACE</http-method>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name></role-name>
</auth-constraint>
</security-constraint>

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

项目的技术特点

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

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

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

Struts2的配置

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

<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter> <filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

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

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd"> <struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.devMode" value="true" />
<constant name="struts.action.extension" value="jsp,action"/>
<constant name="struts.ui.theme" value="java"></constant>
<constant name="struts.objectFactory" value="spring" />
<constant name="struts.i18n.encoding" value="UTF-8" />
<package name="default" namespace="/" extends="struts-default"> <interceptors>
<interceptor-stack name="myStack">
<interceptor-ref name="basicStack"></interceptor-ref>
</interceptor-stack>
</interceptors> <default-interceptor-ref name="myStack" /> <global-results>
<result name="error">/error.jsp</result>
</global-results> <global-exception-mappings>
<exception-mapping exception="java.lang.Exception" result="error"/>
</global-exception-mappings> <action name="*" class="MainAction">
<result name="success">{1}.jsp</result>
</action>
</package>
</struts>

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

import com.opensymphony.xwork2.ActionSupport;

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

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

分析过程

访问jsp页面的流程

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

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

     <constant name="struts.action.extension" value="jsp,action"/>
  2. 由于在struts.xml中有如下配置,请求会被转发给MainAction来处理;

     <action name="*" class="MainAction">
    <result name="success">{1}.jsp</result>
    </action>
  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 HEAD
type Status report
message JSPs only permit GET POST or HEAD
description 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 check
if (!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动词篡改导致的认证旁路问题的方法的更多相关文章

  1. 网络安全-使用HTTP动词篡改的认证旁路

    这个东西去年的安全扫描都没有,今天就扫出来了,非常奇怪的一个东西.好吧,找资料找原因.结果可能应为搜索名词的原因,这个问题在群友的帮助下解决了. 在我理解中servlet只有post和get方法,然后 ...

  2. asp.net mvc 安全测试漏洞 " HTTP 动词篡改的认证旁路" 问题解决

    IBM Security Appscan漏洞筛查-HTTP 动词篡改的认证旁路漏洞,具体解决方案: 在Web.Config中system.webServer节点增加配置security: <se ...

  3. MVC4升级MVC5导致原项目出错的解决方法

    原文:MVC4升级MVC5导致原项目出错的解决方法 出现安全透明方法"WebMatrix.WebData.PreApplicationStartCode.Start()"尝试访问安 ...

  4. 将Python的Django框架与认证系统整合的方法

    将Python的Django框架与认证系统整合的方法 这篇文章主要介绍了将Python的Django框架与认证系统整合的方法,包括指定认证后台和编写认证后台等内容,需要的朋友可以参考下 将Django ...

  5. 安装sqlserver导致80端口被占用解决方法

    安装sqlserver导致80端口被占用解决方法 系统占用的端口一般都是微软官方的产品占用的.所以这个时候主要考虑到几个服务: SQL Server导致.其中很有可能是SQL Server Repor ...

  6. &#65279导致页面顶部空白一行解决方法 【】

    2016年11月7日10:57:10 模板文件生成html文件之后会在body开头处加入一个可见的控制符&#65279,导致页面头部会出现一个空白行.原因是页面的编码是UTF-8 + BOM. ...

  7. Ubuntu ./configure 半途终止 导致没有生成makefile文件 解决方法

    在安装thrift的时候,解压包进入目录,执行命令: ./configure 之后,发现某些包没有安装,导致configure到一半的时候退出,接着make发现没有makefile文件.估计是我系统安 ...

  8. apache不断占内存过大,导致虚拟机内存不足,处理方法。

    我用512M的vps,访问量不大,但内存占用很大,甚至宕机. 我用top,然后shitf+m发现,httpd占用内存极大.经过网上找资料设置后,用过一段时间终于没再出现内存问题了. 首先查找配置文件的 ...

  9. 移动端touch事件影响click事件以及在touchmove添加preventDefault导致页面无法滚动的解决方法

    这两天自己在写一个手机网页,用到了触屏滑动的特效,就是往右滑动的时候左侧隐藏的菜单从左边划出来. 做完之后在手机原生浏览器中运行正常,但在QQ和微信中打开,发现touchmove只会触发一次,而且to ...

  10. &#65279导致页面顶部空白一行解决方法

    模板文件生成html文件之后会在body开头处加入一个可见的控制符&#65279,导致页面头部会出现一个空白行.原因是页面的编码是UTF-8 + BOM. 这种编码方式一般会在windows操 ...

随机推荐

  1. 【LeetCode贪心#05】K 次取反后最大化的数组和(自定义sort、二重贪心)

    K次取反后最大化的数组和 力扣题目链接(opens new window) 给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这 ...

  2. 聊聊图数据库和图数据库的小知识 Vol.02

    2010 年前后,对于社交媒体网络研究的兴起带动了图计算的大规模应用. 2000 年前后热门的是 信息检索 和 分析 ,主要是 Google 的带动,以及 Amazon 的 e-commerce 所用 ...

  3. 为什么(++a)+(++a)=14

    目录 概述 验证 反编译大法 Java 测试 概述 今天有学妹问我,下面这个代码为啥结果是14 int a=5; printf("%d\n",(++a)+(++a)); 我一看,第 ...

  4. union和union all的区别?

    一. 显示结果不同 union会自动压缩多个结果集合中的重复结果,而union all则将所有的结果集全部显示出来 二.对重复结果的处理不同 union all是直接连接,取到的是所有值,记录可能有的 ...

  5. Java package(包) +import 关键字(1)

    Java 包(package) 为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间. 包的作用 1.把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用. 2.如同文件夹一样, ...

  6. AI大模型加速RPAxAI时代到来,谁会是RPA领域的杀手级应用?

    GPT等AI大模型震撼来袭,基于RPA的超级自动化仍是最佳落地载体 对话弘玑CPO贾岿,深入了解国产RPA厂商对AI大模型的探索与实践 文/王吉伟 关于RPA已死的说法,在中国RPA元年(2019年) ...

  7. spring源码手写aop

    AOP:       aop切面编程,其实就是spring增强器的一个扩展,就是通过beanPostProcessor的after后置方式实现的,其中在after中把需要的bean通过放射+动态代理完 ...

  8. PhpStorm设置FTP功能

    1.版本介绍 本文操作针对PhpStorm 2020.1版本 2.[ctrl + alt + s]打开设置,选择"Build,Execution,Deployment" 3.选择& ...

  9. react中css里面 class中的 图片的相对地址 完美解决 backgroundImage

    发现问题:缓存 之前react的图片,也在style里面,也无所谓. 刚做了一个输入框,change的时候改变图片,每次都刷新图片,关键是没缓存,这哪受得了 之前用的: 网上搜索各种插件,替换什么的, ...

  10. C# NAudio 播放多个MP3文件

    C# 使用 NAudio 来播放多个MP3文件.上代码 1.引入NAudio:using NAudio.Wave; 2.定义变量: private WaveOutEvent outputDevice; ...