mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
Merge pull request #3141 from pwntester/InsecureBeanValidation
Insecure Bean Validation query
This commit is contained in:
48
java/ql/src/Security/CWE/CWE-094/InsecureBeanValidation.java
Normal file
48
java/ql/src/Security/CWE/CWE-094/InsecureBeanValidation.java
Normal file
@@ -0,0 +1,48 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Custom error messages for constraint validators support different types of interpolation,
|
||||
including <a href="https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-message-interpolation.html#section-interpolation-with-message-expressions">Java EL expressions</a>.
|
||||
Controlling part of the message template being passed to <code>ConstraintValidatorContext.buildConstraintViolationWithTemplate()</code>
|
||||
argument can lead to arbitrary Java code execution. Unfortunately, it is common that validated (and therefore, normally
|
||||
untrusted) bean properties flow into the custom error message.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>There are different approaches to remediate the issue:</p>
|
||||
<ul>
|
||||
<li>Do not include validated bean properties in the custom error message.</li>
|
||||
<li>Use parameterized messages instead of string concatenation. For example:
|
||||
<pre>
|
||||
HibernateConstraintValidatorContext context = constraintValidatorContext.unwrap( HibernateConstraintValidatorContext.class );
|
||||
context.addMessageParameter( "foo", "bar" );
|
||||
context.buildConstraintViolationWithTemplate( "My violation message contains a parameter {foo}").addConstraintViolation();
|
||||
</pre></li>
|
||||
<li>Sanitize the validated bean properties to make sure that there are no EL expressions.
|
||||
An example of valid sanitization logic can be found <a href="https://github.com/hibernate/hibernate-validator/blob/master/engine/src/main/java/org/hibernate/validator/internal/engine/messageinterpolation/util/InterpolationHelper.java#L17">here</a>.</li>
|
||||
<li>Disable the EL interpolation and only use <code>ParameterMessageInterpolator</code>:
|
||||
<pre>
|
||||
Validator validator = Validation.byDefaultProvider()
|
||||
.configure()
|
||||
.messageInterpolator( new ParameterMessageInterpolator() )
|
||||
.buildValidatorFactory()
|
||||
.getValidator();
|
||||
</pre></li>
|
||||
<li>Replace Hibernate Validator with Apache BVal, which in its latest version does not interpolate EL expressions by default.
|
||||
Note that this replacement may not be a simple drop-in replacement.</li>
|
||||
</ul>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following validator could result in arbitrary Java code execution:</p>
|
||||
<sample src="InsecureBeanValidation.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Hibernate Reference Guide: <a href="https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#_the_code_constraintvalidatorcontext_code">ConstraintValidatorContext</a>.</li>
|
||||
<li>GitHub Security Lab research: <a href="https://securitylab.github.com/research/bean-validation-RCE">Bean validation</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
42
java/ql/src/Security/CWE/CWE-094/InsecureBeanValidation.ql
Normal file
42
java/ql/src/Security/CWE/CWE-094/InsecureBeanValidation.ql
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @name Insecure Bean Validation
|
||||
* @description User-controlled data may be evaluated as a Java EL expression, leading to arbitrary code execution.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/insecure-bean-validation
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class BuildConstraintViolationWithTemplateMethod extends Method {
|
||||
BuildConstraintViolationWithTemplateMethod() {
|
||||
this
|
||||
.getDeclaringType()
|
||||
.getASupertype*()
|
||||
.hasQualifiedName("javax.validation", "ConstraintValidatorContext") and
|
||||
this.hasName("buildConstraintViolationWithTemplate")
|
||||
}
|
||||
}
|
||||
|
||||
class BeanValidationConfig extends TaintTracking::Configuration {
|
||||
BeanValidationConfig() { this = "BeanValidationConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(MethodAccess ma |
|
||||
ma.getMethod() instanceof BuildConstraintViolationWithTemplateMethod and
|
||||
sink.asExpr() = ma.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from BeanValidationConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink, source, sink, "Custom constraint error message contains unsanitized user data"
|
||||
@@ -183,6 +183,23 @@ private class WebSocketMessageParameterSource extends RemoteFlowSource {
|
||||
override string getSourceType() { result = "Websocket onText parameter" }
|
||||
}
|
||||
|
||||
private class BeanValidationSource extends RemoteFlowSource {
|
||||
BeanValidationSource() {
|
||||
exists(Method m, Parameter v |
|
||||
this.asParameter() = v and
|
||||
m.getParameter(0) = v and
|
||||
m
|
||||
.getDeclaringType()
|
||||
.getASourceSupertype+()
|
||||
.hasQualifiedName("javax.validation", "ConstraintValidator") and
|
||||
m.hasName("isValid") and
|
||||
m.fromSource()
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "BeanValidation source" }
|
||||
}
|
||||
|
||||
/** Class for `tainted` user input. */
|
||||
abstract class UserInput extends DataFlow::Node { }
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
edges
|
||||
| InsecureBeanValidation.java:7:28:7:40 | object : String | InsecureBeanValidation.java:11:64:11:68 | value |
|
||||
nodes
|
||||
| InsecureBeanValidation.java:7:28:7:40 | object : String | semmle.label | object : String |
|
||||
| InsecureBeanValidation.java:11:64:11:68 | value | semmle.label | value |
|
||||
#select
|
||||
| InsecureBeanValidation.java:11:64:11:68 | value | InsecureBeanValidation.java:7:28:7:40 | object : String | InsecureBeanValidation.java:11:64:11:68 | value | Custom constraint error message contains unsanitized user data |
|
||||
@@ -0,0 +1,18 @@
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
|
||||
public class InsecureBeanValidation implements ConstraintValidator<Override, String> {
|
||||
|
||||
@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: Using message parameters
|
||||
constraintContext.buildConstraintViolationWithTemplate("literal {message_parameter}").addConstraintViolation().disableDefaultConstraintViolation();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE/CWE-094/InsecureBeanValidation.ql
|
||||
1
java/ql/test/query-tests/security/CWE-094/options
Normal file
1
java/ql/test/query-tests/security/CWE-094/options
Normal file
@@ -0,0 +1 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/validation-api-2.0.1.Final
|
||||
@@ -0,0 +1,8 @@
|
||||
package javax.validation;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
public interface ClockProvider {
|
||||
|
||||
Clock getClock();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package javax.validation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
public interface ConstraintValidator<A extends Annotation, T> {
|
||||
default void initialize(A constraintAnnotation) {}
|
||||
boolean isValid(T value, ConstraintValidatorContext context);
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package javax.validation;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
public interface ConstraintValidatorContext {
|
||||
|
||||
|
||||
void disableDefaultConstraintViolation();
|
||||
|
||||
|
||||
String getDefaultConstraintMessageTemplate();
|
||||
|
||||
|
||||
ClockProvider getClockProvider();
|
||||
|
||||
|
||||
ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate);
|
||||
|
||||
|
||||
<T> T unwrap(Class<T> type);
|
||||
|
||||
|
||||
interface ConstraintViolationBuilder {
|
||||
|
||||
|
||||
NodeBuilderDefinedContext addNode(String name);
|
||||
|
||||
|
||||
NodeBuilderCustomizableContext addPropertyNode(String name);
|
||||
|
||||
|
||||
LeafNodeBuilderCustomizableContext addBeanNode();
|
||||
|
||||
|
||||
ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name,
|
||||
Class<?> containerType, Integer typeArgumentIndex);
|
||||
|
||||
|
||||
NodeBuilderDefinedContext addParameterNode(int index);
|
||||
|
||||
|
||||
ConstraintValidatorContext addConstraintViolation();
|
||||
|
||||
|
||||
interface LeafNodeBuilderDefinedContext {
|
||||
|
||||
|
||||
ConstraintValidatorContext addConstraintViolation();
|
||||
}
|
||||
|
||||
|
||||
interface LeafNodeBuilderCustomizableContext {
|
||||
|
||||
|
||||
LeafNodeContextBuilder inIterable();
|
||||
|
||||
|
||||
LeafNodeBuilderCustomizableContext inContainer(Class<?> containerClass,
|
||||
Integer typeArgumentIndex);
|
||||
|
||||
|
||||
ConstraintValidatorContext addConstraintViolation();
|
||||
}
|
||||
|
||||
|
||||
interface LeafNodeContextBuilder {
|
||||
|
||||
|
||||
LeafNodeBuilderDefinedContext atKey(Object key);
|
||||
|
||||
|
||||
LeafNodeBuilderDefinedContext atIndex(Integer index);
|
||||
|
||||
|
||||
ConstraintValidatorContext addConstraintViolation();
|
||||
}
|
||||
|
||||
|
||||
interface NodeBuilderDefinedContext {
|
||||
|
||||
|
||||
NodeBuilderCustomizableContext addNode(String name);
|
||||
|
||||
|
||||
NodeBuilderCustomizableContext addPropertyNode(String name);
|
||||
|
||||
|
||||
LeafNodeBuilderCustomizableContext addBeanNode();
|
||||
|
||||
|
||||
ContainerElementNodeBuilderCustomizableContext addContainerElementNode(
|
||||
String name, Class<?> containerType, Integer typeArgumentIndex);
|
||||
|
||||
|
||||
ConstraintValidatorContext addConstraintViolation();
|
||||
}
|
||||
|
||||
|
||||
interface NodeBuilderCustomizableContext {
|
||||
|
||||
|
||||
NodeContextBuilder inIterable();
|
||||
|
||||
|
||||
NodeBuilderCustomizableContext inContainer(Class<?> containerClass,
|
||||
Integer typeArgumentIndex);
|
||||
|
||||
|
||||
NodeBuilderCustomizableContext addNode(String name);
|
||||
|
||||
|
||||
NodeBuilderCustomizableContext addPropertyNode(String name);
|
||||
|
||||
|
||||
LeafNodeBuilderCustomizableContext addBeanNode();
|
||||
|
||||
|
||||
ContainerElementNodeBuilderCustomizableContext addContainerElementNode(
|
||||
String name, Class<?> containerType, Integer typeArgumentIndex);
|
||||
|
||||
|
||||
ConstraintValidatorContext addConstraintViolation();
|
||||
}
|
||||
|
||||
|
||||
interface NodeContextBuilder {
|
||||
|
||||
|
||||
NodeBuilderDefinedContext atKey(Object key);
|
||||
|
||||
|
||||
NodeBuilderDefinedContext atIndex(Integer index);
|
||||
|
||||
|
||||
NodeBuilderCustomizableContext addNode(String name);
|
||||
|
||||
|
||||
NodeBuilderCustomizableContext addPropertyNode(String name);
|
||||
|
||||
|
||||
LeafNodeBuilderCustomizableContext addBeanNode();
|
||||
|
||||
|
||||
ContainerElementNodeBuilderCustomizableContext addContainerElementNode(
|
||||
String name, Class<?> containerType, Integer typeArgumentIndex);
|
||||
|
||||
|
||||
ConstraintValidatorContext addConstraintViolation();
|
||||
}
|
||||
|
||||
|
||||
interface ContainerElementNodeBuilderDefinedContext {
|
||||
|
||||
|
||||
NodeBuilderCustomizableContext addPropertyNode(String name);
|
||||
|
||||
|
||||
LeafNodeBuilderCustomizableContext addBeanNode();
|
||||
|
||||
|
||||
ContainerElementNodeBuilderCustomizableContext addContainerElementNode(
|
||||
String name, Class<?> containerType, Integer typeArgumentIndex);
|
||||
|
||||
|
||||
ConstraintValidatorContext addConstraintViolation();
|
||||
}
|
||||
|
||||
|
||||
interface ContainerElementNodeBuilderCustomizableContext {
|
||||
|
||||
|
||||
ContainerElementNodeContextBuilder inIterable();
|
||||
|
||||
|
||||
NodeBuilderCustomizableContext addPropertyNode(String name);
|
||||
|
||||
|
||||
LeafNodeBuilderCustomizableContext addBeanNode();
|
||||
|
||||
|
||||
ContainerElementNodeBuilderCustomizableContext addContainerElementNode(
|
||||
String name, Class<?> containerType, Integer typeArgumentIndex);
|
||||
|
||||
|
||||
ConstraintValidatorContext addConstraintViolation();
|
||||
}
|
||||
|
||||
|
||||
interface ContainerElementNodeContextBuilder {
|
||||
|
||||
|
||||
ContainerElementNodeBuilderDefinedContext atKey(Object key);
|
||||
|
||||
|
||||
ContainerElementNodeBuilderDefinedContext atIndex(Integer index);
|
||||
|
||||
|
||||
NodeBuilderCustomizableContext addPropertyNode(String name);
|
||||
|
||||
|
||||
LeafNodeBuilderCustomizableContext addBeanNode();
|
||||
|
||||
|
||||
ContainerElementNodeBuilderCustomizableContext addContainerElementNode(
|
||||
String name, Class<?> containerType, Integer typeArgumentIndex);
|
||||
|
||||
|
||||
ConstraintValidatorContext addConstraintViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user