Skip to content

Commit

Permalink
fix(rest): Prevent stored XSS
Browse files Browse the repository at this point in the history
Signed-off-by: Smruti Prakash Sahoo <[email protected]>
  • Loading branch information
smrutis1 authored and heliocastro committed Jan 12, 2025
1 parent c273f19 commit 6ba3bf6
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 2 deletions.
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
<version.jee.jaxb.api>2.3.1</version.jee.jaxb.api>
<keycloak.version>26.0.7</keycloak.version>
<spring.security.crypto>6.3.3</spring.security.crypto>
<org.owasp.encoder.version>1.3.1</org.owasp.encoder.version>
</properties>
<dependencyManagement>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
import org.eclipse.sw360.datahandler.thrift.users.UserGroup;
import org.eclipse.sw360.rest.common.PropertyUtils;
import org.eclipse.sw360.rest.common.Sw360CORSFilter;
import org.eclipse.sw360.rest.common.Sw360XssFilter;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Import;

@SpringBootApplication
@Import(Sw360CORSFilter.class)
@Import({Sw360CORSFilter.class, Sw360XssFilter.class})
public class Sw360AuthorizationServer extends SpringBootServletInitializer {

private static final String SW360_PROPERTIES_FILE_PATH = "/sw360.properties";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.eclipse.sw360.datahandler.thrift.users.UserGroup;
import org.eclipse.sw360.rest.common.PropertyUtils;
import org.eclipse.sw360.rest.common.Sw360CORSFilter;
import org.eclipse.sw360.rest.common.Sw360XssFilter;
import org.eclipse.sw360.rest.resourceserver.core.OpenAPIPaginationHelper;
import org.eclipse.sw360.rest.resourceserver.core.RestControllerHelper;
import org.eclipse.sw360.rest.resourceserver.security.apiToken.ApiTokenAuthenticationFilter;
Expand All @@ -50,7 +51,7 @@
import java.util.*;

@SpringBootApplication
@Import(Sw360CORSFilter.class)
@Import({Sw360CORSFilter.class, Sw360XssFilter.class})
public class Sw360ResourceServer extends SpringBootServletInitializer {

public static final String REST_BASE_PATH = "/api";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;

@Profile("!SECURITY_MOCK")
@Configuration
Expand Down Expand Up @@ -74,6 +75,8 @@ public SecurityFilterChain securityFilterChainRS1(HttpSecurity http) throws Exce
.jwkSetUri(issuerUri)))
.httpBasic(Customizer.withDefaults())
.exceptionHandling(x -> x.authenticationEntryPoint(saep))
.headers(headers -> headers.xssProtection(xXssConfig -> xXssConfig.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))
.contentSecurityPolicy(cps -> cps.policyDirectives("script-src 'self'")))
.csrf(csrf -> csrf.disable()).build();
}

Expand Down
6 changes: 6 additions & 0 deletions rest/rest-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.owasp.encoder</groupId>
<artifactId>encoder</artifactId>
<version>${org.owasp.encoder.version}</version>
</dependency>

</dependencies>
<build>
<finalName>rest-common</finalName>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
SPDX-FileCopyrightText: © 2024 Siemens AG
SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.sw360.rest.common;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;

import java.io.ByteArrayInputStream;
import java.io.IOException;

/**
* This class is used to sanitize the input from the user to prevent XSS attacks.
*
* @author [email protected]
*/
public class Sw360XSSRequestWrapper extends HttpServletRequestWrapper {

public Sw360XSSRequestWrapper(HttpServletRequest request) {
super(request);
}

@Override
public ServletInputStream getInputStream() throws IOException {
ServletInputStream originalInputStream = super.getInputStream();
String requestBody = new String(originalInputStream.readAllBytes());

JsonNode requestBodyJSON = sanitizeInput(new ObjectMapper().readTree(requestBody));
String sanitizedBody = requestBodyJSON.toString();
return new ServletInputStream() {
private final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
sanitizedBody.getBytes()
);

@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}

@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener(ReadListener readListener) {
}
};
}

@Override
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = stripXSS(values[i]);
}
return encodedValues;
}

@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
return stripXSS(value);
}

@Override
public String getHeader(String name) {
String value = super.getHeader(name);
return stripXSS(value);
}

private String stripXSS(String value) {
return org.owasp.encoder.Encode.forHtml(value);

}

private JsonNode sanitizeInput(JsonNode input) {
if (input.isTextual()) {
return JsonNodeFactory.instance.textNode(stripXSS(input.asText()));
} else if (input.isArray()) {
ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode();
for (JsonNode element : input) {
arrayNode.add(sanitizeInput(element));
}
return arrayNode;
} else if (input.isObject()) {
ObjectNode objectNode = JsonNodeFactory.instance.objectNode();
input.fields().forEachRemaining(entry -> objectNode.set(entry.getKey(), sanitizeInput(entry.getValue())));
return objectNode;
} else {
return input;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
SPDX-FileCopyrightText: © 2024 Siemens AG
SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.sw360.rest.common;


import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import java.io.IOException;

/**
* This filter is used to sanitize the input from the user to prevent XSS attacks.
* @author [email protected]
*/
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class Sw360XssFilter implements Filter {

/**
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(new Sw360XSSRequestWrapper((HttpServletRequest) servletRequest), servletResponse);
}
}

0 comments on commit 6ba3bf6

Please sign in to comment.