CVE-2020-11002 (Java, published 2020-04-10T19:15Z)
Description: value: dropwizard-validation before versions 2.0.3 and 1.3.21 has a remote code execution vulnerability. A server-side template injection was identified in the self-validating feature enabling attackers to inject arbitrary Java EL expressions, leading to Remote Code Execution (RCE) vulnerability. If you are using a self-validating bean an upgrade to Dropwizard 1.3.21/2.0.3 or later is strongly recommended. The changes introduced in Dropwizard 1.3.19 and 2.0.2 for CVE-2020-5245 unfortunately did not fix the underlying issue completely. The issue has been fixed in dropwizard-validation 1.3.21 and 2.0.3 or later. We strongly recommend upgrading to one of these versions.
Severity: HIGH (CVSS v3 base score: 8.8)
Weakness: CWE-74 \u2014 Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
Repository: https://github.com/dropwizard/dropwizard
Files changed:
dropwizard-validation/src/main/java/io/dropwizard/validation/InterpolationHelper.java
dropwizard-validation/src/main/java/io/dropwizard/validation/selfvalidating/SelfValidating.java
dropwizard-validation/src/main/java/io/dropwizard/validation/selfvalidating/SelfValidatingValidator.java
dropwizard-validation/src/main/java/io/dropwizard/validation/selfvalidating/ViolationCollector.java
dropwizard-validation/src/test/java/io/dropwizard/validation/SelfValidationTest.java
Commit message: Disable message interpolation in ConstraintViolations by default (#3208)

Disable message interpolation in ConstraintViolations by default but allow enabling it explicitly with `SelfValidating#escapeExpressions()`.

Additionally, `ConstraintViolations` now provides a set of methods which take a map of message parameters for interpolation.
The message parameters will be escaped by default.

Refs #3153
Refs #3157

Vulnerable code:
        final ViolationCollector collector = new ViolationCollector(context);
import java.util.regex.Matcher;
import java.util.regex.Pattern;
    private static final Pattern ESCAPE_PATTERN = Pattern.compile("\\$\\{");
    private ConstraintValidatorContext context;
    public ViolationCollector(ConstraintValidatorContext context) {
        this.context = context;
     * @param message the message of the violation (any EL expression will be escaped and not parsed)
        String messageTemplate = escapeEl(message);
        context.buildConstraintViolationWithTemplate(messageTemplate)
     * @param message      the message of the violation (any EL expression will be escaped and not parsed)
        String messageTemplate = escapeEl(message);
        context.buildConstraintViolationWithTemplate(messageTemplate)
        String messageTemplate = escapeEl(message);
        context.buildConstraintViolationWithTemplate(messageTemplate)
     * @param message      the message of the violation (any EL expression will be escaped and not parsed)
        String messageTemplate = escapeEl(message);
    @Nullable
    private String escapeEl(@Nullable String s) {
        if (s == null || s.isEmpty()) {
            return s;
        }

        final Matcher m = ESCAPE_PATTERN.matcher(s);
        final StringBuffer sb = new StringBuffer(s.length() + 16);
        while (m.find()) {
            m.appendReplacement(sb, "\\\\\\${");
        m.appendTail(sb);
        return sb.toString();
        return context;
    public void violationMessagesAreEscaped() {

Fixed code:
/*
 * Hibernate Validator, declare and validate application constraints
 *
 * License: Apache License, Version 2.0
 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
 */
package io.dropwizard.validation;

import javax.annotation.Nullable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Utilities used for message interpolation.
 *
 * @author Guillaume Smet
 * @since 2.0.3
 */
public final class InterpolationHelper {

    public static final char BEGIN_TERM = '{';
    public static final char END_TERM = '}';
    public static final char EL_DESIGNATOR = '$';
    public static final char ESCAPE_CHARACTER = '\\';

    private static final Pattern ESCAPE_MESSAGE_PARAMETER_PATTERN = Pattern.compile("([\\" + ESCAPE_CHARACTER + BEGIN_TERM + END_TERM + EL_DESIGNATOR + "])");

    private InterpolationHelper() {
    }

    @Nullable
    public static String escapeMessageParameter(@Nullable String messageParameter) {
        if (messageParameter == null) {
            return null;
        }
        return ESCAPE_MESSAGE_PARAMETER_PATTERN.matcher(messageParameter).replaceAll(Matcher.quoteReplacement(String.valueOf(ESCAPE_CHARACTER)) + "$1");
    }
}
import java.util.Map;

    /**
     * Escape EL expressions to avoid template injection attacks.
     * <p>
     * This has serious security implications and you will
     * have to escape the violation messages added to {@link ViolationCollector} appropriately.
     *
     * @see ViolationCollector#addViolation(String, Map)
     * @see ViolationCollector#addViolation(String, String, Map)
     * @see ViolationCollector#addViolation(String, Integer, String, Map)
     */
    boolean escapeExpressions() default true;
    private boolean escapeExpressions = true;
        escapeExpressions = constraintAnnotation.escapeExpressions();
        final ViolationCollector collector = new ViolationCollector(context, escapeExpressions);
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;

import java.util.Collections;
import java.util.Map;

import static io.dropwizard.validation.InterpolationHelper.escapeMessageParameter;
    private final ConstraintValidatorContext constraintValidatorContext;
    private final boolean escapeExpressions;
    public ViolationCollector(ConstraintValidatorContext constraintValidatorContext) {
        this(constraintValidatorContext, true);
    }
    public ViolationCollector(ConstraintValidatorContext constraintValidatorContext, boolean escapeExpressions) {
        this.constraintValidatorContext = constraintValidatorContext;
        this.escapeExpressions = escapeExpressions;
     * <p>
     * Prefer the method with explicit message parameters if you want to interpolate the message.
     * @param message the message of the violation
     * @see #addViolation(String, Map)
        addViolation(message, Collections.emptyMap());
    }

    /**
     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
     *
     * @param message           the message of the violation
     * @param messageParameters a map of message parameters which can be interpolated in the violation message
     * @since 2.0.3
     */
    public void addViolation(String message, Map<String, Object> messageParameters) {
        getContextWithMessageParameters(messageParameters)
                .buildConstraintViolationWithTemplate(sanitizeTemplate(message))
     * <p>
     * Prefer the method with explicit message parameters if you want to interpolate the message.
     * @param message      the message of the violation
     * @see #addViolation(String, String, Map)
        addViolation(propertyName, message, Collections.emptyMap());
    }

    /**
     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
     *
     * @param propertyName      the name of the property
     * @param message           the message of the violation
     * @param messageParameters a map of message parameters which can be interpolated in the violation message
     * @since 2.0.3
     */
    public void addViolation(String propertyName, String message, Map<String, Object> messageParameters) {
        getContextWithMessageParameters(messageParameters)
                .buildConstraintViolationWithTemplate(sanitizeTemplate(message))
     * Prefer the method with explicit message parameters if you want to interpolate the message.
     * @see ViolationCollector#addViolation(String, Integer, String, Map)
        addViolation(propertyName, index, message, Collections.emptyMap());
    }

    /**
     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
     *
     * @param propertyName      the name of the property with the violation
     * @param index             the index of the element with the violation
     * @param message           the message of the violation
     * @param messageParameters a map of message parameters which can be interpolated in the violation message
     * @since 2.0.3
     */
    public void addViolation(String propertyName, Integer index, String message, Map<String, Object> messageParameters) {
        getContextWithMessageParameters(messageParameters)
                .buildConstraintViolationWithTemplate(sanitizeTemplate(message))
     * @param message      the message of the violation
        addViolation(propertyName, key, message, Collections.emptyMap());
    }

    /**
     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
     *
     * @param propertyName      the name of the property with the violation
     * @param key               the key of the element with the violation
     * @param message           the message of the violation
     * @param messageParameters a map of message parameters which can be interpolated in the violation message
     * @since 2.0.3
     */
    public void addViolation(String propertyName, String key, String message, Map<String, Object> messageParameters) {
        final String messageTemplate = sanitizeTemplate(message);
        final HibernateConstraintValidatorContext context = getContextWithMessageParameters(messageParameters);
    private HibernateConstraintValidatorContext getContextWithMessageParameters(Map<String, Object> messageParameters) {
        final HibernateConstraintValidatorContext context =
                constraintValidatorContext.unwrap(HibernateConstraintValidatorContext.class);
        for (Map.Entry<String, Object> messageParameter : messageParameters.entrySet()) {
            final Object value = messageParameter.getValue();
            final String escapedValue = value == null ? null : escapeMessageParameter(value.toString());
            context.addMessageParameter(messageParameter.getKey(), escapedValue);
        return context;
    }
    @Nullable
    private String sanitizeTemplate(@Nullable String message) {
        return escapeExpressions ? escapeMessageParameter(message) : message;
        return constraintValidatorContext;
import io.dropwizard.util.Maps;
import java.util.Collections;
            col.addViolation("$\\A{1+1}");
            col.addViolation("{value}", Collections.singletonMap("value", "TEST"));
    @SelfValidating(escapeExpressions = false)
    public static class EscapingDisabledExample {
        @SuppressWarnings("unused")
        @SelfValidation
        public void validateFail(ViolationCollector col) {
            col.addViolation("${'value'}");
            col.addViolation("$\\A{1+1}");
            col.addViolation("{value}", Collections.singletonMap("value", "TEST"));
            col.addViolation("${'property'}", "${'value'}");
            col.addViolation("${'property'}", 1, "${'value'}");
            col.addViolation("${'property'}", "${'key'}", "${'value'}");
        }
    }

    @SelfValidating(escapeExpressions = false)
    public static class MessageParametersExample {
        @SuppressWarnings("unused")
        @SelfValidation
        public void validateFail(ViolationCollector col) {
            col.addViolation("{1+1}");
            col.addViolation("{value}", Collections.singletonMap("value", "VALUE"));
            col.addViolation("No parameter", Collections.singletonMap("value", "VALUE"));
            col.addViolation("{value} {unsetParameter}", Collections.singletonMap("value", "VALUE"));
            col.addViolation("{value", Collections.singletonMap("value", "VALUE"));
            col.addViolation("value}", Collections.singletonMap("value", "VALUE"));
            col.addViolation("{  value  }", Collections.singletonMap("value", "VALUE"));
            col.addViolation("Mixed ${'value'} {value}", Collections.singletonMap("value", "VALUE"));
            col.addViolation("Nested {value}", Collections.singletonMap("value", "${'nested'}"));
            col.addViolation("{property}", "{value}", Maps.of("property", "PROPERTY", "value", "VALUE"));
            col.addViolation("{property}", 1, "{value}", Maps.of("property", "PROPERTY", "value", "VALUE"));
            col.addViolation("{property}", "{key}", "{value}", Maps.of("property", "PROPERTY", "key", "KEY", "value", "VALUE"));
        }
    }

    public void violationMessagesAreEscapedByDefault() {
                " $\\A{1+1}",
                " {value}",

    @Test
    public void violationMessagesAreInterpolatedIfEscapingDisabled() {
        assertThat(ConstraintViolations.format(validator.validate(new EscapingDisabledExample()))).containsExactly(
                " A2",
                " TEST",
                " value",
                "${'property'} value",
                "${'property'}[${'key'}] value",
                "${'property'}[1] value"
        );
        assertThat(TestLoggerFactory.getAllLoggingEvents()).isEmpty();
    }

    @Test
    public void messageParametersExample() {
        assertThat(ConstraintViolations.format(validator.validate(new MessageParametersExample()))).containsExactly(
                " Mixed value VALUE",
                " Nested ${'nested'}",
                " No parameter",
                " VALUE",
                " VALUE {unsetParameter}",
                " value}",
                " {  value  }",
                " {1+1}",
                " {value",
                "{property} VALUE",
                "{property}[1] VALUE",
                "{property}[{key}] VALUE"
        );
        assertThat(TestLoggerFactory.getAllLoggingEvents()).isEmpty();
    }