Skip to content

Commit 701d0bc

Browse files
committed
Spring content types: recognise constant content-type strings
1 parent 4397371 commit 701d0bc

File tree

3 files changed

+24
-5
lines changed

3 files changed

+24
-5
lines changed

java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,18 @@ class SpringRequestMappingMethod extends SpringControllerMethod {
121121
SpringRequestMappingParameter getARequestParameter() { result = getAParameter() }
122122

123123
/** Gets the "produces" @RequestMapping annotation value, if present. */
124+
Expr getProducesExpr() { result = requestMappingAnnotation.getValue("produces") }
125+
126+
/** Gets the "produces" @RequestMapping annotation value, if present. */
127+
Expr getAProducesExpr() {
128+
result = this.getProducesExpr() and not result instanceof ArrayInit
129+
or
130+
result = this.getProducesExpr().(ArrayInit).getAnInit()
131+
}
132+
133+
/** Gets the "produces" @RequestMapping annotation value, if present and a string constant. */
124134
string getProduces() {
125-
result =
126-
requestMappingAnnotation.getValue("produces").(CompileTimeConstantExpr).getStringValue()
135+
result = this.getProducesExpr().(CompileTimeConstantExpr).getStringValue()
127136
}
128137

129138
/** Gets the "value" @RequestMapping annotation value, if present. */

java/ql/lib/semmle/code/java/frameworks/spring/SpringHttp.qll

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,19 @@ private class SpringHttpFlowStep extends SummaryModelCsv {
145145
}
146146
}
147147

148+
private predicate specifiesContentType(SpringRequestMappingMethod method) {
149+
method.getProducesExpr().(ArrayInit).getSize() != 0 or
150+
not method.getProducesExpr() instanceof ArrayInit
151+
}
152+
148153
private class SpringXssSink extends XSS::XssSink {
149154
SpringXssSink() {
150155
exists(SpringRequestMappingMethod requestMappingMethod, ReturnStmt rs |
151156
requestMappingMethod = rs.getEnclosingCallable() and
152157
this.asExpr() = rs.getResult() and
153158
(
154-
not exists(requestMappingMethod.getProduces()) or
155-
requestMappingMethod.getProduces().matches("text/%")
159+
not specifiesContentType(requestMappingMethod) or
160+
isXssVulnerableContentTypeExpr(requestMappingMethod.getAProducesExpr())
156161
)
157162
|
158163
// If a Spring request mapping method is either annotated with @ResponseBody (or equivalent),
@@ -251,6 +256,11 @@ private string getSpringConstantContentType(FieldAccess e) {
251256
)
252257
}
253258

259+
private predicate isXssVulnerableContentTypeExpr(Expr e) {
260+
XSS::isXssVulnerableContentType(e.(CompileTimeConstantExpr).getStringValue()) or
261+
XSS::isXssVulnerableContentType(getSpringConstantContentType(e))
262+
}
263+
254264
private predicate isXssSafeContentTypeExpr(Expr e) {
255265
XSS::isXssSafeContentType(e.(CompileTimeConstantExpr).getStringValue()) or
256266
XSS::isXssSafeContentType(getSpringConstantContentType(e))

java/ql/test/query-tests/security/CWE-079/semmle/tests/SpringXSS.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public static ResponseEntity<String> methodContentTypeSafeStringLiteral(String u
6060

6161
@GetMapping(value = "/xyz", produces = MediaType.TEXT_HTML_VALUE)
6262
public static ResponseEntity<String> methodContentTypeUnsafe(String userControlled) {
63-
return ResponseEntity.ok(userControlled); // $MISSING: xss
63+
return ResponseEntity.ok(userControlled); // $xss
6464
}
6565

6666
@GetMapping(value = "/xyz", produces = "text/html")

0 commit comments

Comments
 (0)