Add @Pattern as RegexExecution => SSRF sanitizer

This commit is contained in:
Owen Mansel-Chan
2026-02-12 16:08:29 +00:00
parent d0999e3abd
commit bfe26c1989
4 changed files with 48 additions and 184 deletions

View File

@@ -8,6 +8,7 @@ module;
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.frameworks.JavaxAnnotations
/**
* A data-flow node that executes a regular expression.

View File

@@ -163,3 +163,38 @@ class WebServiceAnnotation extends Annotation {
class WebServiceRefAnnotation extends Annotation {
WebServiceRefAnnotation() { this.getType().hasQualifiedName("javax.xml.ws", "WebServiceRef") }
}
/*
* Annotations in the package `javax.validation.constraints`.
*/
/**
* A `@javax.validation.constraints.Pattern` annotation.
*/
class PatternAnnotation extends Annotation, RegexExecutionExpr::Range {
PatternAnnotation() {
this.getType()
.hasQualifiedName(["javax.validation.constraints", "jakarta.validation.constraints"],
"Pattern")
}
override Expr getRegex() { result = this.getValue("regexp") }
override Expr getString() {
// Annotation on field accessed by direct read - value of field will match regexp
result = this.getAnnotatedElement().(Field).getAnAccess()
or
// Annotation on field accessed by getter - value of field will match regexp
result.(MethodCall).getMethod().(GetterMethod).getField() = this.getAnnotatedElement()
or
// Annotation on parameter - value of parameter will match regexp
result = this.getAnnotatedElement().(Parameter).getAnAccess().(VarRead)
or
// Annotation on method - return value of method will match regexp
result.(Call).getCallee() = this.getAnnotatedElement()
// TODO - we could also consider the case where the annotation is on a type
// but this harder to model and not very common.
}
override string getName() { result = "@javax.validation.constraints.Pattern annotation" }
}

View File

@@ -41,17 +41,11 @@ class SimpleTypeSanitizer extends DataFlow::Node {
* make the type recursive. Otherwise use `RegexpCheckBarrier`.
*/
predicate regexpMatchGuardChecks(Guard guard, Expr e, boolean branch) {
exists(Method method, MethodCall mc |
method = mc.getMethod() and
guard = mc and
branch = true
|
e = mc.(RegexExecutionExpr::Range).getString()
or
// Other `matches` methods.
method.getName() = "matches" and
e = mc.getQualifier()
)
exists(RegexExecutionExpr::Range ree | not ree instanceof Annotation |
guard = ree and
e = ree.getString()
) and
branch = true
}
/**
@@ -62,5 +56,12 @@ predicate regexpMatchGuardChecks(Guard guard, Expr e, boolean branch) {
class RegexpCheckBarrier extends DataFlow::Node {
RegexpCheckBarrier() {
this = DataFlow::BarrierGuard<regexpMatchGuardChecks/3>::getABarrierNode()
or
// Annotations don't fit into the model of barrier guards because the
// annotation doesn't dominate the sanitized expression, so we instead
// treat them as barriers directly.
exists(RegexExecutionExpr::Range ree | ree instanceof Annotation |
this.asExpr() = ree.getString()
)
}
}