Skip to content

Commit b2a83cc

Browse files
committed
Bug #14784
Fix the vulnerability by setting a restrictive content security policy of the HTTP response when serving a file in Silverpeas. Now, when serving a file, it is done explicitly for download and not anymore for inlining its content.
1 parent 3462c67 commit b2a83cc

File tree

4 files changed

+36
-89
lines changed

4 files changed

+36
-89
lines changed

core-web/src/main/java/org/silverpeas/core/web/http/FileResponse.java

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import java.time.Instant;
4646
import java.util.Base64;
4747
import java.util.Date;
48-
import java.util.Optional;
4948
import java.util.regex.Matcher;
5049
import java.util.regex.Pattern;
5150

@@ -59,7 +58,6 @@
5958
*/
6059
public abstract class FileResponse {
6160

62-
public static final String DOWNLOAD_CONTEXT_PARAM = "downloadContext";
6361
private static final int MAX_PATH_LENGTH_IN_LOGS = 100;
6462
private static final int BUFFER_LENGTH = 1024 * 16;
6563
private static final Pattern RANGE_PATTERN = Pattern.compile("bytes=(?<start>\\d*)-(?<end>\\d*)");
@@ -82,6 +80,8 @@ public abstract class FileResponse {
8280
FileResponse(final HttpServletRequest request, final HttpServletResponse response) {
8381
this.request = request;
8482
this.response = response;
83+
response.setHeader("Content-Security-Policy",
84+
"default-src 'none'; img-src 'self'; style-src 'none'; script-src 'none'");
8585
}
8686

8787
/**
@@ -128,20 +128,6 @@ public static ServletFileResponse fromServlet(final HttpServletRequest request,
128128
return new ServletFileResponse(request, response);
129129
}
130130

131-
/**
132-
* Indicates a download context if any set into request.
133-
* @return true if download context, false otherwise.
134-
*/
135-
boolean isDownloadContext() {
136-
final String parameter = Optional.ofNullable(request.getParameter(DOWNLOAD_CONTEXT_PARAM))
137-
.filter(StringUtil::isDefined)
138-
.orElseGet(() -> {
139-
final Object attribute = request.getAttribute(DOWNLOAD_CONTEXT_PARAM);
140-
return attribute == null ? "false" : attribute.toString();
141-
});
142-
return StringUtil.getBooleanValue(parameter);
143-
}
144-
145131
/**
146132
* Gets mime type.
147133
* @param absoluteFilePath the absolute file path.
@@ -251,7 +237,7 @@ void partialOutputStream(final Path path, final ContentRangeData partialData,
251237
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_LENGTH);
252238
while ((bytesRead = input.read(buffer)) != -1 && bytesLeft > 0) {
253239
buffer.clear();
254-
output.write(buffer.array(), 0, bytesLeft < bytesRead ? bytesLeft : bytesRead);
240+
output.write(buffer.array(), 0, Math.min(bytesLeft, bytesRead));
255241
bytesLeft -= bytesRead;
256242
}
257243
SilverLogger.getLogger(this).debug("{0} - all part content bytes sent", StringUtil
@@ -298,7 +284,7 @@ String getFileIdentifier(final Path path) throws IOException {
298284
}
299285
final File file = path.toFile();
300286
final String sb =
301-
String.valueOf(FileUtils.checksumCRC32(file)) +
287+
FileUtils.checksumCRC32(file) +
302288
"|" + file.getName() +
303289
"|" + file.length() +
304290
"|" + file.lastModified();
@@ -343,12 +329,12 @@ ContentRangeData getContentRangeData(final Matcher partialHeaders, final int ful
343329
response.setBufferSize(BUFFER_LENGTH);
344330

345331
String startGroup = partialHeaders.group("start");
346-
int start = startGroup.isEmpty() ? 0 : Integer.valueOf(startGroup);
347-
start = start < 0 ? 0 : start;
332+
int start = startGroup.isEmpty() ? 0 : Integer.parseInt(startGroup);
333+
start = Math.max(start, 0);
348334

349335
String endGroup = partialHeaders.group("end");
350-
int end = endGroup.isEmpty() ? endOfFullContent : Integer.valueOf(endGroup);
351-
end = end > endOfFullContent ? endOfFullContent : end;
336+
int end = endGroup.isEmpty() ? endOfFullContent : Integer.parseInt(endGroup);
337+
end = Math.min(end, endOfFullContent);
352338

353339
int partContentLength = end - start + 1;
354340
return new ContentRangeData(start, end, partContentLength,

core-web/src/main/java/org/silverpeas/core/web/http/PreparedDownload.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public static Optional<PreparedDownload> getPreparedDownloadToPerform(
102102
* Gets from the given request the {@link PreparedDownload} instance registered into the
103103
* session if any.
104104
* @param request the request.
105-
* @throw IllegalArgumentException if parameter id is not defined into request or if it does
105+
* @throws IllegalArgumentException if parameter id is not defined into request or if it does
106106
* not exists {@link PreparedDownload} instance referenced by the identifier.
107107
* @return a {@link PreparedDownload} instance.
108108
*/
@@ -170,7 +170,7 @@ public void sendTo(final HttpServletRequest request, final HttpServletResponse r
170170
.forceCharacterEncoding(characterEncoding)
171171
.forceFileName(fileName)
172172
.noCache()
173-
.sendPath(Paths.get(file.toURI()), true);
173+
.sendPath(Paths.get(file.toURI()));
174174
} finally {
175175
deleteQuietly(file);
176176
}

core-web/src/main/java/org/silverpeas/core/web/http/RestFileResponse.java

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -55,53 +55,35 @@ public class RestFileResponse extends FileResponse {
5555
}
5656

5757
/**
58-
* Centralization of getting of silverpeas file content.
59-
* <p>
60-
* A download context flag is verified from parameters and attributes of the request.
61-
* </p>
58+
* Centralization of getting the silverpeas file content. By default, the file will be for
59+
* download and not for viewing its content directly in the client (as the file content can
60+
* contain corrupting code).
6261
* @param file the silverpeas file to send.
6362
* @return the response builder.
6463
*/
6564
public Response.ResponseBuilder silverpeasFile(final SilverpeasFile file) {
66-
return silverpeasFile(file, isDownloadContext());
67-
}
68-
69-
/**
70-
* Centralization of getting of silverpeas file content.
71-
* <p>
72-
* Using directly this method means that the request downloadContext flag is ignored.
73-
* </p>
74-
* @param file the silverpeas file to send.
75-
* @param downloadContext indicating a download context in order to specify rightly response
76-
* headers.
77-
* @return the response builder.
78-
*/
79-
public Response.ResponseBuilder silverpeasFile(final SilverpeasFile file,
80-
final boolean downloadContext) {
8165
if (isNotDefined(forcedMimeType)) {
8266
forceMimeType(file.getMimeType());
8367
}
84-
return path(Paths.get(file.toURI()), downloadContext);
68+
return path(Paths.get(file.toURI()));
8569
}
8670

8771
/**
8872
* Centralization of getting of a file content.
8973
* @param path the file to send.
90-
* @param downloadContext indicating a download context in order to specify rightly response
9174
* headers.
9275
* @return the response.
9376
*/
94-
private Response.ResponseBuilder path(final Path path, final boolean downloadContext) {
77+
private Response.ResponseBuilder path(final Path path) {
9578
try {
96-
final Path absoluteFilePath = path.toAbsolutePath();
97-
final String fileName = getFileName(absoluteFilePath);
98-
final String fileMimeType = getMimeType(absoluteFilePath);
99-
100-
final int fullContentLength = (int) Files.size(absoluteFilePath);
101-
final Matcher partialMatcher = getPartialMatcher();
102-
final boolean isPartialRequest = partialMatcher.matches();
103-
104-
final Response.ResponseBuilder responseBuilder;
79+
var absoluteFilePath = path.toAbsolutePath();
80+
var fileName = getFileName(absoluteFilePath);
81+
var fileMimeType = getMimeType(absoluteFilePath);
82+
var fullContentLength = (int) Files.size(absoluteFilePath);
83+
var partialMatcher = getPartialMatcher();
84+
var isPartialRequest = partialMatcher.matches();
85+
86+
Response.ResponseBuilder responseBuilder;
10587
if (isPartialRequest) {
10688
// Handling here a partial response (pseudo streaming)
10789
responseBuilder =
@@ -111,9 +93,7 @@ private Response.ResponseBuilder path(final Path path, final boolean downloadCon
11193
responseBuilder = getFullResponseBuilder(absoluteFilePath, fullContentLength);
11294
}
11395

114-
final String filename = downloadContext
115-
? encodeAttachmentFilenameAsUtf8(fileName)
116-
: encodeInlineFilenameAsUtf8(fileName);
96+
var filename = encodeAttachmentFilenameAsUtf8(fileName);
11797
return responseBuilder.type(fileMimeType).header("Content-Disposition", filename);
11898
} catch (final WebApplicationException ex) {
11999
throw ex;

core-web/src/main/java/org/silverpeas/core/web/http/ServletFileResponse.java

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -55,53 +55,34 @@ public class ServletFileResponse extends FileResponse {
5555
}
5656

5757
/**
58-
* Centralization of getting of silverpeas file content.
59-
* <p>
60-
* A download context flag is verified from parameters and attributes of the request.
61-
* </p>
58+
* Centralization of getting of silverpeas file content. By default, the file will be for
59+
* download and not for viewing its content directly in the client (as the file content can
60+
* contain corrupting code).
6261
* @param file the silverpeas file to send.
6362
*/
6463
public void sendSilverpeasFile(final SilverpeasFile file) {
65-
sendSilverpeasFile(file, isDownloadContext());
66-
}
67-
68-
/**
69-
* Centralization of getting of silverpeas file content.
70-
* <p>
71-
* Using directly this method means that the request downloadContext flag is ignored.
72-
* </p>
73-
* @param file the silverpeas file to send.
74-
* @param downloadContext indicating a download context in order to specify rightly response
75-
* headers.
76-
*/
77-
public void sendSilverpeasFile(final SilverpeasFile file,
78-
final boolean downloadContext) {
7964
if (isNotDefined(forcedMimeType)) {
8065
forceMimeType(file.getMimeType());
8166
}
82-
sendPath(Paths.get(file.toURI()), downloadContext);
67+
sendPath(Paths.get(file.toURI()));
8368
}
8469

8570
/**
8671
* Centralization of getting of a file content.
8772
* @param path the file to send.
88-
* @param downloadContext indicating a download context in order to specify rightly response
8973
* headers.
9074
*/
91-
void sendPath(final Path path, final boolean downloadContext) {
75+
void sendPath(final Path path) {
9276
try {
93-
final Path absoluteFilePath = path.toAbsolutePath();
94-
final String fileName = getFileName(absoluteFilePath);
95-
final String fileMimeType = getMimeType(absoluteFilePath);
96-
97-
final int fullContentLength = (int) Files.size(absoluteFilePath);
98-
final Matcher partialMatcher = getPartialMatcher();
99-
final boolean isPartialRequest = partialMatcher.matches();
77+
Path absoluteFilePath = path.toAbsolutePath();
78+
String fileName = getFileName(absoluteFilePath);
79+
String fileMimeType = getMimeType(absoluteFilePath);
80+
int fullContentLength = (int) Files.size(absoluteFilePath);
81+
Matcher partialMatcher = getPartialMatcher();
82+
boolean isPartialRequest = partialMatcher.matches();
10083

10184
response.setContentType(fileMimeType);
102-
final String filename = downloadContext
103-
? encodeAttachmentFilenameAsUtf8(fileName)
104-
: encodeInlineFilenameAsUtf8(fileName);
85+
final String filename = encodeAttachmentFilenameAsUtf8(fileName);
10586
response.setHeader("Content-Disposition", filename);
10687
if (isPartialRequest) {
10788
// Handling here a partial response (pseudo streaming)

0 commit comments

Comments
 (0)