Skip to content

Commit 4c34e67

Browse files
committed
Add @pattern as RegexExecution => SSRF sanitizer
1 parent 679eb0d commit 4c34e67

File tree

4 files changed

+48
-184
lines changed

4 files changed

+48
-184
lines changed

java/ql/lib/semmle/code/java/Concepts.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module;
88

99
import java
1010
private import semmle.code.java.dataflow.DataFlow
11+
private import semmle.code.java.frameworks.JavaxAnnotations
1112

1213
/**
1314
* A data-flow node that executes a regular expression.

java/ql/lib/semmle/code/java/frameworks/JavaxAnnotations.qll

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,38 @@ class WebServiceAnnotation extends Annotation {
163163
class WebServiceRefAnnotation extends Annotation {
164164
WebServiceRefAnnotation() { this.getType().hasQualifiedName("javax.xml.ws", "WebServiceRef") }
165165
}
166+
167+
/*
168+
* Annotations in the package `javax.validation.constraints`.
169+
*/
170+
171+
/**
172+
* A `@javax.validation.constraints.Pattern` annotation.
173+
*/
174+
class PatternAnnotation extends Annotation, RegexExecutionExpr::Range {
175+
PatternAnnotation() {
176+
this.getType()
177+
.hasQualifiedName(["javax.validation.constraints", "jakarta.validation.constraints"],
178+
"Pattern")
179+
}
180+
181+
override Expr getRegex() { result = this.getValue("regexp") }
182+
183+
override Expr getString() {
184+
// Annotation on field accessed by direct read - value of field will match regexp
185+
result = this.getAnnotatedElement().(Field).getAnAccess()
186+
or
187+
// Annotation on field accessed by getter - value of field will match regexp
188+
result.(MethodCall).getMethod().(GetterMethod).getField() = this.getAnnotatedElement()
189+
or
190+
// Annotation on parameter - value of parameter will match regexp
191+
result = this.getAnnotatedElement().(Parameter).getAnAccess().(VarRead)
192+
or
193+
// Annotation on method - return value of method will match regexp
194+
result.(Call).getCallee() = this.getAnnotatedElement()
195+
// TODO - we could also consider the case where the annotation is on a type
196+
// but this harder to model and not very common.
197+
}
198+
199+
override string getName() { result = "@javax.validation.constraints.Pattern annotation" }
200+
}

java/ql/lib/semmle/code/java/security/Sanitizers.qll

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,11 @@ class SimpleTypeSanitizer extends DataFlow::Node {
4141
* make the type recursive. Otherwise use `RegexpCheckBarrier`.
4242
*/
4343
predicate regexpMatchGuardChecks(Guard guard, Expr e, boolean branch) {
44-
exists(Method method, MethodCall mc |
45-
method = mc.getMethod() and
46-
guard = mc and
47-
branch = true
48-
|
49-
e = mc.(RegexExecutionExpr::Range).getString()
50-
or
51-
// Other `matches` methods.
52-
method.getName() = "matches" and
53-
e = mc.getQualifier()
54-
)
44+
exists(RegexExecutionExpr::Range ree | not ree instanceof Annotation |
45+
guard = ree and
46+
e = ree.getString()
47+
) and
48+
branch = true
5549
}
5650

5751
/**
@@ -62,5 +56,12 @@ predicate regexpMatchGuardChecks(Guard guard, Expr e, boolean branch) {
6256
class RegexpCheckBarrier extends DataFlow::Node {
6357
RegexpCheckBarrier() {
6458
this = DataFlow::BarrierGuard<regexpMatchGuardChecks/3>::getABarrierNode()
59+
or
60+
// Annotations don't fit into the model of barrier guards because the
61+
// annotation doesn't dominate the sanitized expression, so we instead
62+
// treat them as barriers directly.
63+
exists(RegexExecutionExpr::Range ree | ree instanceof Annotation |
64+
this.asExpr() = ree.getString()
65+
)
6566
}
6667
}

0 commit comments

Comments
 (0)