Protect against RFD exploits

Issue: SPR-13548
This commit is contained in:
Rossen Stoyanchev
2015-10-04 21:25:41 -04:00
committed by Stephane Nicoll
parent 161fd98656
commit 2bd1daa75e
20 changed files with 465 additions and 109 deletions

View File

@@ -417,7 +417,7 @@ public abstract class AbstractSockJsService implements SockJsService, CorsConfig
response.setStatusCode(HttpStatus.NOT_FOUND);
return;
}
else if (!validateRequest(serverId, sessionId, transport)) {
else if (!validateRequest(serverId, sessionId, transport) || !validatePath(request)) {
if (requestInfo != null) {
logger.debug("Ignoring transport request: " + requestInfo);
}
@@ -452,6 +452,21 @@ public abstract class AbstractSockJsService implements SockJsService, CorsConfig
return true;
}
/**
* Ensure the path does not contain a file extension, either in the filename
* (e.g. "/jsonp.bat") or possibly after path parameters ("/jsonp;Setup.bat")
* which could be used for RFD exploits.
* <p>Since the last part of the path is expected to be a transport type, the
* presence of an extension would not work. All we need to do is check if
* there are any path parameters, which would have been removed from the
* SockJS path during request mapping, and if found reject the request.
*/
private boolean validatePath(ServerHttpRequest request) {
String path = request.getURI().getPath();
int index = path.lastIndexOf('/') + 1;
String filename = path.substring(index);
return filename.indexOf(';') == -1;
}
/**
* Handle request for raw WebSocket communication, i.e. without any SockJS message framing.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -18,10 +18,12 @@ package org.springframework.web.socket.sockjs.transport.handler;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.regex.Pattern;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
@@ -43,6 +45,12 @@ import org.springframework.web.util.UriUtils;
public abstract class AbstractHttpSendingTransportHandler extends AbstractTransportHandler
implements SockJsSessionFactory {
/**
* Pattern for validating jsonp callback parameter values.
*/
private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*");
@Override
public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, SockJsSession wsSession) throws SockJsException {
@@ -109,8 +117,12 @@ public abstract class AbstractHttpSendingTransportHandler extends AbstractTransp
String query = request.getURI().getQuery();
MultiValueMap<String, String> params = UriComponentsBuilder.newInstance().query(query).build().getQueryParams();
String value = params.getFirst("c");
if (StringUtils.isEmpty(value)) {
return null;
}
try {
return (!StringUtils.isEmpty(value) ? UriUtils.decode(value, "UTF-8") : null);
String result = UriUtils.decode(value, "UTF-8");
return (CALLBACK_PARAM_PATTERN.matcher(result).matches() ? result : null);
}
catch (UnsupportedEncodingException ex) {
// should never happen

View File

@@ -84,7 +84,7 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa
// We already validated the parameter above...
String callback = getCallbackParam(request);
return new DefaultSockJsFrameFormat(callback + "(\"%s\");\r\n") {
return new DefaultSockJsFrameFormat("/**/" + callback + "(\"%s\");\r\n") {
@Override
protected String preProcessContent(String content) {
return JavaScriptUtils.javaScriptEscape(content);