(window.webpackJsonp=window.webpackJsonp||[]).push([[617],{1048:function(e,t,a){"use strict";a.r(t);var r=a(56),l=Object(r.a)({},(function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[a("h1",{attrs:{id:"httpfirewall"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#httpfirewall"}},[e._v("#")]),e._v(" HttpFirewall")]),e._v(" "),a("p",[e._v("在针对你定义的模式进行测试时,了解该机制是什么以及使用了什么URL值是很重要的。")]),e._v(" "),a("p",[e._v("Servlet 规范为"),a("code",[e._v("HttpServletRequest")]),e._v("定义了几个属性,这些属性可以通过getter方法访问,并且我们可能希望对其进行匹配。它们是"),a("code",[e._v("contextPath")]),e._v(","),a("code",[e._v("servletPath")]),e._v(","),a("code",[e._v("pathInfo")]),e._v("和"),a("code",[e._v("queryString")]),e._v("。 Spring 安全性只对保护应用程序内的路径感兴趣,因此"),a("code",[e._v("contextPath")]),e._v("被忽略。不幸的是, Servlet 规范并没有确切地定义"),a("code",[e._v("servletPath")]),e._v("和"),a("code",[e._v("pathInfo")]),e._v("对于特定的请求URI的值将包含什么。例如,一个URL的每个路径段可以包含参数,如"),a("a",{attrs:{href:"https://www.ietf.org/rfc/rfc2396.txt",target:"_blank",rel:"noopener noreferrer"}},[e._v("RFC 2396"),a("OutboundLink")],1),a("sup",{staticClass:"footnote"},[e._v("["),a("a",{staticClass:"footnote",attrs:{id:"_footnoteref_1",href:"#_footnotedef_1",title:"View footnote."}},[e._v("1")]),e._v("]")]),e._v("。规范没有明确说明是否应该将这些值包含在"),a("code",[e._v("servletPath")]),e._v("和"),a("code",[e._v("pathInfo")]),e._v("值中,并且不同的容器之间的行为是不同的。当应用程序部署在不从这些值中删除路径参数的容器中时,存在这样的危险:攻击者可能会将它们添加到所请求的URL中,从而导致模式匹配意外成功或失败。"),a("sup",{staticClass:"footnote"},[e._v("["),a("a",{staticClass:"footnote",attrs:{id:"_footnoteref_2",href:"#_footnotedef_2",title:"View footnote."}},[e._v("2")]),e._v("]")]),e._v("。传入URL中的其他变体也是可能的。例如,它可能包含路径遍历序列(如"),a("code",[e._v("/../")]),e._v(")或多个前向斜杠("),a("code",[e._v("//")]),e._v("),这也可能导致模式匹配失败。一些容器在执行 Servlet 映射之前将这些规范化,但其他容器则不这样做。为了防止此类问题,"),a("code",[e._v("FilterChainProxy")]),e._v("使用"),a("code",[e._v("HttpFirewall")]),e._v("策略来检查和包装请求。默认情况下,未规范化的请求会被自动拒绝,并且为了匹配的目的,会删除路径参数和重复的斜杠。"),a("sup",{staticClass:"footnote"},[e._v("["),a("a",{staticClass:"footnote",attrs:{id:"_footnoteref_3",href:"#_footnotedef_3",title:"View footnote."}},[e._v("3")]),e._v("]")]),e._v("。因此,使用"),a("code",[e._v("FilterChainProxy")]),e._v("来管理安全筛选链是非常重要的。请注意,"),a("code",[e._v("servletPath")]),e._v("和"),a("code",[e._v("pathInfo")]),e._v("值是由容器解码的,因此你的应用程序不应该有任何包含半冒号的有效路径,因为为了匹配的目的,这些部分将被删除。")]),e._v(" "),a("p",[e._v("如上所述,默认策略是使用 Ant 样式的路径进行匹配,这可能是大多数用户的最佳选择。该策略在类"),a("code",[e._v("AntPathRequestMatcher")]),e._v("中实现,该类使用 Spring 的"),a("code",[e._v("AntPathMatcher")]),e._v("对串联的"),a("code",[e._v("servletPath")]),e._v("和"),a("code",[e._v("pathInfo")]),e._v("执行不区分大小写的模式匹配,忽略"),a("code",[e._v("queryString")]),e._v("。")]),e._v(" "),a("p",[e._v("如果出于某种原因,你需要一个更强大的匹配策略,那么可以使用正则表达式。那么策略执行"),a("code",[e._v("RegexRequestMatcher")]),e._v("。有关此类的更多信息,请参见Javadoc。")]),e._v(" "),a("p",[e._v("在实践中,我们建议你在服务层使用方法安全性来控制对应用程序的访问,而不是完全依赖于在Web-Application级别定义的安全约束的使用。URL会发生变化,很难考虑到应用程序可能支持的所有可能的URL,以及如何操纵请求。你应该试着把自己限制在几条简单易懂的路径上。始终尝试使用“默认拒绝”方法,其中你有一个包罗万象的通配符(/"),a("strong",[e._v("或")]),e._v(")在最后定义并拒绝访问。")]),e._v(" "),a("p",[e._v("在服务层定义的安全性要强大得多,也更难绕过,因此你应该始终利用 Spring Security的方法安全性选项。")]),e._v(" "),a("p",[a("code",[e._v("HttpFirewall")]),e._v("还通过拒绝HTTP响应头中的新行字符来防止"),a("a",{attrs:{href:"https://www.owasp.org/index.php/HTTP_Response_Splitting",target:"_blank",rel:"noopener noreferrer"}},[e._v("HTTP响应拆分"),a("OutboundLink")],1),e._v("。")]),e._v(" "),a("p",[e._v("默认情况下使用"),a("code",[e._v("StrictHttpFirewall")]),e._v("。此实现拒绝似乎是恶意的请求。如果对你的需求来说过于严格,那么你可以自定义拒绝的请求类型。然而,重要的是,你要知道这可能会使你的应用程序受到攻击。例如,如果你希望利用 Spring MVC的矩阵变量,可以使用以下配置:")]),e._v(" "),a("p",[e._v("例1.允许矩阵变量")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("@Bean\npublic StrictHttpFirewall httpFirewall() {\n StrictHttpFirewall firewall = new StrictHttpFirewall();\n firewall.setAllowSemicolon(true);\n return firewall;\n}\n")])])]),a("p",[e._v("XML")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('\n\n\n')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("@Bean\nfun httpFirewall(): StrictHttpFirewall {\n val firewall = StrictHttpFirewall()\n firewall.setAllowSemicolon(true)\n return firewall\n}\n")])])]),a("p",[a("code",[e._v("StrictHttpFirewall")]),e._v("提供了一个允许的有效HTTP方法列表,这些方法被允许对"),a("a",{attrs:{href:"https://owasp.org/www-community/attacks/Cross_Site_Tracing",target:"_blank",rel:"noopener noreferrer"}},[e._v("跨站点跟踪"),a("OutboundLink")],1),e._v("和"),a("a",{attrs:{href:"https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/06-Test_HTTP_Methods",target:"_blank",rel:"noopener noreferrer"}},[e._v("HTTP动词篡改"),a("OutboundLink")],1),e._v("进行保护。默认的有效方法是“delete”、“get”、“head”、“options”、“patch”、“post”和“put”。如果你的应用程序需要修改有效的方法,你可以配置一个自定义的"),a("code",[e._v("StrictHttpFirewall")]),e._v(" Bean。例如,以下将只允许HTTP“get”和“postmethod”方法:")]),e._v(" "),a("p",[e._v("例2.只允许get和post(P)")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@Bean\npublic StrictHttpFirewall httpFirewall() {\n StrictHttpFirewall firewall = new StrictHttpFirewall();\n firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));\n return firewall;\n}\n')])])]),a("p",[e._v("XML")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('\n\n\n')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@Bean\nfun httpFirewall(): StrictHttpFirewall {\n val firewall = StrictHttpFirewall()\n firewall.setAllowedHttpMethods(listOf("GET", "POST"))\n return firewall\n}\n')])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("如果你正在使用"),a("code",[e._v("new MockHttpServletRequest()")]),e._v(",则它当前将创建一个HTTP方法作为空字符串“。"),a("br"),e._v("这是一个无效的HTTP方法,并且将被 Spring Security拒绝。"),a("br"),e._v("你可以通过将其替换为"),a("code",[e._v('new MockHttpServletRequest("GET", "")')]),e._v("来解决此问题。"),a("br"),e._v("有关请求改进此问题的请参见"),a("a",{attrs:{href:"https://jira.spring.io/browse/SPR-16851",target:"_blank",rel:"noopener noreferrer"}},[e._v("SPR_16851"),a("OutboundLink")],1),e._v("。")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("如果必须允许任何HTTP方法(不推荐),则可以使用"),a("code",[e._v("StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)")]),e._v("。这将完全禁用HTTP方法的验证。")]),e._v(" "),a("p",[a("code",[e._v("StrictHttpFirewall")]),e._v("还检查头名称、值和参数名称。它要求每个字符都有一个定义的代码点,而不是一个控制字符。")]),e._v(" "),a("p",[e._v("可以使用以下方法根据需要放松或调整此要求:")]),e._v(" "),a("ul",[a("li",[a("p",[a("code",[e._v("StrictHttpFirewall#setAllowedHeaderNames(Predicate)")])])]),e._v(" "),a("li",[a("p",[a("code",[e._v("StrictHttpFirewall#setAllowedHeaderValues(Predicate)")])])]),e._v(" "),a("li",[a("p",[a("code",[e._v("StrictHttpFirewall#setAllowedParameterNames(Predicate)")])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("而且,参数值可以用"),a("code",[e._v("setAllowedParameterValues(Predicate)")]),e._v("来控制。")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("例如,要关闭此检查,你可以将你的"),a("code",[e._v("StrictHttpFirewall")]),e._v("与始终返回"),a("code",[e._v("Predicate")]),e._v("的"),a("code",[e._v("true")]),e._v("连接起来,如:")]),e._v(" "),a("p",[e._v("例3.允许任何头名称、头值和参数名称")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("@Bean\npublic StrictHttpFirewall httpFirewall() {\n StrictHttpFirewall firewall = new StrictHttpFirewall();\n firewall.setAllowedHeaderNames((header) -> true);\n firewall.setAllowedHeaderValues((header) -> true);\n firewall.setAllowedParameterNames((parameter) -> true);\n return firewall;\n}\n")])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("@Bean\nfun httpFirewall(): StrictHttpFirewall {\n val firewall = StrictHttpFirewall()\n firewall.setAllowedHeaderNames { true }\n firewall.setAllowedHeaderValues { true }\n firewall.setAllowedParameterNames { true }\n return firewall\n}\n")])])]),a("p",[e._v("或者,你可能需要允许一个特定的值。")]),e._v(" "),a("p",[e._v("例如,iPhone X使用"),a("code",[e._v("User-Agent")]),e._v(",其中包含一个不在ISO-8859-1字符集中的字符。由于这一事实,一些应用程序服务器将把这个值解析为两个单独的字符,后者是一个未定义的字符。")]),e._v(" "),a("p",[e._v("你可以使用"),a("code",[e._v("setAllowedHeaderValues")]),e._v("方法来解决这个问题,如下所示:")]),e._v(" "),a("p",[e._v("例4.允许某些用户代理")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@Bean\npublic StrictHttpFirewall httpFirewall() {\n StrictHttpFirewall firewall = new StrictHttpFirewall();\n Pattern allowed = Pattern.compile("[\\\\p{IsAssigned}&&[^\\\\p{IsControl}]]*");\n Pattern userAgent = ...;\n firewall.setAllowedHeaderValues((header) -> allowed.matcher(header).matches() || userAgent.matcher(header).matches());\n return firewall;\n}\n')])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@Bean\nfun httpFirewall(): StrictHttpFirewall {\n val firewall = StrictHttpFirewall()\n val allowed = Pattern.compile("[\\\\p{IsAssigned}&&[^\\\\p{IsControl}]]*")\n val userAgent = Pattern.compile(...)\n firewall.setAllowedHeaderValues { allowed.matcher(it).matches() || userAgent.matcher(it).matches() }\n return firewall\n}\n')])])]),a("p",[e._v("对于标头值,你可以考虑在验证时将它们解析为UTF-8,如下所示:")]),e._v(" "),a("p",[e._v("例5.将标题解析为UTF-8")]),e._v(" "),a("p",[e._v("Java")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("firewall.setAllowedHeaderValues((header) -> {\n String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);\n return allowed.matcher(parsed).matches();\n});\n")])])]),a("p",[e._v("Kotlin")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("firewall.setAllowedHeaderValues {\n val parsed = String(header.getBytes(ISO_8859_1), UTF_8)\n return allowed.matcher(parsed).matches()\n}\n")])])]),a("hr"),e._v(" "),a("p",[a("a",{attrs:{href:"#_footnoteref_1"}},[e._v("1")]),e._v("。当浏览器不支持cookie并且"),a("code",[e._v("jsessionid")]),e._v("参数在分号之后的URL中附加时,你可能已经看到了这种情况。然而,RFC允许在URL的任何路径段中存在这些参数。")]),e._v(" "),a("p",[a("a",{attrs:{href:"#_footnoteref_2"}},[e._v("2")]),e._v("。一旦请求离开"),a("code",[e._v("FilterChainProxy")]),e._v(",将返回原始值,因此应用程序仍然可用。")]),e._v(" "),a("p",[a("a",{attrs:{href:"#_footnoteref_3"}},[e._v("3")]),e._v("。因此,例如,原始请求路径"),a("code",[e._v("/secure;hack=1/somefile.html;hack=2")]),e._v("将返回为"),a("code",[e._v("/secure/somefile.html")]),e._v("。")]),e._v(" "),a("p",[a("RouterLink",{attrs:{to:"/spring-security/http.html"}},[e._v("HTTP")]),a("RouterLink",{attrs:{to:"/integrations/index.html"}},[e._v("整合")])],1)])}),[],!1,null,null,null);t.default=l.exports}}]);