|
4 | 4 | */
|
5 | 5 |
|
6 | 6 | import java
|
| 7 | + |
7 | 8 | private import semmle.code.java.dataflow.ExternalFlow
|
8 | 9 | private import semmle.code.java.dataflow.DataFlow
|
| 10 | +private import semmle.code.java.frameworks.spring.SpringController |
9 | 11 | private import semmle.code.java.security.XSS as XSS
|
10 | 12 |
|
11 | 13 | /** The class `org.springframework.http.HttpEntity` or an instantiation of it. */
|
@@ -143,6 +145,52 @@ private class SpringHttpFlowStep extends SummaryModelCsv {
|
143 | 145 | }
|
144 | 146 | }
|
145 | 147 |
|
| 148 | +private class SpringXssSink extends XSS::XssSink { |
| 149 | + SpringXssSink() { |
| 150 | + exists(SpringRequestMappingMethod requestMappingMethod, ReturnStmt rs | |
| 151 | + requestMappingMethod = rs.getEnclosingCallable() and |
| 152 | + this.asExpr() = rs.getResult() and |
| 153 | + ( |
| 154 | + not exists(requestMappingMethod.getProduces()) or |
| 155 | + requestMappingMethod.getProduces().matches("text/%") |
| 156 | + ) |
| 157 | + | |
| 158 | + // If a Spring request mapping method is either annotated with @ResponseBody (or equivalent), |
| 159 | + // or returns a HttpEntity or sub-type, then the return value of the method is converted into |
| 160 | + // a HTTP reponse using a HttpMessageConverter implementation. The implementation is chosen |
| 161 | + // based on the return type of the method, and the Accept header of the request. |
| 162 | + // |
| 163 | + // By default, the only message converter which produces a response which is vulnerable to |
| 164 | + // XSS is the StringHttpMessageConverter, which "Accept"s all text/* content types, including |
| 165 | + // text/html. Therefore, if a browser request includes "text/html" in the "Accept" header, |
| 166 | + // any String returned will be converted into a text/html response. |
| 167 | + requestMappingMethod.isResponseBody() and |
| 168 | + requestMappingMethod.getReturnType() instanceof TypeString |
| 169 | + or |
| 170 | + exists(Type returnType | |
| 171 | + // A return type of HttpEntity<T> or ResponseEntity<T> represents an HTTP response with both |
| 172 | + // a body and a set of headers. The body is subject to the same HttpMessageConverter |
| 173 | + // process as above. |
| 174 | + returnType = requestMappingMethod.getReturnType() and |
| 175 | + ( |
| 176 | + returnType instanceof SpringHttpEntity |
| 177 | + or |
| 178 | + returnType instanceof SpringResponseEntity |
| 179 | + ) |
| 180 | + | |
| 181 | + // The type argument, representing the type of the body, is type String |
| 182 | + returnType.(ParameterizedClass).getTypeArgument(0) instanceof TypeString |
| 183 | + or |
| 184 | + // Return type is a Raw class, which means no static type information on the body. In this |
| 185 | + // case we will still treat this as an XSS sink, but rely on our taint flow steps for |
| 186 | + // HttpEntity/ResponseEntity to only pass taint into those instances if the body type was |
| 187 | + // String. |
| 188 | + returnType instanceof RawClass |
| 189 | + ) |
| 190 | + ) |
| 191 | + } |
| 192 | +} |
| 193 | + |
146 | 194 | private string getSpringConstantContentType(FieldAccess e) {
|
147 | 195 | e.getQualifier().getType().(RefType).hasQualifiedName("org.springframework.http", "MediaType") and
|
148 | 196 | exists(string fieldName | e.getField().hasName(fieldName) |
|
|
0 commit comments