Skip to content

Commit f3e2bd0

Browse files
authored
Merge pull request #3141 from pwntester/InsecureBeanValidation
Insecure Bean Validation query
2 parents 7c3964a + 34ae6e0 commit f3e2bd0

File tree

12 files changed

+414
-0
lines changed

12 files changed

+414
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
lgtm,codescanning
2+
* A new query "Insecure Bean Validation" (`java/insecure-bean-validation`) has been added. This query
3+
finds server-side template injections caused by untrusted data flowing from a bean
4+
property into a custom error message for a constraint validator. This
5+
vulnerability can lead to arbitrary code execution.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import javax.validation.ConstraintValidator;
2+
import javax.validation.ConstraintValidatorContext;
3+
import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
4+
import java.util.regex.Matcher;
5+
import java.util.regex.Pattern;
6+
7+
public class TestValidator implements ConstraintValidator<Object, String> {
8+
9+
public static class InterpolationHelper {
10+
11+
public static final char BEGIN_TERM = '{';
12+
public static final char END_TERM = '}';
13+
public static final char EL_DESIGNATOR = '$';
14+
public static final char ESCAPE_CHARACTER = '\\';
15+
16+
private static final Pattern ESCAPE_MESSAGE_PARAMETER_PATTERN = Pattern.compile( "([\\" + ESCAPE_CHARACTER + BEGIN_TERM + END_TERM + EL_DESIGNATOR + "])" );
17+
18+
private InterpolationHelper() {
19+
}
20+
21+
public static String escapeMessageParameter(String messageParameter) {
22+
if ( messageParameter == null ) {
23+
return null;
24+
}
25+
return ESCAPE_MESSAGE_PARAMETER_PATTERN.matcher( messageParameter ).replaceAll( Matcher.quoteReplacement( String.valueOf( ESCAPE_CHARACTER ) ) + "$1" );
26+
}
27+
28+
}
29+
30+
@Override
31+
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
32+
String value = object + " is invalid";
33+
34+
// Bad: Bean properties (normally user-controlled) are passed directly to `buildConstraintViolationWithTemplate`
35+
constraintContext.buildConstraintViolationWithTemplate(value).addConstraintViolation().disableDefaultConstraintViolation();
36+
37+
// Good: Bean properties (normally user-controlled) are escaped
38+
String escaped = InterpolationHelper.escapeMessageParameter(value);
39+
constraintContext.buildConstraintViolationWithTemplate(escaped).addConstraintViolation().disableDefaultConstraintViolation();
40+
41+
// Good: Bean properties (normally user-controlled) are parameterized
42+
HibernateConstraintValidatorContext context = constraintContext.unwrap( HibernateConstraintValidatorContext.class );
43+
context.addMessageParameter( "prop", object );
44+
context.buildConstraintViolationWithTemplate( "{prop} is invalid").addConstraintViolation();
45+
return false;
46+
}
47+
48+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>Custom error messages for constraint validators support different types of interpolation,
7+
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>.
8+
Controlling part of the message template being passed to <code>ConstraintValidatorContext.buildConstraintViolationWithTemplate()</code>
9+
argument can lead to arbitrary Java code execution. Unfortunately, it is common that validated (and therefore, normally
10+
untrusted) bean properties flow into the custom error message.</p>
11+
</overview>
12+
13+
<recommendation>
14+
<p>There are different approaches to remediate the issue:</p>
15+
<ul>
16+
<li>Do not include validated bean properties in the custom error message.</li>
17+
<li>Use parameterized messages instead of string concatenation. For example:
18+
<pre>
19+
HibernateConstraintValidatorContext context = constraintValidatorContext.unwrap( HibernateConstraintValidatorContext.class );
20+
context.addMessageParameter( "foo", "bar" );
21+
context.buildConstraintViolationWithTemplate( "My violation message contains a parameter {foo}").addConstraintViolation();
22+
</pre></li>
23+
<li>Sanitize the validated bean properties to make sure that there are no EL expressions.
24+
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>
25+
<li>Disable the EL interpolation and only use <code>ParameterMessageInterpolator</code>:
26+
<pre>
27+
Validator validator = Validation.byDefaultProvider()
28+
.configure()
29+
.messageInterpolator( new ParameterMessageInterpolator() )
30+
.buildValidatorFactory()
31+
.getValidator();
32+
</pre></li>
33+
<li>Replace Hibernate Validator with Apache BVal, which in its latest version does not interpolate EL expressions by default.
34+
Note that this replacement may not be a simple drop-in replacement.</li>
35+
</ul>
36+
</recommendation>
37+
38+
<example>
39+
<p>The following validator could result in arbitrary Java code execution:</p>
40+
<sample src="InsecureBeanValidation.java" />
41+
</example>
42+
43+
<references>
44+
<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>
45+
<li>GitHub Security Lab research: <a href="https://securitylab.github.com/research/bean-validation-RCE">Bean validation</a>.</li>
46+
</references>
47+
</qhelp>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @name Insecure Bean Validation
3+
* @description User-controlled data may be evaluated as a Java EL expression, leading to arbitrary code execution.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @precision high
7+
* @id java/insecure-bean-validation
8+
* @tags security
9+
* external/cwe/cwe-094
10+
*/
11+
12+
import java
13+
import semmle.code.java.dataflow.TaintTracking
14+
import semmle.code.java.dataflow.FlowSources
15+
import DataFlow::PathGraph
16+
17+
class BuildConstraintViolationWithTemplateMethod extends Method {
18+
BuildConstraintViolationWithTemplateMethod() {
19+
this
20+
.getDeclaringType()
21+
.getASupertype*()
22+
.hasQualifiedName("javax.validation", "ConstraintValidatorContext") and
23+
this.hasName("buildConstraintViolationWithTemplate")
24+
}
25+
}
26+
27+
class BeanValidationConfig extends TaintTracking::Configuration {
28+
BeanValidationConfig() { this = "BeanValidationConfig" }
29+
30+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
31+
32+
override predicate isSink(DataFlow::Node sink) {
33+
exists(MethodAccess ma |
34+
ma.getMethod() instanceof BuildConstraintViolationWithTemplateMethod and
35+
sink.asExpr() = ma.getArgument(0)
36+
)
37+
}
38+
}
39+
40+
from BeanValidationConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink
41+
where cfg.hasFlowPath(source, sink)
42+
select sink, source, sink, "Custom constraint error message contains unsanitized user data"

