Skip to content

Commit

Permalink
added sanitization filter for request sanitization
Browse files Browse the repository at this point in the history
  • Loading branch information
kreloaded committed Aug 29, 2023
1 parent 729d590 commit 4094d3e
Show file tree
Hide file tree
Showing 7 changed files with 419 additions and 2 deletions.
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,18 @@
<version>2.20.132</version>
</dependency>

<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
<version>20220608.1</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
</dependency>

</dependencies>

<build>
Expand Down
2 changes: 1 addition & 1 deletion repo-docs/LOCAL_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ $ touch test.secrets.json
"ERROR_MAIL_FROM": "",
"ERROR_MAIL_TO": "",
"COOKIE_DOMAIN": "",
"OPENAI_API_KEY: ""
"OPENAI_API_KEY": ""
}
```

Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/salessparrow/api/config/FilterConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.context.annotation.Configuration;

import com.salessparrow.api.filter.SameSiteCookieFilter;
import com.salessparrow.api.filter.SanitizationFilter;

@Configuration
public class FilterConfig {
Expand All @@ -18,6 +19,21 @@ public FilterRegistrationBean<SameSiteCookieFilter> sameSiteCookieFilter() {
FilterRegistrationBean<SameSiteCookieFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new SameSiteCookieFilter());
registrationBean.addUrlPatterns("/*"); // or specific URL patterns
registrationBean.setOrder(1);
return registrationBean;
}

/**
* Register SanitizationFilter
*
* @return FilterRegistrationBean<SanitizationFilter>
*/
@Bean
public FilterRegistrationBean<SanitizationFilter> sanitizationFilter() {
FilterRegistrationBean<SanitizationFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new SanitizationFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(0);
return registrationBean;
}
}
11 changes: 10 additions & 1 deletion src/main/java/com/salessparrow/api/config/InterceptorConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private final UserAuthInterceptor userAuthInterceptor;

public InterceptorConfig(UserAuthInterceptor userAuthInterceptor, JsonOnlyInterceptor jsonOnlyInterceptor, LoggerInterceptor loggerInterceptor, V1Interceptor V1Interceptor) {
public InterceptorConfig(
UserAuthInterceptor userAuthInterceptor,
JsonOnlyInterceptor jsonOnlyInterceptor,
LoggerInterceptor loggerInterceptor,
V1Interceptor V1Interceptor
) {
this.userAuthInterceptor = userAuthInterceptor;
this.loggerInterceptor = loggerInterceptor;
this.V1Interceptor = V1Interceptor;
Expand All @@ -34,15 +39,19 @@ public InterceptorConfig(UserAuthInterceptor userAuthInterceptor, JsonOnlyInterc

@Override
public void addInterceptors(InterceptorRegistry registry) {
/* Add logger interceptor to all the routes */
registry.addInterceptor(loggerInterceptor)
.addPathPatterns("/**");

/* Add json only interceptor to all the routes */
registry.addInterceptor(jsonOnlyInterceptor)
.addPathPatterns("/**");

/* Add v1 interceptor only to all the routes */
registry.addInterceptor(V1Interceptor)
.addPathPatterns("/api/v1/**");

/* Add user auth interceptor to all the routes except the ones below */
registry.addInterceptor(userAuthInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/api/v1/auth/salesforce/**")
Expand Down
151 changes: 151 additions & 0 deletions src/main/java/com/salessparrow/api/filter/SanitizationFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package com.salessparrow.api.filter;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;

import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;

import com.salessparrow.api.lib.wrappers.SanitizedRequestWrapper;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Enumeration;

/**
* Class to sanitize the request
*/
public class SanitizationFilter implements Filter {
private SanitizedRequestWrapper sanitizedRequest;

private final PolicyFactory policy = new HtmlPolicyBuilder()
.allowElements(
"a", "label", "h1", "h2", "h3", "h4", "h5", "h6",
"p", "i", "b", "u", "strong", "em", "strike", "code", "hr", "br", "div",
"table", "thead", "caption", "tbody", "tr", "th", "td", "pre")
.allowUrlProtocols("https")
.allowAttributes("href").onElements("a")
.allowAttributes("target").onElements("a")
.allowAttributes("class").globally()
.allowAttributes("id").globally()
.disallowElements(
"script", "iframe", "object", "embed", "form", "input", "button", "select",
"textarea", "style", "link", "meta", "base"
)
.toFactory();

@Override
public void init(FilterConfig filterConfig) {
}

/**
* Method to sanitize the request
*
* @param servletRequest - Servlet request object
* @param servletResponse - Servlet response object
* @param chain - Filter chain - Filter chain
*
* @throws IOException - IOException
* @throws ServletException - ServletException
*
* @return void
*/
@Override
public void doFilter(
ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain chain
) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
sanitizeRequestBody(request);
sanitizeRequestParams();
sanitizeRequestHeaders();

chain.doFilter(sanitizedRequest, servletResponse);
}

/**
* Method to sanitize the request body
*
* @param request - Servlet request object
*
* @return void
*/
private void sanitizeRequestBody(HttpServletRequest request) {
String originalBody = getRequestBody(request);
String sanitizedBody = sanitizeHtml(originalBody);
this.sanitizedRequest = new SanitizedRequestWrapper(request, sanitizedBody);
}

/**
* Method to sanitize the request params
*
* @return void
*/
private void sanitizeRequestParams() {
this.sanitizedRequest.getParameterMap().forEach((key, value) -> {
String sanitizedValue = sanitizeHtml(value[0]);
this.sanitizedRequest.setParameter(key, sanitizedValue);
});
}

/**
* Method to sanitize the request headers
*
* @return void
*/
private void sanitizeRequestHeaders() {
Enumeration<String> headerNames = this.sanitizedRequest.getHeaderNames();

if (headerNames != null && headerNames.hasMoreElements()) {
this.sanitizedRequest.getHeaderNames().asIterator().forEachRemaining(headerName -> {
String sanitizedValue = sanitizeHtml(this.sanitizedRequest.getHeader(headerName));
this.sanitizedRequest.setHeader(headerName, sanitizedValue);
});
}
}

/**
* Method to get the request body
*
* @param request - Servlet request object
* @return String - Request body
*/
private String getRequestBody(HttpServletRequest request) {
try {
BufferedReader reader = request.getReader();
String line;
StringBuilder requestBody = new StringBuilder();
while ((line = reader.readLine()) != null) {
requestBody.append(line);
}
return requestBody.toString();
} catch (IOException e) {
e.printStackTrace();
return "";
}
}

/**
* Method to sanitize the html
*
* @param input - Input string
* @return String - Sanitized string
*/
public String sanitizeHtml(String input) {
String sanitizedInput = policy.sanitize(input);
return sanitizedInput;
}

/**
* Method to destroy the filter
*/
@Override
public void destroy() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.salessparrow.api.lib.wrappers;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.text.StringEscapeUtils;

/**
* Custom request wrapper to sanitize the request body
*/
public class SanitizedRequestWrapper extends HttpServletRequestWrapper {
private final String sanitizedBody;

private Map<String, String> sanitizedParams;

private Map<String, String> sanitizedHeaders;

public SanitizedRequestWrapper(HttpServletRequest request, String sanitizedBody) {
super(request);
this.sanitizedBody = StringEscapeUtils.unescapeHtml4(sanitizedBody);
this.sanitizedParams = new HashMap<>();
this.sanitizedHeaders = new HashMap<>();
}

/**
* Method to get the request body as buffered reader
*/
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(
new InputStreamReader(
new ByteArrayInputStream(sanitizedBody.getBytes())));
}

/**
* Method to get the request body as input stream
*/
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(sanitizedBody.getBytes());

return new ServletInputStream() {
@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) {
throw new UnsupportedOperationException("Not implemented");
}
};
}

/**
* Method to set the request parameter value by key
*
* @param key
* @param value
*
* @return void
*/
public void setParameter(String key, String value) {
this.sanitizedParams.put(key, value);
}

/**
* Method to get the request parameter value by
* key from the sanitized params map if present
* else from the super class
*
* @param key - Request parameter key
*
* @return String - Request parameter value
*/
@Override
public String getParameter(String key) {
return this.sanitizedParams.getOrDefault(key, super.getParameter(key));
}

/**
* Method to get the request parameter map
*
* @param key - header key
* @param value - header value
*
* @return void
*/
public void setHeader(String key, String value) {
this.sanitizedHeaders.put(key, value);
}

@Override
public String getHeader(String key) {
return this.sanitizedHeaders.getOrDefault(key, super.getHeader(key));
}
}

Loading

0 comments on commit 4094d3e

Please sign in to comment.