CodeQL 文档

不安全的 Bean 验证

ID: java/insecure-bean-validation
Kind: path-problem
Security severity: 9.3
Severity: error
Precision: high
Tags:
   - security
   - external/cwe/cwe-094
Query suites:
   - java-code-scanning.qls
   - java-security-extended.qls
   - java-security-and-quality.qls

单击以在 CodeQL 存储库中查看查询

约束验证器的自定义错误消息支持不同类型的插值,包括 Java EL 表达式。控制传递给 ConstraintValidatorContext.buildConstraintViolationWithTemplate() 参数的消息模板的一部分会导致任意 Java 代码执行。不幸的是,经过验证(因此通常不受信任)的 bean 属性流入自定义错误消息的情况很常见。

建议

有不同的方法来解决此问题

  • 不要在自定义错误消息中包含经过验证的 bean 属性。

  • 使用参数化消息,而不是字符串连接。例如

HibernateConstraintValidatorContext context =
   constraintValidatorContext.unwrap(HibernateConstraintValidatorContext.class);
context.addMessageParameter("foo", "bar");
context.buildConstraintViolationWithTemplate("My violation message contains a parameter {foo}")
   .addConstraintViolation();
  • 清理经过验证的 Bean 属性以确保没有 EL 表达式。可以在 此处 找到有效清理逻辑的示例。

  • 禁用 EL 插值,仅使用 ParameterMessageInterpolator

Validator validator = Validation.byDefaultProvider()
   .configure()
   .messageInterpolator(new ParameterMessageInterpolator())
   .buildValidatorFactory()
   .getValidator();
  • 将 Hibernate Validator 替换为 Apache BVal,其最新版本默认不插值 EL 表达式。请注意,此替换可能不是简单的直接替换。

示例

以下验证器可能导致任意 Java 代码执行

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TestValidator implements ConstraintValidator<Object, String> {

    public static 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() {
        }

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

    }

    @Override
    public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
        String value = object + " is invalid";

        // Bad: Bean properties (normally user-controlled) are passed directly to `buildConstraintViolationWithTemplate`
        constraintContext.buildConstraintViolationWithTemplate(value).addConstraintViolation().disableDefaultConstraintViolation();

        // Good: Bean properties (normally user-controlled) are escaped 
        String escaped = InterpolationHelper.escapeMessageParameter(value);
        constraintContext.buildConstraintViolationWithTemplate(escaped).addConstraintViolation().disableDefaultConstraintViolation();

        // Good: Bean properties (normally user-controlled) are parameterized
        HibernateConstraintValidatorContext context = constraintContext.unwrap( HibernateConstraintValidatorContext.class );
        context.addMessageParameter( "prop", object );
        context.buildConstraintViolationWithTemplate( "{prop} is invalid").addConstraintViolation();
        return false;
    }

}

参考

  • ©GitHub, Inc.
  • 条款
  • 隐私