java/ql/src/semmle/code/java/dataflow/FlowSources.qll

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,23 @@ private class WebSocketMessageParameterSource extends RemoteFlowSource {
183183
override string getSourceType() { result = "Websocket onText parameter" }
184184
}
185185

186+
private class BeanValidationSource extends RemoteFlowSource {
187+
BeanValidationSource() {
188+
exists(Method m, Parameter v |
189+
this.asParameter() = v and
190+
m.getParameter(0) = v and
191+
m
192+
.getDeclaringType()
193+
.getASourceSupertype+()
194+
.hasQualifiedName("javax.validation", "ConstraintValidator") and
195+
m.hasName("isValid") and
196+
m.fromSource()
197+
)
198+
}
199+
200+
override string getSourceType() { result = "BeanValidation source" }
201+
}
202+
186203
/** Class for `tainted` user input. */
187204
abstract class UserInput extends DataFlow::Node { }
188205

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
edges
2+
| InsecureBeanValidation.java:7:28:7:40 | object : String | InsecureBeanValidation.java:11:64:11:68 | value |
3+
nodes
4+
| InsecureBeanValidation.java:7:28:7:40 | object : String | semmle.label | object : String |
5+
| InsecureBeanValidation.java:11:64:11:68 | value | semmle.label | value |
6+
#select
7+
| 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 |
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import javax.validation.ConstraintValidator;
2+
import javax.validation.ConstraintValidatorContext;
3+
4+
public class InsecureBeanValidation implements ConstraintValidator<Override, String> {
5+
6+
@Override
7+
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
8+
String value = object + " is invalid";
9+
10+
// Bad: Bean properties (normally user-controlled) are passed directly to `buildConstraintViolationWithTemplate`
11+
constraintContext.buildConstraintViolationWithTemplate(value).addConstraintViolation().disableDefaultConstraintViolation();
12+
13+
// Good: Using message parameters
14+
constraintContext.buildConstraintViolationWithTemplate("literal {message_parameter}").addConstraintViolation().disableDefaultConstraintViolation();
15+
16+
return true;
17+
}
18+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE/CWE-094/InsecureBeanValidation.ql
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/validation-api-2.0.1.Final
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package javax.validation;
2+
3+
import java.time.Clock;
4+
5+
public interface ClockProvider {
6+
7+
Clock getClock();
8+
}

0 commit comments

Comments
 (0)