|
5 | 5 |
|
6 | 6 | import java
|
7 | 7 | 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 |
8 | 10 |
|
9 | 11 | /** The class `org.springframework.http.HttpEntity` or an instantiation of it. */
|
10 | 12 | class SpringHttpEntity extends Class {
|
@@ -140,3 +142,96 @@ private class SpringHttpFlowStep extends SummaryModelCsv {
|
140 | 142 | ]
|
141 | 143 | }
|
142 | 144 | }
|
| 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 | +} |
0 commit comments