Skip to content

Commit f0464e8

Browse files
committed
Add section on RFD + whitelist yml/properties/csv
Issue: SPR-13643
1 parent b46a301 commit f0464e8

File tree

4 files changed

+100
-45
lines changed

4 files changed

+100
-45
lines changed

spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,15 @@ public void setFavorPathExtension(boolean favorPathExtension) {
118118
}
119119

120120
/**
121-
* Add mappings from keys, extracted from a path extension or a query
121+
* Add a mapping from a key, extracted from a path extension or a query
122122
* parameter, to a MediaType. This is required in order for the parameter
123-
* strategy to work. The path extension strategy will also try
124-
* {@link ServletContext#getMimeType} and JAF if it is present and is not
125-
* suppressed via {@link #setUseJaf}.
123+
* strategy to work. Any extensions explicitly registered here are also
124+
* whitelisted for the purpose of Reflected File Download attack detection
125+
* (see Spring Framework reference documentation for more details on RFD
126+
* attack protection).
127+
* <p>The path extension strategy will also try to use
128+
* {@link ServletContext#getMimeType} and JAF (if present) to resolve path
129+
* extensions. To change this behavior see the {@link #useJaf} property.
126130
* @param mediaTypes media type mappings
127131
* @see #addMediaType(String, MediaType)
128132
* @see #addMediaTypes(Map)

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,13 @@ public ContentNegotiationConfigurer favorPathExtension(boolean favorPathExtensio
109109
/**
110110
* Add a mapping from a key, extracted from a path extension or a query
111111
* parameter, to a MediaType. This is required in order for the parameter
112-
* strategy to work. The path extension strategy will also try
113-
* {@link ServletContext#getMimeType} and JAF if it is present and is not
114-
* suppressed via {@link #useJaf}.
112+
* strategy to work. Any extensions explicitly registered here are also
113+
* whitelisted for the purpose of Reflected File Download attack detection
114+
* (see Spring Framework reference documentation for more details on RFD
115+
* attack protection).
116+
* <p>The path extension strategy will also try to use
117+
* {@link ServletContext#getMimeType} and JAF (if present) to resolve path
118+
* extensions. To change this behavior see the {@link #useJaf} property.
115119
* @param extension the key to look up
116120
* @param mediaType the media type
117121
* @see #mediaTypes(Map)

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe
7373

7474
/* Extensions associated with the built-in message converters */
7575
private static final Set<String> WHITELISTED_EXTENSIONS = new HashSet<String>(Arrays.asList(
76-
"txt", "text", "json", "xml", "atom", "rss", "png", "jpe", "jpeg", "jpg", "gif", "wbmp", "bmp"));
76+
"txt", "text", "yml", "properties", "csv",
77+
"json", "xml", "atom", "rss",
78+
"png", "jpe", "jpeg", "jpg", "gif", "wbmp", "bmp"));
7779

7880

7981
private final ContentNegotiationManager contentNegotiationManager;

src/asciidoc/web-mvc.adoc

Lines changed: 82 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -846,21 +846,75 @@ configuration. For more information on placeholders, see the javadocs of the
846846

847847

848848
[[mvc-ann-requestmapping-suffix-pattern-match]]
849-
==== Path Pattern Matching By Suffix
850-
By default Spring MVC automatically performs `".{asterisk}"` suffix pattern matching so
851-
that a controller mapped to `/person` is also implicitly mapped to `/person.{asterisk}`.
852-
This allows indicating content types via file extensions, e.g. `/person.pdf`,
853-
`/person.xml`, etc. A common pitfall however is when the last path segment of the
854-
mapping is a URI variable, e.g. `/person/{id}`. While a request for `/person/1.json`
855-
would correctly result in path variable id=1 and extension ".json", when the id
856-
naturally contains a dot, e.g. `/person/[email protected]` the result does not match
857-
expectations. Clearly here ".com" is not a file extension.
849+
==== Suffix Pattern Matching
850+
By default Spring MVC performs `".{asterisk}"` suffix pattern matching so that a
851+
controller mapped to `/person` is also implicitly mapped to `/person.{asterisk}`.
852+
This makes it easy to request different representations of a resource through the
853+
URL path (e.g. `/person.pdf`, `/person.xml`).
854+
855+
Suffix pattern matching can be turned off or restricted to a set of path extensions
856+
explicitly registered for content negotiation purposes. This is generally
857+
recommended to minimize ambiguity with common request mappings such as
858+
`/person/{id}` where a dot might not represent a file extension, e.g.
859+
`/person/[email protected]` vs `/person/[email protected]`. Furthermore as explained
860+
in the note below suffix pattern matching as well as content negotiation may be
861+
used in some circumstances to attempt malicious attacks and there are good
862+
reasons to restrict them meaningfully.
863+
864+
See <<mvc-config-path-matching>> for suffix pattern matching configuration and
865+
also <<mvc-config-content-negotiation>> for content negotiation configuration.
866+
867+
868+
869+
[[mvc-ann-requestmapping-rfd]]
870+
==== Suffix Suffix Pattern Matching and RFD
871+
872+
Reflected file download (RFD) attack was first described in a
873+
https://www.trustwave.com/Resources/SpiderLabs-Blog/Reflected-File-Download---A-New-Web-Attack-Vector/[paper by Trustwave]
874+
in 2014. The attack is similar to XSS in that it relies on input
875+
(e.g. query parameter, URI variable) being reflected in the response.
876+
However instead of inserting JavaScript into HTML, an RFD attack relies on the
877+
browser switching to perform a download and treating the response as an executable
878+
script if double-clicked based on the file extension (e.g. .bat, .cmd).
879+
880+
In Spring MVC `@ResponseBody` and `ResponseEntity` methods are at risk because
881+
they can render different content types which clients can request including
882+
via URL path extensions. Note however that neither disabling suffix pattern matching
883+
nor disabling the use of path extensions for content negotiation purposes alone
884+
are effective at preventing RFD attacks.
885+
886+
For comprehensive protection against RFD, prior to rendering the response body
887+
Spring MVC adds a `Content-Disposition:attachment;filename=f.txt` header to
888+
suggest a fixed and safe download file filename. This is done only if the URL
889+
path contains a file extension that is neither whitelisted nor explicitly
890+
registered for content negotiation purposes. However it may potentially have
891+
side effects when URLs are typed directly into a browser.
892+
893+
Many common path extensions are whitelisted by
894+
default. Furthermore REST API calls are typically not meant to be used as URLs
895+
directly in browsers. Nevertheless applications that use custom
896+
`HttpMessageConverter` implementations can explicitly register file extensions
897+
for content negotiation and the Content-Disposition header will not be added
898+
for such extensions. See <<mvc-config-content-negotiation>>.
899+
900+
[NOTE]
901+
====
902+
This was originally introduced as part of work for
903+
http://pivotal.io/security/cve-2015-5211[CVE-2015-5211].
904+
Below are additional recommendations from the report:
905+
906+
* Encode rather than escape JSON responses. This is also an OWASP XSS recommendation.
907+
For an example of how to do that with Spring see https://github.com/rwinch/spring-jackson-owasp[spring-jackson-owasp].
908+
* Configure suffix pattern matching to be turned off or restricted to explicitly
909+
registered suffixes only.
910+
* Configure content negotiation with the properties “useJaf” and “ignoreUknownPathExtension”
911+
set to false which would result in a 406 response for URLs with unknown extensions.
912+
Note however that this may not be an option if URLs are naturally expected to have
913+
a dot towards the end.
914+
* Add `X-Content-Type-Options: nosniff` header to responses. Spring Security 4 does
915+
this by default.
916+
====
858917

