Skip to content

Commit 392e405

Browse files
committed
Add Spring-XSS test
This covers the cases currently exercised in https://github.com/github/codeql-securitylab/blob/main/java/ql/src/pwntester/security/RestXSS.ql
1 parent 16c5952 commit 392e405

File tree

2 files changed

+156
-1
lines changed

2 files changed

+156
-1
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import org.springframework.http.ResponseEntity;
2+
import org.springframework.http.MediaType;
3+
import org.springframework.http.HttpStatus;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.PostMapping;
6+
import org.springframework.web.bind.annotation.RequestMapping;
7+
import org.springframework.web.bind.annotation.RestController;
8+
9+
import java.util.Optional;
10+
11+
@RestController
12+
public class SpringXSS {
13+
14+
@GetMapping
15+
public static ResponseEntity<String> specificContentType(boolean safeContentType, boolean chainDirectly, String userControlled) {
16+
17+
ResponseEntity.BodyBuilder builder = ResponseEntity.ok();
18+
19+
if(safeContentType) {
20+
if(chainDirectly) {
21+
return builder.contentType(MediaType.TEXT_HTML).body(userControlled); // $xss
22+
}
23+
else {
24+
ResponseEntity.BodyBuilder builder2 = builder.contentType(MediaType.TEXT_HTML);
25+
return builder2.body(userControlled); // $xss
26+
}
27+
}
28+
else {
29+
if(chainDirectly) {
30+
return builder.contentType(MediaType.APPLICATION_JSON).body(userControlled); // $SPURIOUS: xss
31+
}
32+
else {
33+
ResponseEntity.BodyBuilder builder2 = builder.contentType(MediaType.APPLICATION_JSON);
34+
return builder2.body(userControlled); // $SPURIOUS: xss
35+
}
36+
}
37+
38+
}
39+
40+
@GetMapping(value = "/xyz", produces = MediaType.APPLICATION_JSON_VALUE)
41+
public static ResponseEntity<String> methodContentTypeSafe(String userControlled) {
42+
return ResponseEntity.ok(userControlled);
43+
}
44+
45+
@PostMapping(value = "/xyz", produces = MediaType.APPLICATION_JSON_VALUE)
46+
public static ResponseEntity<String> methodContentTypeSafePost(String userControlled) {
47+
return ResponseEntity.ok(userControlled);
48+
}
49+
50+
@RequestMapping(value = "/xyz", produces = MediaType.APPLICATION_JSON_VALUE)
51+
public static ResponseEntity<String> methodContentTypeSafeRequest(String userControlled) {
52+
return ResponseEntity.ok(userControlled);
53+
}
54+
55+
@GetMapping(value = "/xyz", produces = "application/json")
56+
public static ResponseEntity<String> methodContentTypeSafeStringLiteral(String userControlled) {
57+
return ResponseEntity.ok(userControlled);
58+
}
59+
60+
@GetMapping(value = "/xyz", produces = MediaType.TEXT_HTML_VALUE)
61+
public static ResponseEntity<String> methodContentTypeUnsafe(String userControlled) {
62+
return ResponseEntity.ok(userControlled); // $MISSING: xss
63+
}
64+
65+
@GetMapping(value = "/xyz", produces = "text/html")
66+
public static ResponseEntity<String> methodContentTypeUnsafeStringLiteral(String userControlled) {
67+
return ResponseEntity.ok(userControlled); // $xss
68+
}
69+
70+
@GetMapping(value = "/xyz", produces = {MediaType.TEXT_HTML_VALUE, MediaType.APPLICATION_JSON_VALUE})
71+
public static ResponseEntity<String> methodContentTypeMaybeSafe(String userControlled) {
72+
return ResponseEntity.ok(userControlled); // $xss
73+
}
74+
75+
@GetMapping(value = "/xyz", produces = MediaType.APPLICATION_JSON_VALUE)
76+
public static ResponseEntity<String> methodContentTypeSafeOverriddenWithUnsafe(String userControlled) {
77+
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(userControlled); // $MISSING: xss
78+
}
79+
80+
@GetMapping(value = "/xyz", produces = MediaType.TEXT_HTML_VALUE)
81+
public static ResponseEntity<String> methodContentTypeUnsafeOverriddenWithSafe(String userControlled) {
82+
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(userControlled);
83+
}
84+
85+
@GetMapping(value = "/xyz", produces = {"text/html", "application/json"})
86+
public static ResponseEntity<String> methodContentTypeMaybeSafeStringLiterals(String userControlled, int constructionMethod) {
87+
// Also try out some alternative constructors for the ResponseEntity:
88+
switch(constructionMethod) {
89+
case 0:
90+
return ResponseEntity.ok(userControlled); // $xss
91+
case 1:
92+
return ResponseEntity.of(Optional.of(userControlled)); // $MISSING: xss
93+
case 2:
94+
return ResponseEntity.ok().body(userControlled); // $xss
95+
case 3:
96+
return new ResponseEntity<String>(userControlled, HttpStatus.OK); // $xss
97+
default:
98+
return null;
99+
}
100+
}
101+
102+
@RequestMapping(produces = {"application/json"})
103+
private static class ClassContentTypeSafe {
104+
public ResponseEntity<String> test(String userControlled) {
105+
return ResponseEntity.ok(userControlled);
106+
}
107+
108+
@GetMapping(value = "/abc")
109+
public String testDirectReturn(String userControlled) {
110+
return userControlled;
111+
}
112+
113+
@GetMapping(value = "/xyz", produces = {"text/html"})
114+
public ResponseEntity<String> overridesWithUnsafe(String userControlled) {
115+
return ResponseEntity.ok(userControlled); // $MISSING: xss
116+
}
117+
118+
@GetMapping(value = "/abc")
119+
public ResponseEntity<String> overridesWithUnsafe2(String userControlled) {
120+
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(userControlled); // $MISSING: xss
121+
}
122+
}
123+
124+
@RequestMapping(produces = {"text/html"})
125+
private static class ClassContentTypeUnsafe {
126+
public ResponseEntity<String> test(String userControlled) {
127+
return ResponseEntity.ok(userControlled); // $MISSING: xss
128+
}
129+
130+
@GetMapping(value = "/abc")
131+
public String testDirectReturn(String userControlled) {
132+
return userControlled; //$MISSING: xss
133+
}
134+
135+
@GetMapping(value = "/xyz", produces = {"application/json"})
136+
public ResponseEntity<String> overridesWithSafe(String userControlled) {
137+
return ResponseEntity.ok(userControlled);
138+
}
139+
140+
public ResponseEntity<String> overridesWithSafe2(String userControlled) {
141+
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(userControlled);
142+
}
143+
}
144+
145+
@GetMapping(value = "/abc")
146+
public static ResponseEntity<String> entityWithNoMediaType(String userControlled) {
147+
return ResponseEntity.ok(userControlled); // $xss
148+
}
149+
150+
@GetMapping(value = "/abc")
151+
public static String stringWithNoMediaType(String userControlled) {
152+
return userControlled; // $xss
153+
}
154+
155+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/javax-ws-rs-api-2.1.1/
1+
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/javax-ws-rs-api-2.1.1/:${testdir}/../../../../../stubs/springframework-5.3.8

0 commit comments

Comments
 (0)