Skip to content
Permalink
Browse files Browse the repository at this point in the history
Added CSRF protection to the togglz console via a CSRF token passed b…
…etween the server and the clinet. This remediates the vulnerabilty CVE-2020-28191 by blocking CSRF attacks as the attcker will not be able to guess the CSRF token value. (#495)

This has been implemented with either the session timeout of the application the togglz console is embedded in. Or if no user session is available it defaults to a 10 minute timeout for the CSRF token.
This CSRF token does not interfere with either OWASP's CSRFGuard or Spring-Security's CSRF protection if they are used within the application.

Co-authored-by: Joseph Beeton <joseph.p.beeton1@aexp.com>
  • Loading branch information
JoeBeeton and Joseph Beeton committed Jan 12, 2021
1 parent 5f48f45 commit ed66e3f
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 5 deletions.
6 changes: 6 additions & 0 deletions console/pom.xml
Expand Up @@ -77,5 +77,11 @@
<version>6.0.0</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>

</dependencies>
</project>
Expand Up @@ -19,6 +19,7 @@

import com.floreysoft.jmte.Engine;
import org.togglz.core.util.Services;
import org.togglz.servlet.spi.CSRFTokenValidator;

public class EditPageHandler extends RequestHandlerBase {

Expand All @@ -37,7 +38,10 @@ public void process(RequestEvent event) throws IOException {
FeatureManager featureManager = event.getFeatureManager();
HttpServletRequest request = event.getRequest();
HttpServletResponse response = event.getResponse();

if(!validateCSRFToken(event)) {
renderErrorPage(event);
return;
}
// identify the feature
Feature feature = null;
String featureAsString = request.getParameter("f");
Expand Down Expand Up @@ -78,7 +82,6 @@ public void process(RequestEvent event) throws IOException {
response.sendRedirect("index");

}

// got validation errors
else {
renderEditPage(event, featureModel);
Expand All @@ -88,6 +91,24 @@ public void process(RequestEvent event) throws IOException {

}

private boolean validateCSRFToken(RequestEvent event) {
boolean isValid = true;
for (CSRFTokenValidator validator : Services.get(CSRFTokenValidator.class)) {
if(!validator.isTokenValid(event.getRequest())){
isValid = false;
break;
}
}
return isValid;
}

private void renderErrorPage(RequestEvent event) throws IOException {
String template = getResourceAsString("error.html");
String content = new Engine().transform(template, new HashMap<>());
event.getResponse().setStatus(401);
writeResponse(event, content);
}

private void renderEditPage(RequestEvent event, FeatureModel featureModel) throws IOException {
List<CSRFToken> tokens = new ArrayList<>();
for (CSRFTokenProvider provider : Services.get(CSRFTokenProvider.class)) {
Expand All @@ -96,7 +117,6 @@ private void renderEditPage(RequestEvent event, FeatureModel featureModel) throw
tokens.add(token);
}
}

Map<String, Object> model = new HashMap<>();
model.put("model", featureModel);
model.put("tokens", tokens);
Expand Down
@@ -0,0 +1,32 @@
package org.togglz.console.security;

import java.util.HashMap;
import java.util.concurrent.TimeUnit;

import org.apache.commons.collections4.map.PassiveExpiringMap;
import org.togglz.servlet.spi.CSRFToken;

public class TogglzCSRFTokenCache {

private static final PassiveExpiringMap<String, CSRFToken> expiringMap;
private static final Object lock = new Object();
static {
PassiveExpiringMap.ConstantTimeToLiveExpirationPolicy<String, CSRFToken>
expirationPolicy = new PassiveExpiringMap.ConstantTimeToLiveExpirationPolicy<>(
10, TimeUnit.MINUTES);
expiringMap = new PassiveExpiringMap<>(expirationPolicy, new HashMap<>());
}

public static void cacheToken(CSRFToken token) {
synchronized (lock) {
expiringMap.put(token.getValue(), token);
}
}

public static boolean isTokenInCache(CSRFToken token) {
synchronized (lock) {
return expiringMap.containsKey(token.getValue());
}
}

}
@@ -0,0 +1,25 @@
package org.togglz.console.security;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.togglz.servlet.spi.CSRFToken;
import org.togglz.servlet.spi.CSRFTokenProvider;

import static org.togglz.console.security.TogglzCSRFTokenValidator.CSRF_TOKEN_NAME;

public class TogglzCSRFTokenProvider implements CSRFTokenProvider {

@Override
public CSRFToken getToken(HttpServletRequest request) {
CSRFToken token;
if (request.getAttribute(CSRF_TOKEN_NAME) == null) {
token = new CSRFToken(CSRF_TOKEN_NAME, UUID.randomUUID().toString());
TogglzCSRFTokenCache.cacheToken(token);
} else {
token = new CSRFToken(CSRF_TOKEN_NAME, request.getAttribute(CSRF_TOKEN_NAME).toString());
}
return token;
}
}
@@ -0,0 +1,22 @@
package org.togglz.console.security;

import javax.servlet.http.HttpServletRequest;

import org.togglz.servlet.spi.CSRFToken;
import org.togglz.servlet.spi.CSRFTokenValidator;

public class TogglzCSRFTokenValidator implements CSRFTokenValidator {


public static final String CSRF_TOKEN_NAME = "togglz_csrf";

@Override
public boolean isTokenValid(HttpServletRequest request) {
String token = request.getParameter(CSRF_TOKEN_NAME);
if(token==null) {
return false;
} else {
return TogglzCSRFTokenCache.isTokenInCache(new CSRFToken(CSRF_TOKEN_NAME,token));
}
}
}
@@ -0,0 +1 @@
org.togglz.console.security.TogglzCSRFTokenProvider
@@ -0,0 +1 @@
org.togglz.console.security.TogglzCSRFTokenValidator
4 changes: 4 additions & 0 deletions console/src/main/resources/org/togglz/console/error.html
@@ -0,0 +1,4 @@
<div style="text-align: center;">
<h1>ERROR</h1>
<h1><small>Invalid CSRF Token, please refresh browser from the main Togglz page.</small></h1>
</div>
3 changes: 1 addition & 2 deletions console/src/main/resources/org/togglz/console/index.html
Expand Up @@ -67,8 +67,7 @@
${end}
</td>
<td class="feature-actions">
<a class="btn btn-sm btn-default" href="edit?f=${feature.name}" title="Edit ${feature.label} feature">
<span class="glyphicon glyphicon-cog text-muted"></span>
<a class="btn btn-sm btn-default" href="edit?f=${feature.name}${foreach tokens token}${if token.name = "togglz_csrf"}&${token.name}=${token.value}${end}${end}" title="Edit ${feature.label} feature"> <span class="glyphicon glyphicon-cog text-muted"></span>
</a>
</td>
</tr>
Expand Down
@@ -0,0 +1,9 @@
package org.togglz.servlet.spi;

import javax.servlet.http.HttpServletRequest;

public interface CSRFTokenValidator {


boolean isTokenValid(HttpServletRequest request);
}

0 comments on commit ed66e3f

Please sign in to comment.