859-
The proper way to address this is to configure Spring MVC to only do suffix pattern
860-
matching against file extensions registered for content negotiation purposes.
861-
For more on this, first see <<mvc-config-content-negotiation>> and then
862-
<<mvc-config-path-matching>> showing how to enable suffix pattern matching
863-
along with how to use registered suffix patterns only.
864918

865919

866920

@@ -4837,26 +4891,19 @@ And in XML use the `<mvc:interceptors>` element:
48374891

48384892
[[mvc-config-content-negotiation]]
48394893
=== Content Negotiation
4840-
You can configure how Spring MVC determines the requested media types from the client
4841-
for request mapping as well as for content negotiation purposes. The available options
4842-
are to check the file extension in the request URI, the "Accept" header, a request
4843-
parameter, as well as to fall back on a default content type. By default, file extension
4844-
in the request URI is checked first and the "Accept" header is checked next.
4845-
4846-
For file extensions in the request URI, the MVC Java config and the MVC namespace,
4847-
automatically register extensions such as `.json`, `.xml`, `.rss`, and `.atom` if the
4848-
corresponding dependencies such as Jackson, JAXB2, or Rome are present on the classpath.
4849-
Additional extensions may be not need to be registered explicitly if they can be
4850-
discovered via `ServletContext.getMimeType(String)` or the __Java Activation Framework__
4851-
(see `javax.activation.MimetypesFileTypeMap`). You can register more extensions with the
4852-
{api-spring-framework}/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.html#setUseRegisteredSuffixPatternMatch(boolean)[setUseRegisteredSuffixPatternMatch
4853-
method].
4854-
4855-
The introduction of `ContentNegotiationManager` also enables selective suffix pattern
4856-
matching for incoming requests. For more details, see its javadocs.
4857-
4858-
Below is an example of customizing content negotiation options through the MVC Java
4859-
config:
4894+
You can configure how Spring MVC determines the requested media types from the request.
4895+
The available options are to check the URL path for a file extension, check the
4896+
"Accept" header, a specific query parameter, or to fall back on a default content
4897+
type when nothing is requested. By default the path extension in the request URI
4898+
is checked first and the "Accept" header is checked second.
4899+
4900+
The MVC Java config and the MVC namespace register `json`, `xml`, `rss`, `atom` by
4901+
default if corresponding dependencies are on the classpath. Additional
4902+
path extension-to-media type mappings may also be registered explicitly and that
4903+
also has the effect of whitelisting them as safe extensions for the purpose of RFD
4904+
attack detection (see <<mvc-ann-requestmapping-rfd>> for more detail).
4905+
4906+
Below is an example of customizing content negotiation options through the MVC Java config:
48604907

48614908
[source,java,indent=0]
48624909
[subs="verbatim,quotes"]
@@ -4867,7 +4914,7 @@ config:
48674914
48684915
@Override
48694916
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
4870-
configurer.favorPathExtension(false).favorParameter(true);
4917+
configurer.mediaType("json", MediaType.APPLICATION_JSON);
48714918
}
48724919
}
48734920
----
@@ -4882,8 +4929,6 @@ that in turn can be created with a `ContentNegotiationManagerFactoryBean`:
48824929
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
48834930
48844931
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
4885-
<property name="favorPathExtension" value="false"/>
4886-
<property name="favorParameter" value="true"/>
48874932
<property name="mediaTypes">
48884933
<value>
48894934
json=application/json

0 commit comments

Comments
 (0)