CVE-2020-28191 (Java, published 2022-12-26T22:15Z)
Description: value: The console in Togglz before 2.9.4 allows CSRF.
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-352 \u2014 Cross-Site Request Forgery (CSRF)
The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
Repository: https://github.com/togglz/togglz
Files changed:
console/pom.xml
console/src/main/java/org/togglz/console/handlers/edit/EditPageHandler.java
console/src/main/java/org/togglz/console/security/TogglzCSRFTokenCache.java
console/src/main/java/org/togglz/console/security/TogglzCSRFTokenProvider.java
console/src/main/java/org/togglz/console/security/TogglzCSRFTokenValidator.java
console/src/main/resources/META-INF/services/org.togglz.servlet.spi.CSRFTokenProvider
console/src/main/resources/META-INF/services/org.togglz.servlet.spi.CSRFTokenValidator
console/src/main/resources/org/togglz/console/error.html
console/src/main/resources/org/togglz/console/index.html
servlet/src/main/java/org/togglz/servlet/spi/CSRFTokenValidator.java
Commit message: Added CSRF protection to the togglz console via a CSRF token passed between 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>

Vulnerable code:



                <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>

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

import org.togglz.servlet.spi.CSRFTokenValidator;
		if(!validateCSRFToken(event)) {
			renderErrorPage(event);
			return;
		}
    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);
	}

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());
		}
	}
	
}
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;
	}
}
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));
		}
	}
}
org.togglz.console.security.TogglzCSRFTokenProvider
org.togglz.console.security.TogglzCSRFTokenValidator
<div style="text-align: center;">
    <h1>ERROR</h1>
    <h1><small>Invalid CSRF Token, please refresh browser from the main Togglz page.</small></h1>
</div>
                  <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>
package org.togglz.servlet.spi;

import javax.servlet.http.HttpServletRequest;

public interface CSRFTokenValidator {


	boolean isTokenValid(HttpServletRequest request);
}