Skip to content

Commit 3b6cc97

Browse files
committed
Sanitize Spring bodies directly associated with an XSS-safe Content-Type
1 parent 0ebbb33 commit 3b6cc97

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed

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

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import java
77
private import semmle.code.java.dataflow.ExternalFlow
8+
private import semmle.code.java.dataflow.DataFlow
9+
private import semmle.code.java.security.XSS as XSS
810

911
/** The class `org.springframework.http.HttpEntity` or an instantiation of it. */
1012
class SpringHttpEntity extends Class {
@@ -140,3 +142,96 @@ private class SpringHttpFlowStep extends SummaryModelCsv {
140142
]
141143
}
142144
}
145+
146+
private string getSpringConstantContentType(FieldAccess e) {
147+
e.getQualifier().getType().(RefType).hasQualifiedName("org.springframework.http", "MediaType") and
148+
exists(string fieldName | e.getField().hasName(fieldName) |
149+
fieldName = "APPLICATION_ATOM_XML" and result = "application/atom+xml"
150+
or
151+
fieldName = "APPLICATION_CBOR" and result = "application/cbor"
152+
or
153+
fieldName = "APPLICATION_FORM_URLENCODED" and result = "application/x-www-form-urlencoded"
154+
or
155+
fieldName = "APPLICATION_JSON" and result = "application/json"
156+
or
157+
fieldName = "APPLICATION_JSON_UTF8" and result = "application/json;charset=UTF-8"
158+
or
159+
fieldName = "APPLICATION_NDJSON" and result = "application/x-ndjson"
160+
or
161+
fieldName = "APPLICATION_OCTET_STREAM" and result = "application/octet-stream"
162+
or
163+
fieldName = "APPLICATION_PDF" and result = "application/pdf"
164+
or
165+
fieldName = "APPLICATION_PROBLEM_JSON" and result = "application/problem+json"
166+
or
167+
fieldName = "APPLICATION_PROBLEM_JSON_UTF8" and
168+
result = "application/problem+json;charset=UTF-8"
169+
or
170+
fieldName = "APPLICATION_PROBLEM_XML" and result = "application/problem+xml"
171+
or
172+
fieldName = "APPLICATION_RSS_XML" and result = "application/rss+xml"
173+
or
174+
fieldName = "APPLICATION_STREAM_JSON" and result = "application/stream+json"
175+
or
176+
fieldName = "APPLICATION_XHTML_XML" and result = "application/xhtml+xml"
177+
or
178+
fieldName = "APPLICATION_XML" and result = "application/xml"
179+
or
180+
fieldName = "IMAGE_GIF" and result = "image/gif"
181+
or
182+
fieldName = "IMAGE_JPEG" and result = "image/jpeg"
183+
or
184+
fieldName = "IMAGE_PNG" and result = "image/png"
185+
or
186+
fieldName = "MULTIPART_FORM_DATA" and result = "multipart/form-data"
187+
or
188+
fieldName = "MULTIPART_MIXED" and result = "multipart/mixed"
189+
or
190+
fieldName = "MULTIPART_RELATED" and result = "multipart/related"
191+
or
192+
fieldName = "TEXT_EVENT_STREAM" and result = "text/event-stream"
193+
or
194+
fieldName = "TEXT_HTML" and result = "text/html"
195+
or
196+
fieldName = "TEXT_MARKDOWN" and result = "text/markdown"
197+
or
198+
fieldName = "TEXT_PLAIN" and result = "text/plain"
199+
or
200+
fieldName = "TEXT_XML" and result = "text/xml"
201+
)
202+
}
203+
204+
private predicate isXssSafeContentTypeExpr(Expr e) {
205+
XSS::isXssSafeContentType(e.(CompileTimeConstantExpr).getStringValue()) or
206+
XSS::isXssSafeContentType(getSpringConstantContentType(e))
207+
}
208+
209+
private DataFlow::Node getASanitizedBodyBuilder() {
210+
result.asExpr() =
211+
any(MethodAccess ma |
212+
ma.getCallee()
213+
.hasQualifiedName("org.springframework.http", "ResponseEntity<>$BodyBuilder",
214+
"contentType") and
215+
isXssSafeContentTypeExpr(ma.getArgument(0))
216+
)
217+
or
218+
result.asExpr() =
219+
any(MethodAccess ma |
220+
ma.getQualifier() = getASanitizedBodyBuilder().asExpr() and
221+
ma.getType()
222+
.(RefType)
223+
.hasQualifiedName("org.springframework.http", "ResponseEntity<>$BodyBuilder")
224+
)
225+
or
226+
DataFlow::localFlow(getASanitizedBodyBuilder(), result)
227+
}
228+
229+
private class SanitizedBodyCall extends XSS::XssSanitizer {
230+
SanitizedBodyCall() {
231+
this.asExpr() =
232+
any(MethodAccess ma |
233+
ma.getQualifier() = getASanitizedBodyBuilder().asExpr() and
234+
ma.getCallee().hasName("body")
235+
).getArgument(0)
236+
}
237+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ public static ResponseEntity<String> specificContentType(boolean safeContentType
2828
}
2929
else {
3030
if(chainDirectly) {
31-
return builder.contentType(MediaType.APPLICATION_JSON).body(userControlled); // $SPURIOUS: xss
31+
return builder.contentType(MediaType.APPLICATION_JSON).body(userControlled);
3232
}
3333
else {
3434
ResponseEntity.BodyBuilder builder2 = builder.contentType(MediaType.APPLICATION_JSON);
35-
return builder2.body(userControlled); // $SPURIOUS: xss
35+
return builder2.body(userControlled);
3636
}
3737
}
3838

0 commit comments

Comments
 (0)