Skip to content
graemerocher edited this page Apr 30, 2013 · 27 revisions

GRAILS-9906

Challenge

A default codec of HTML for GSPs is useful to protect applications from Cross Site Scripting (XSS) attacks. The trouble is, the default codec applies to all GSPs, including those provided by plugins. Since it's the application developer that controls the settings, how does a plugin know whether to encode a value as HTML or not? What if a value should be encoded as Javascript rather than HTML?

You can currently override the default codec for a GSP through a defaultCodec page directive, but finer-grained control would be nice.

The problems can be expressed as the following:

  • Out of the box, apps and plugins should be immune to such XSS attacks, unless the developers explicitly take action to change the default behaviour.
  • There is a risk of double-encoding of data when the developer is not aware of encodings already applied.
  • Plugins cannot have their pages break because the app developer changes default codec setting. Currently they do.
  • Ideally the user should never need to explicitly think about codecs or calling them except in rare situations. Normally we have all the contextual information we need (i.e. inside g:javascript defaulting to HTML codec is pretty stupid)
  • Behaviour is inconsistent. <% ... %> and <%= ... %> do not apply default codec. ${g.xxx([:])} does apply codec. <g:xxx/> does not apply codec.

Suggestions

Solution 1

These items work together to create a coherent environment:

  1. Deprecate the existing global default codec in favour of smart context-sensitive default codecs and explicit page directive
  2. GSPs always default to HTML codec. The page defaultCodec directive can be used to alter this.
  3. Add a function/tag to switch the current default codec - effectively pushing and popping a default codec stack. This could take the form of a withCodec(name, Closure) method in tags.
  4. Use this function/tag in core tags like <g:javascript> and <r:script> to automatically set an appropriate codec
  5. Add some smarts to throw exception if same codec is applied multiple times (without specifying an override for this behaviour). i.e. attach applied codec names as a Set to the stream char buffers or similar
  6. <g:render> and similar tags would need to set default codec to HTML again when including another GSP, pushing whatever was default onto a stack
  7. Change <% ... %> and <%= ... %> to apply current default codec, as currently this is a little known security hole. Possible breaking change, but are people really using these with pre-escaped HTML right now?
  8. Add support for an optional encodeAs attribute to all tags automatically, such that the result will be encoded with that codec if specified i.e. var s = ${g.createLink(...., encodeAs:'JavaScript')}

The rationale for the above is the simplicity and removal of confusion:

  1. All GSPs in app or plugins default to HTML codec unless developer does something to change that using directive/tag
  2. All outputs of expressions/inline code apply the current default codec
  3. Tags are responsible for the correct encoding of their output, unless specified in encodeAs= attribute

Final Solution Proposed Documentation

Grails provides a number of ways to prevent Cross Site Scripting (XSS) attacks, depending on your level of paranoia. It is recommended that you review the configuration of a newly created Grails application to garner an understanding of XSS prevention works in Grails.

Warning: Note that Grails provides a compelling set of tools to help you prevent the occurrence of XSS attacks, but it is still possible for an application developer to create vulnerable applications, often without realising. It is ultimately the developer's responsibility to review an application for vulnerabilities.

Configuration

GSP features the ability to automatically HTML encode GSP expressions, and as of Grails 2.3 this is the default configuration. The default configuration (found in Config.groovy) for a newly created Grails application can be seen below:

grails {
    views {
        gsp {
            encoding = 'UTF-8'
            htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping
            codecs {
                expression = 'html' // escapes values inside ${}
                scriptlet = 'html' // escapes output from scriptlets in GSPs
                taglib = 'none' // escapes output from taglibs
                staticparts = 'none' // escapes output from static template parts
            }
        }
        // escapes all not-encoded output at final stage of outputting
        filteringCodecForContentType {
            //'text/html' = 'html'
        }
    }
}

GSP features several codecs that it uses when writing the page to the response. The codecs are configured in the codecs block and are described below:

  • expression - The expression codec is used to encode any code found within ${..} expressions. The default for newly created application is html encoding.
  • scriptlet - Used for output from GSP scriplets (<% %>, <%= %> blocks). The default for newly created applications is html encoding
  • taglib - Used to encode output from GSP tag libraries. The default is none for new applications, as typically it is the responsibility of the tag author to define the encoding of a given tag and by specifying none Grails remains backwards compatible with older tag libraries.
  • staticparts - Used to encode the raw markup output by a GSP page. The default is none.

Double Encoding Prevention

Versions of Grails prior to 2.3, included the ability to set the default codec to html, however enabling this setting sometimes proved problematic when using existing plugins due to encoding being applied twice (once by the html codec and then again if the plugin manually called encodeAsHTML).

Grails 2.3 includes double encoding prevention so that when an expression is evaluated, it will not encode if the data has already been encoded (Example ${foo.encodeAsHTML()}).

Raw Output

If you are 100% sure that the value you wish to present on the page has not been received from user input, and you do not wish the value to be encoded then you can use the raw method:

${raw(book.title)}

The 'raw' method is available in tag libraries, controllers and GSP pages.

Per Plugin Encoding

Grails also features the ability to control the codecs used on a per plugin basis. For example if you have a plugin named foo installed, then placing the following configuration in your application's Config.groovy will disabling encoding for only the foo plugin

foo.grails.views.gsp.codecs.expression = "none"

Per Page Encoding

You can also control the various codecs used to render a GSP page on a per page basis, using a page directive:

<%@ expressionCodec="none" %>

Per Tag Library Encoding

Each tag library created has the opportunity to specify a default codec used to encode output from the tag library using the defaultEncodeAs property:

static defaultEncodeAs = 'html'

Encoding can also be specified on a per tag basis using encodeAsForTags:

static encodeAsForTags = [tagName: 'raw']

Forced Encoding

The default configuration for new applications is fine for most use cases, and backwards compatible with existing plugins and tag libraries. However, you can also make your application even more secure by configuring Grails to always encode all output at the end of a response. This is done using the filteringCodecForContentType configuration:

// escapes all not-encoded output at final stage of outputting
filteringCodecForContentType {
   'text/html' = 'html'
}

Note that, if activated, the staticparts codec typically needs to be set to raw so that static markup is not encoded:

codecs {
    expression = 'html' // escapes values inside ${}
    scriptlet = 'html' // escapes output from scriptlets in GSPs
    taglib = 'none' // escapes output from taglibs
    staticparts = 'raw' // escapes output from static template parts
}
Clone this wiki locally