2
0
Эх сурвалжийг харах

SEC-2098, SEC-2099: Polishing

Rob Winch 12 жил өмнө
parent
commit
a63baa8391

+ 1 - 1
config/src/main/java/org/springframework/security/config/Elements.java

@@ -54,5 +54,5 @@ public abstract class Elements {
     public static final String LDAP_PASSWORD_COMPARE = "password-compare";
     public static final String DEBUG = "debug";
     public static final String HTTP_FIREWALL = "http-firewall";
-    public static final String ADD_HEADERS = "add-headers";
+    public static final String HEADERS = "headers";
 }

+ 6 - 4
config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java

@@ -60,7 +60,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
         BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HeadersFilter.class);
         final Map<String, String> headers = new HashMap<String, String>();
 
-        parseXssElement(element, headers);
+        parseXssElement(element, parserContext, headers);
         parseFrameOptionsElement(element, parserContext, headers);
         parseContentTypeOptionsElement(element, headers);
 
@@ -91,7 +91,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
             if (ALLOW_FROM.equals(header) ) {
                 String origin = frameElt.getAttribute(ATT_ORIGIN);
                 if (!StringUtils.hasText(origin) ) {
-                    parserContext.getReaderContext().error("Frame options header value ALLOW-FROM required an origin to be specified.", frameElt);
+                    parserContext.getReaderContext().error("<frame-options policy=\"ALLOW-FROM\"/> requires a non-empty string value for the origin attribute to be specified.", frameElt);
                 }
                 header += " " + origin;
             }
@@ -99,15 +99,17 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
         }
     }
 
