Skip to content

Commit 802d930

Browse files
committed
updates
1 parent 5313c47 commit 802d930

8 files changed

Lines changed: 498 additions & 11 deletions

File tree

core-tests/e2e-tests/spring/spring-rest-bb/src/main/kotlin/com/foo/rest/examples/bb/cleanup/BBCleanUpApplication.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,22 @@ open class BBCleanUpApplication {
1919
val data = mutableMapOf<String,BBCleanUpDto>()
2020
}
2121

22+
private fun containsUnsafeUrlCharacters(str: String): Boolean {
23+
// Characters that are problematic in URL path parameters
24+
val unsafeChars = setOf('<', '>', '"', '\'', '&', '/', '\\', '{', '}', '|', '^', '[', ']', '`', ' ')
25+
return str.any { it in unsafeChars || it.code < 32 || it.code > 126 }
26+
}
2227

2328
@PostMapping(path = ["/items"], consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
2429
fun postCreate(@RequestBody dto: BBCleanUpDto) : ResponseEntity<BBCleanUpDto> {
2530

2631
if(dto.id.isNullOrBlank() || dto.x == null) return ResponseEntity.status(400).build()
2732

33+
// Check if ID contains characters that are not safe for URL path parameters
34+
if(containsUnsafeUrlCharacters(dto.id!!)) {
35+
return ResponseEntity.status(400).build()
36+
}
37+
2838
if(data.containsKey(dto.id)){
2939
return ResponseEntity.status(409).build()
3040
}
@@ -34,7 +44,6 @@ open class BBCleanUpApplication {
3444
return ResponseEntity.status(201).body(dto)
3545
}
3646

37-
3847
@DeleteMapping(path = ["/items/{id}"])
3948
fun delete(@PathVariable id: String) : ResponseEntity<Void> {
4049

@@ -46,4 +55,4 @@ open class BBCleanUpApplication {
4655
CoveredTargets.cover("DELETE")
4756
return ResponseEntity.status(204).build()
4857
}
49-
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.foo.rest.examples.spring.openapi.v3.security.xss.reflected
2+
3+
import io.swagger.v3.oas.annotations.Operation
4+
import io.swagger.v3.oas.annotations.responses.ApiResponse
5+
import io.swagger.v3.oas.annotations.responses.ApiResponses
6+
import org.springframework.boot.SpringApplication
7+
import org.springframework.boot.autoconfigure.SpringBootApplication
8+
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
9+
import org.springframework.http.MediaType
10+
import org.springframework.web.bind.annotation.*
11+
12+
data class CommentDto(
13+
val comment: String? = null,
14+
val author: String? = null
15+
)
16+
17+
@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
18+
@RequestMapping(path = ["/api/reflected"])
19+
@RestController
20+
open class XSSReflectedApplication {
21+
22+
companion object {
23+
@JvmStatic
24+
fun main(args: Array<String>) {
25+
SpringApplication.run(XSSReflectedApplication::class.java, *args)
26+
}
27+
}
28+
29+
// ==== BODY PARAMETER - Comment System ====
30+
31+
@PostMapping(path = ["/comment"], produces = [MediaType.TEXT_HTML_VALUE])
32+
open fun reflectComment(@RequestBody commentDto: CommentDto): String {
33+
// VULNERABLE: Reflects user input without sanitization
34+
val comment = commentDto.comment ?: "No comment"
35+
val author = commentDto.author ?: "Anonymous"
36+
37+
return """
38+
<!DOCTYPE html>
39+
<html>
40+
<head>
41+
<title>Comment Reflected</title>
42+
</head>
43+
<body>
44+
<h2>Comment Received!</h2>
45+
<div class="comment">
46+
<p><strong>Author:</strong> $author</p>
47+
<p><strong>Comment:</strong> $comment</p>
48+
</div>
49+
</body>
50+
</html>
51+
""".trimIndent()
52+
}
53+
54+
// ==== PATH PARAMETER - User Profile System ====
55+
56+
@Operation(
57+
summary = "GET endpoint to display user profile (Reflected XSS with path parameter)",
58+
description = "Displays user profile without sanitization - allows Reflected XSS attacks via path parameter"
59+
)
60+
@ApiResponses(
61+
value = [
62+
ApiResponse(responseCode = "200", description = "User profile displayed"),
63+
ApiResponse(responseCode = "400", description = "Invalid URI with special characters")
64+
]
65+
)
66+
@GetMapping(path = ["/user/{username}"], produces = [MediaType.TEXT_HTML_VALUE])
67+
open fun getUserProfile(@PathVariable username: String): String {
68+
// VULNERABLE: Reflects path parameter without sanitization
69+
return """
70+
<!DOCTYPE html>
71+
<html>
72+
<head>
73+
<title>User Profile</title>
74+
</head>
75+
<body>
76+
<h1>Profile of $username</h1>
77+
<div class="profile-info">
78+
<p><strong>Username:</strong> $username</p>
79+
<p>Welcome to $username's profile page!</p>
80+
</div>
81+
</body>
82+
</html>
83+
""".trimIndent()
84+
}
85+
86+
// ==== QUERY PARAMETER - Search System ====
87+
88+
@GetMapping(path = ["/search"], produces = [MediaType.TEXT_HTML_VALUE])
89+
open fun search(
90+
@RequestParam(name = "query", required = false, defaultValue = "") query: String
91+
): String {
92+
// VULNERABLE: Reflects query parameter without sanitization
93+
return """
94+
<!DOCTYPE html>
95+
<html>
96+
<head>
97+
<title>Search Results</title>
98+
</head>
99+
<body>
100+
<h1>Search Results</h1>
101+
<p>You searched for: <strong>$query</strong></p>
102+
<div class="results">
103+
<p>No results found for "$query"</p>
104+
</div>
105+
</body>
106+
</html>
107+
""".trimIndent()
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package com.foo.rest.examples.spring.openapi.v3.security.xss.stored
2+
3+
import io.swagger.v3.oas.annotations.Operation
4+
import io.swagger.v3.oas.annotations.responses.ApiResponse
5+
import io.swagger.v3.oas.annotations.responses.ApiResponses
6+
import org.springframework.boot.SpringApplication
7+
import org.springframework.boot.autoconfigure.SpringBootApplication
8+
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
9+
import org.springframework.http.MediaType
10+
import org.springframework.web.bind.annotation.*
11+
12+
data class CommentDto(
13+
val comment: String? = null,
14+
val author: String? = null
15+
)
16+
17+
@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
18+
@RequestMapping(path = ["/api/stored"])
19+
@RestController
20+
open class XSSStoredApplication {
21+
22+
companion object {
23+
@JvmStatic
24+
fun main(args: Array<String>) {
25+
SpringApplication.run(XSSStoredApplication::class.java, *args)
26+
}
27+
28+
// In-memory storage for stored XSS examples
29+
private val comments = mutableListOf<Pair<String, String>>() // Body parameter
30+
private val userBios = mutableMapOf<String, String>() // Path parameter
31+
private val guestbookEntries = mutableListOf<Pair<String, String>>() // Query parameter
32+
}
33+
34+
// ==== BODY PARAMETER - Comment System ====
35+
36+
@PostMapping(path = ["/comment"], produces = [MediaType.TEXT_HTML_VALUE])
37+
open fun storeComment(@RequestBody commentDto: CommentDto): String {
38+
// VULNERABLE: Stores user input without sanitization
39+
val comment = commentDto.comment ?: "No comment"
40+
val author = commentDto.author ?: "Anonymous"
41+
42+
comments.add(Pair(author, comment))
43+
44+
return """
45+
<!DOCTYPE html>
46+
<html>
47+
<head>
48+
<title>Comment Stored</title>
49+
</head>
50+
<body>
51+
<h2>Comment Stored Successfully!</h2>
52+
<p>Your comment has been saved and will be displayed to other users.</p>
53+
<a href="/api/stored/comments">View all comments</a>
54+
</body>
55+
</html>
56+
""".trimIndent()
57+
}
58+
59+
@GetMapping(path = ["/comments"], produces = [MediaType.TEXT_HTML_VALUE])
60+
open fun getComments(): String {
61+
// VULNERABLE: Displays stored user input without sanitization
62+
val commentsList = comments.joinToString("\n") { (author, comment) ->
63+
"""
64+
<div class="comment">
65+
<p><strong>Author:</strong> $author</p>
66+
<p><strong>Comment:</strong> $comment</p>
67+
<hr>
68+
</div>
69+
""".trimIndent()
70+
}
71+
72+
return """
73+
<!DOCTYPE html>
74+
<html>
75+
<head>
76+
<title>All Comments</title>
77+
</head>
78+
<body>
79+
<h1>All Comments</h1>
80+
${if (comments.isEmpty()) "<p>No comments yet.</p>" else commentsList}
81+
</body>
82+
</html>
83+
""".trimIndent()
84+
}
85+
86+
// ==== PATH PARAMETER - User Bio System ====
87+
88+
@Operation(
89+
summary = "POST endpoint to store user bio (Stored XSS with path parameter)",
90+
description = "Stores user bio in memory without sanitization - allows Stored XSS attacks via path parameter"
91+
)
92+
@ApiResponses(
93+
value = [
94+
ApiResponse(responseCode = "200", description = "Bio stored successfully"),
95+
ApiResponse(responseCode = "400", description = "Invalid URI with special characters")
96+
]
97+
)
98+
@PostMapping(path = ["/user/{username}"], produces = [MediaType.TEXT_HTML_VALUE])
99+
open fun storeBio(
100+
@PathVariable username: String,
101+
@RequestParam(name = "bio", required = false, defaultValue = "") bio: String
102+
): String {
103+
// VULNERABLE: Stores user input from both path parameter and query parameter without sanitization
104+
userBios[username] = bio
105+
106+
return """
107+
<!DOCTYPE html>
108+
<html>
109+
<head>
110+
<title>Bio Stored</title>
111+
</head>
112+
</html>
113+
""".trimIndent()
114+
}
115+
116+
@Operation(
117+
summary = "GET endpoint to retrieve user profile with bio (Stored XSS)",
118+
description = "Displays stored user bio without sanitization - executes stored XSS from path parameter data"
119+
)
120+
@ApiResponses(
121+
value = [
122+
ApiResponse(responseCode = "200", description = "User profile displayed"),
123+
ApiResponse(responseCode = "400", description = "Invalid URI with special characters")
124+
]
125+
)
126+
@GetMapping(path = ["/user/{username}"], produces = [MediaType.TEXT_HTML_VALUE])
127+
open fun getUserProfile(@PathVariable username: String): String {
128+
// VULNERABLE: Displays stored user input without sanitization
129+
val bio = userBios[username] ?: "No bio available"
130+
131+
return """
132+
<!DOCTYPE html>
133+
<html>
134+
<head>
135+
<title>User Profile</title>
136+
</head>
137+
<body>
138+
<div class="profile-info">
139+
<p><strong>Bio:</strong> $bio</p>
140+
</div>
141+
</body>
142+
</html>
143+
""".trimIndent()
144+
}
145+
146+
// ==== QUERY PARAMETER - Guestbook System ====
147+
148+
@PostMapping(path = ["/guestbook"], produces = [MediaType.TEXT_HTML_VALUE])
149+
open fun storeGuestbookEntry(
150+
@RequestParam(name = "name", required = false, defaultValue = "Anonymous") name: String,
151+
@RequestParam(name = "entry", required = false, defaultValue = "") entry: String
152+
): String {
153+
// VULNERABLE: Stores user input from query parameters without sanitization
154+
guestbookEntries.add(Pair(name, entry))
155+
156+
return """
157+
<!DOCTYPE html>
158+
<html>
159+
<head>
160+
<title>Entry Stored</title>
161+
</head>
162+
<body>
163+
<h2>Guestbook Entry Stored!</h2>
164+
<p>Thank you for signing our guestbook!</p>
165+
<a href="/api/stored/guestbook">View guestbook</a>
166+
</body>
167+
</html>
168+
""".trimIndent()
169+
}
170+
171+
@GetMapping(path = ["/guestbook"], produces = [MediaType.TEXT_HTML_VALUE])
172+
open fun getGuestbook(): String {
173+
// VULNERABLE: Displays stored user input without sanitization
174+
val entriesList = guestbookEntries.joinToString("\n") { (name, entry) ->
175+
"""
176+
<div class="entry">
177+
<p><strong>$name</strong> wrote:</p>
178+
<p>$entry</p>
179+
<hr>
180+
</div>
181+
""".trimIndent()
182+
}
183+
184+
return """
185+
<!DOCTYPE html>
186+
<html>
187+
<head>
188+
<title>Guestbook</title>
189+
</head>
190+
<body>
191+
<h1>Guestbook</h1>
192+
${if (guestbookEntries.isEmpty()) "<p>No entries yet. Be the first to sign!</p>" else entriesList}
193+
</body>
194+
</html>
195+
""".trimIndent()
196+
}
197+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.foo.rest.examples.spring.openapi.v3.security.xss.reflected
2+
3+
import com.foo.rest.examples.spring.openapi.v3.SpringController
4+
5+
class XSSReflectedController: SpringController(XSSReflectedApplication::class.java)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.foo.rest.examples.spring.openapi.v3.security.xss.stored
2+
3+
import com.foo.rest.examples.spring.openapi.v3.SpringController
4+
5+
class XSSStoredController: SpringController(XSSStoredApplication::class.java)

0 commit comments

Comments
 (0)