-    private void parseXssElement(Element element, Map<String, String> headers) {
+    private void parseXssElement(Element element, ParserContext parserContext, Map<String, String> headers) {
         Element xssElt = DomUtils.getChildElementByTagName(element, XSS_ELEMENT);
         if (xssElt != null) {
             boolean enabled = Boolean.valueOf(getAttribute(xssElt, ATT_ENABLED, "true"));
-            boolean block = Boolean.valueOf(getAttribute(xssElt, ATT_BLOCK, "true"));
+            boolean block = Boolean.valueOf(getAttribute(xssElt, ATT_BLOCK, enabled ? "true" : "false"));
 
             String value = enabled ? "1" : "0";
             if (enabled && block) {
                 value += "; mode=block";
+            } else if (!enabled && block) {
+                parserContext.getReaderContext().error("<xss-protection enabled=\"false\"/> does not allow for the block=\"true\".", xssElt);
             }
             headers.put(XSS_PROTECTION_HEADER, value);
         }

+ 1 - 1
config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java

@@ -557,7 +557,7 @@ class HttpConfigurationBuilder {
     }
 
     private void createAddHeadersFilter() {
-        Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.ADD_HEADERS);
+        Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.HEADERS);
         if (elmt != null) {
             this.addHeadersFilter = new HeadersBeanDefinitionParser().parse(elmt, pc);
         }

+ 293 - 0
config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy

@@ -0,0 +1,293 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.springframework.security.config.http
+
+import javax.servlet.Filter
+import javax.servlet.http.HttpServletRequest
+
+import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
+import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
+import org.springframework.mock.web.MockFilterChain
+import org.springframework.mock.web.MockHttpServletRequest
+import org.springframework.mock.web.MockHttpServletResponse
+import org.springframework.security.config.BeanIds
+import org.springframework.security.openid.OpenIDAuthenticationFilter
+import org.springframework.security.openid.OpenIDAuthenticationToken
+import org.springframework.security.openid.OpenIDConsumer
+import org.springframework.security.openid.OpenIDConsumerException
+import org.springframework.security.web.access.ExceptionTranslationFilter
+import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices
+import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
+import org.springframework.security.web.headers.HeadersFilter
+
+/**
+ *
+ * @author Rob Winch
+ */
+class HttpHeadersConfigTests extends AbstractHttpConfigTests {
+
+    def 'no http headers filter'() {
+        httpAutoConfig {
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        expect:
+        !hf
+    }
+
+    def 'http headers with empty headers'() {
+        httpAutoConfig {
+            'headers'()
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        expect:
+        hf
+        hf.headers.isEmpty()
+    }
+
+    def 'http headers content-type-options'() {
+        httpAutoConfig {
+            'headers'() {
+                'content-type-options'()
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        expect:
+        hf
+        hf.headers == ['X-Content-Type-Options':'nosniff']
+    }
+
+    def 'http headers frame-options defaults to DENY'() {
+        httpAutoConfig {
+            'headers'() {
+                'frame-options'()
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        expect:
+        hf
+        hf.headers == ['X-Frame-Options':'DENY']
+    }
+
+    def 'http headers frame-options DENY'() {
+        httpAutoConfig {
+            'headers'() {
+                'frame-options'(policy : 'DENY')
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        expect:
+        hf
+        hf.headers == ['X-Frame-Options':'DENY']
+    }
+
+    def 'http headers frame-options SAMEORIGIN'() {
+        httpAutoConfig {
+            'headers'() {
+                'frame-options'(policy : 'SAMEORIGIN')
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        expect:
+        hf
+        hf.headers == ['X-Frame-Options':'SAMEORIGIN']
+    }
+
+    def 'http headers frame-options ALLOW-FROM no origin reports error'() {
+        when:
+        httpAutoConfig {
+            'headers'() {
+                'frame-options'(policy : 'ALLOW-FROM')
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        then:
+        BeanDefinitionParsingException e = thrown()
+        e.message.contains '<frame-options policy="ALLOW-FROM"/> requires a non-empty string value for the origin attribute to be specified.'
+    }
+
+    def 'http headers frame-options ALLOW-FROM spaces only origin reports error'() {
+        when:
+        httpAutoConfig {
+            'headers'() {
+                'frame-options'(policy : 'ALLOW-FROM', origin : ' ')
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        then:
+        BeanDefinitionParsingException e = thrown()
+        e.message.contains '<frame-options policy="ALLOW-FROM"/> requires a non-empty string value for the origin attribute to be specified.'
+    }
+
+    def 'http headers frame-options ALLOW-FROM'() {
+        when:
+        httpAutoConfig {
+            'headers'() {
+                'frame-options'(policy : 'ALLOW-FROM', origin : 'https://example.com')
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        then:
+        hf
+        hf.headers == ['X-Frame-Options':'ALLOW-FROM https://example.com']
+    }
+
+    def 'http headers header a=b'() {
+        when:
+        httpAutoConfig {
+            'headers'() {
+                'header'(name : 'a', value: 'b')
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        then:
+        hf
+        hf.headers == ['a':'b']
+    }
+
+    def 'http headers header a=b and c=d'() {
+        when:
+        httpAutoConfig {
+            'headers'() {
+                'header'(name : 'a', value: 'b')
+                'header'(name : 'c', value: 'd')
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        then:
+        hf
+        hf.headers.sort() == ['a':'b', 'c':'d'].sort()
+    }
+
+    def 'http headers header no name produces error'() {
+        when:
+        httpAutoConfig {
+            'headers'() {
+                'header'(value: 'b')
+            }
+        }
+        createAppContext()
+
+        then:
+        thrown(XmlBeanDefinitionStoreException)
+    }
+
+    def 'http headers header no value produces error'() {
+        when:
+        httpAutoConfig {
+            'headers'() {
+                'header'(name: 'a')
+            }
+        }
+        createAppContext()
+
+        then:
+        thrown(XmlBeanDefinitionStoreException)
+    }
+
+    def 'http headers xss-protection defaults'() {
+        when:
+        httpAutoConfig {
+            'headers'() {
+                'xss-protection'()
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        then:
+        hf
+        hf.headers == ['X-XSS-Protection':'1; mode=block']
+    }
+
+    def 'http headers xss-protection enabled=true'() {
+        when:
+        httpAutoConfig {
+            'headers'() {
+                'xss-protection'(enabled:'true')
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        then:
+        hf
+        hf.headers == ['X-XSS-Protection':'1; mode=block']
+    }
+
+    def 'http headers xss-protection enabled=false'() {
+        when:
+        httpAutoConfig {
+            'headers'() {
+                'xss-protection'(enabled:'false')
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        then:
+        hf
+        hf.headers == ['X-XSS-Protection':'0']
+    }
+
+    def 'http headers xss-protection enabled=false and block=true produces error'() {
+        when:
+        httpAutoConfig {
+            'headers'() {
+                'xss-protection'(enabled:'false', block:'true')
+            }
+        }
+        createAppContext()
+
+        def hf = getFilter(HeadersFilter)
+
+        then:
+        BeanDefinitionParsingException e = thrown()
+        e.message.contains '<xss-protection enabled="false"/> does not allow for the block="true".'
+    }
+}

+ 13 - 8
docs/manual/src/docbook/appendix-namespace.xml

@@ -210,11 +210,11 @@
                 <title>Child Elements of &lt;http&gt;</title>
                 <itemizedlist>
                     <listitem><link xlink:href="#nsa-access-denied-handler">access-denied-handler</link></listitem>
-                    <listitem><link xlink:href="#nsa-headers">headers</link></listitem>
                     <listitem><link xlink:href="#nsa-anonymous">anonymous</link></listitem>
                     <listitem><link xlink:href="#nsa-custom-filter">custom-filter</link></listitem>
                     <listitem><link xlink:href="#nsa-expression-handler">expression-handler</link></listitem>
                     <listitem><link xlink:href="#nsa-form-login">form-login</link></listitem>
+                    <listitem><link xlink:href="#nsa-headers">headers</link></listitem>
                     <listitem><link xlink:href="#nsa-http-basic">http-basic</link></listitem>
                     <listitem><link xlink:href="#nsa-intercept-url">intercept-url</link></listitem>
                     <listitem><link xlink:href="#nsa-jee">jee</link></listitem>
@@ -225,7 +225,6 @@
                     <listitem><link xlink:href="#nsa-request-cache">request-cache</link></listitem>
                     <listitem><link xlink:href="#nsa-session-management">session-management</link></listitem>
                     <listitem><link xlink:href="#nsa-x509">x509</link></listitem>
-                    <listitem><link xlink:href="#nsa-headers">headers</link></listitem>
                 </itemizedlist>
             </section>
         </section>
@@ -307,7 +306,7 @@
                     <para>
                         <itemizedlist>
                             <listitem><literal>DENY</literal> The page cannot be displayed in a frame, regardless of
-                                the site attempting to do so. </listitem>
+                                the site attempting to do so. This is the default when frame-options-policy is specified.</listitem>
                             <listitem><literal>SAMEORIGIN</literal> The page can only be displayed in a frame on the
                                 same origin as the page itself</listitem>
                             <listitem><literal>ALLOW-FROM <link xlink:href="#nsa-frame-options-origin">origin</link></literal>
@@ -334,15 +333,20 @@
         </section>
         <section xml:id="nsa-xss-protection">
             <title><literal>&lt;xss-protection&gt;</literal></title>
-            <para>Adds the X-XSS-Protection header to the response. This is in no-way a full protection to XSS attacks!</para>
+            <para>Adds the <a href="http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx">X-XSS-Protection header</a>
+                to the response to assist in protecting against <a href="http://en.wikipedia.org/wiki/Cross-site_scripting#Non-Persistent">reflected / “Type-1” Cross-Site Scripting (XSS)</a>
+                attacks. This is in no-way a full protection to XSS attacks!</para>
             <section xml:id="nsa-xss-protection-attributes">
                 <section xml:id="nsa-xss-protection-enabled">
                     <title><literal>xss-protection-enabled</literal></title>
-                    <para>Enable or Disable xss-protection.</para>
+                    <para>Enable or Disable <a href="http://en.wikipedia.org/wiki/Cross-site_scripting#Non-Persistent">reflected / “Type-1” Cross-Site Scripting (XSS)</a> protection.</para>
                 </section>
                 <section xml:id="nsa-xss-protection-block">
                     <title><literal>xss-protection-block</literal></title>
-                    <para>When enabled adds mode=block to the header. Which indicates to the browser that loading should be blocked.</para>
+                    <para>When true and xss-protection-enabled is true, adds mode=block to the header. This indicates to the browser that the
+                        page should not be loaded at all. When false and xss-protection-enabled is true, the page will still be rendered when
+                        an reflected attack is detected but the response will be modified to protect against the attack. Note that there are
+                        sometimes ways of bypassing this mode which can often times make blocking the page more desirable.</para>
                 </section>
             </section>
             <section xml:id="nsa-xss-protection-parents">
@@ -354,8 +358,9 @@
         </section>
         <section xml:id="nsa-content-type-options">
             <title><literal>&lt;content-type-options&gt;</literal></title>
-            <para>Add the X-Content-Type-Options header to the response. Indicates the browser (IE8+) to enable detection
-            for MIME-sniffing.</para>
+            <para>Add the X-Content-Type-Options header with the value of nosniff to the response. This
+                <a href="http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx">disables MIME-sniffing</a>
+                for IE8+ and Chrome extensions.</para>
             <section xml:id="nsa-content-type-options-parents">
                 <title>Parent Elements of <literal>&lt;content-type-options&gt;</literal></title>
                 <itemizedlist>