Skip to content

Commit 8c561bf

Browse files
authored
Add XSS protection example (#238)
1 parent 26e3e60 commit 8c561bf

File tree

17 files changed

+444
-2
lines changed

17 files changed

+444
-2
lines changed

README.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,6 @@ All tutorials are documented in AsciiDoc format and published as an https://anto
6363
|link:test-rest-assured[Spring Test: Integration with RestAssured] | Implement Behaviour Driven Development with https://rest-assured.io/[RestAssured]
6464
|link:test-slice-tests-rest[Spring Test: Implementing Slice Tests for REST application] | Dive into available options to implement tests with Spring Boot's test components
6565
|link:web-rest-client[Spring Web: REST Clients for calling Synchronous API] | Implement REST client to perform synchronous API calls
66+
|link:web-thymeleaf-xss[Spring Web: Preventing XSS with Thymeleaf] |Prevent Cross-Site Scripting (XSS) attacks in Spring Boot applications using Spring Security and Thymeleaf
6667
|link:modulith[Spring Modulith: Building Modular Monolithic Applications] | Structure Spring Boot applications into well-defined modules with clear boundaries
6768
|===

docs/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@
3030
** xref:test-slice-tests-rest.adoc[Implementing Slice Tests for REST application]
3131
* Spring Web
3232
** xref:web-rest-client.adoc[REST Clients for calling Synchronous API]
33+
** xref:web-thymeleaf-xss.adoc[]
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
= Preventing XSS with Spring Security and Thymeleaf
2+
:source-highlighter: highlight.js
3+
Rashidi Zin <rashidi@zin.my>
4+
1.0, May 25, 2023
5+
:icons: font
6+
:source-highlighter: highlight.js
7+
:url-quickref: https://github.com/rashidi/spring-boot-tutorials/tree/master/web-thymeleaf-xss
8+
9+
Prevent Cross-Site Scripting (XSS) attacks in Spring Boot applications using Spring Security and Thymeleaf.
10+
11+
== Background
12+
13+
https://owasp.org/www-community/attacks/xss/[Cross-Site Scripting (XSS)] is a security vulnerability that allows attackers to inject client-side scripts into web pages viewed by other users. This can lead to various attacks, including stealing session cookies, redirecting users to malicious websites, or performing actions on behalf of the user.
14+
15+
In this tutorial, we will demonstrate how to prevent XSS attacks in a Spring Boot application using Spring Security and Thymeleaf. We will focus on two main approaches:
16+
17+
1. Using Spring Security to add security headers
18+
2. Leveraging Thymeleaf's automatic HTML escaping
19+
20+
== Security Configuration
21+
22+
The first line of defense against XSS attacks is to configure Spring Security to add appropriate security headers. In our example, we use the following configuration:
23+
24+
[source,java]
25+
----
26+
@Configuration
27+
@EnableWebSecurity
28+
class SecurityConfiguration {
29+
30+
@Bean
31+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
32+
return http
33+
.headers(headers -> headers
34+
.contentSecurityPolicy(policy -> policy.policyDirectives("default-src 'self'"))
35+
.xssProtection(xss -> xss.headerValue(ENABLED_MODE_BLOCK))
36+
)
37+
.build();
38+
}
39+
}
40+
----
41+
42+
This configuration adds two important security headers:
43+
44+
1. **Content-Security-Policy**: Restricts the sources from which content can be loaded. In this case, `default-src 'self'` means that the browser should only load resources from the same origin.
45+
46+
2. **X-XSS-Protection**: Enables the browser's built-in XSS filter. The value `1; mode=block` (represented by `ENABLED_MODE_BLOCK`) tells the browser to block the response if a XSS attack is detected.
47+
48+
== Thymeleaf Template
49+
50+
Thymeleaf provides built-in protection against XSS by automatically escaping HTML content. Let's look at our simple greeting template:
51+
52+
[source,html]
53+
----
54+
<!DOCTYPE html>
55+
<html xmlns:th="http://www.thymeleaf.org">
56+
<head>
57+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
58+
<title>Greeting</title>
59+
</head>
60+
<body>
61+
<p th:id="greet" th:utext="|Hello, ${name}!|"></p>
62+
</body>
63+
</html>
64+
----
65+
66+
The key part here is the use of `th:utext` attribute. Unlike `th:text` which automatically escapes HTML content, `th:utext` (unescaped text) renders HTML as-is. This means that if a user inputs `<script>alert('XSS')</script>`, it will be executed as code rather than displayed as text. This approach is used when you need to render HTML content, but it requires careful handling of user input to prevent XSS attacks.
67+
68+
== Controller Implementation
69+
70+
Our controller simply takes a name parameter and passes it to the Thymeleaf template:
71+
72+
[source,java]
73+
----
74+
@Controller
75+
class GreetResource {
76+
77+
@GetMapping("/greet")
78+
public String greet(@RequestParam String name, Model model) {
79+
model.addAttribute("name", name);
80+
81+
return "greet";
82+
}
83+
}
84+
----
85+
86+
== Verifying XSS Protection
87+
88+
We can verify that our XSS protection is working by checking that the appropriate security headers are present in the response:
89+
90+
[source,java]
91+
----
92+
@WebMvcTest(controllers = GreetResource.class, includeFilters = @Filter(classes = EnableWebSecurity.class))
93+
class GreetResourceTests {
94+
95+
@Autowired
96+
private MockMvcTester mvc;
97+
98+
@Test
99+
@DisplayName("Given XSS protection is enabled Then response header should contain information about X-XSS-Protection and Content-Security-Policy")
100+
void headers() {
101+
mvc.get()
102+
.uri("/greet?name={name}", "rashidi")
103+
.assertThat()
104+
.matches(status().isOk())
105+
.matches(header().string("Content-Security-Policy", "default-src 'self'"))
106+
.matches(header().string("X-XSS-Protection", "1; mode=block"));
107+
}
108+
}
109+
----
110+
111+
== Common XSS Vulnerabilities to Avoid
112+
113+
1. **Understanding the risks of `th:utext`**: As demonstrated in our example, the `th:utext` attribute in Thymeleaf renders unescaped HTML, which can lead to XSS vulnerabilities if not handled properly. While our example uses `th:utext` for demonstration purposes, in production applications you should use `th:text` instead unless you are absolutely sure the content is safe and HTML rendering is required.
114+
115+
2. **Disabling Content Security Policy**: The Content Security Policy is a powerful defense against XSS. Avoid disabling it or setting overly permissive policies.
116+
117+
3. **Trusting user input**: Always validate and sanitize user input before processing it, even if you're using automatic escaping.
118+
119+
== Conclusion
120+
121+
In this tutorial, we've seen how to implement XSS protection in a Spring Boot application using Spring Security and Thymeleaf. We've demonstrated how to add appropriate security headers through Spring Security configuration. We've also shown how Thymeleaf handles HTML content with `th:utext`, while highlighting the potential security implications and when to use the safer `th:text` alternative instead.
122+
123+
Remember that security is a multi-layered approach, and XSS protection is just one aspect of a comprehensive security strategy. Always keep your dependencies up to date and follow security best practices to ensure your application remains secure.

modulith/docs/components.puml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ Container_Boundary("ModulithApplication.ModulithApplication_boundary", "Modulith
1414
Component(ModulithApplication.ModulithApplication.Subscription, "Subscription", $techn="Module", $descr="", $tags="", $link="")
1515
}
1616

17-
Rel(ModulithApplication.ModulithApplication.Subscription, ModulithApplication.ModulithApplication.Course, "listens to", $techn="", $tags="", $link="")
1817
Rel(ModulithApplication.ModulithApplication.Subscription, ModulithApplication.ModulithApplication.Student, "listens to", $techn="", $tags="", $link="")
18+
Rel(ModulithApplication.ModulithApplication.Subscription, ModulithApplication.ModulithApplication.Course, "listens to", $techn="", $tags="", $link="")
1919

2020
SHOW_LEGEND(true)
2121
@enduml

modulith/docs/module-subscription.puml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ Container_Boundary("ModulithApplication.ModulithApplication_boundary", "Modulith
1414
Component(ModulithApplication.ModulithApplication.Subscription, "Subscription", $techn="Module", $descr="", $tags="", $link="")
1515
}
1616

17-
Rel(ModulithApplication.ModulithApplication.Subscription, ModulithApplication.ModulithApplication.Course, "listens to", $techn="", $tags="", $link="")
1817
Rel(ModulithApplication.ModulithApplication.Subscription, ModulithApplication.ModulithApplication.Student, "listens to", $techn="", $tags="", $link="")
18+
Rel(ModulithApplication.ModulithApplication.Subscription, ModulithApplication.ModulithApplication.Course, "listens to", $techn="", $tags="", $link="")
1919

2020
SHOW_LEGEND(true)
2121
@enduml

web-thymeleaf-xss/.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/gradlew text eol=lf
2+
*.bat text eol=crlf
3+
*.jar binary

web-thymeleaf-xss/.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
HELP.md
2+
.gradle
3+
build/
4+
!gradle/wrapper/gradle-wrapper.jar
5+
!**/src/main/**/build/
6+
!**/src/test/**/build/
7+
8+
### STS ###
9+
.apt_generated
10+
.classpath
11+
.factorypath
12+
.project
13+
.settings
14+
.springBeans
15+
.sts4-cache
16+
bin/
17+
!**/src/main/**/bin/
18+
!**/src/test/**/bin/
19+
20+
### IntelliJ IDEA ###
21+
.idea
22+
*.iws
23+
*.iml
24+
*.ipr
25+
out/
26+
!**/src/main/**/out/
27+
!**/src/test/**/out/
28+
29+
### NetBeans ###
30+
/nbproject/private/
31+
/nbbuild/
32+
/dist/
33+
/nbdist/
34+
/.nb-gradle/
35+
36+
### VS Code ###
37+
.vscode/

web-thymeleaf-xss/README.adoc

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
= Preventing XSS with Spring Security and Thymeleaf
2+
:source-highlighter: highlight.js
3+
Rashidi Zin <rashidi@zin.my>
4+
1.0, May 25, 2023
5+
:toc:
6+
:nofooter:
7+
:icons: font
8+
:url-quickref: https://github.com/rashidi/spring-boot-tutorials/tree/master/web-thymeleaf-xss
9+
10+
Prevent Cross-Site Scripting (XSS) attacks in Spring Boot applications using Spring Security and Thymeleaf.
11+
12+
== Background
13+
14+
https://owasp.org/www-community/attacks/xss/[Cross-Site Scripting (XSS)] is a security vulnerability that allows attackers to inject client-side scripts into web pages viewed by other users. This can lead to various attacks, including stealing session cookies, redirecting users to malicious websites, or performing actions on behalf of the user.
15+
16+
In this tutorial, we will demonstrate how to prevent XSS attacks in a Spring Boot application using Spring Security and Thymeleaf. We will focus on two main approaches:
17+
18+
1. Using Spring Security to add security headers
19+
2. Leveraging Thymeleaf's automatic HTML escaping
20+
21+
== Security Configuration
22+
23+
The first line of defense against XSS attacks is to configure Spring Security to add appropriate security headers. In our example, we use the following configuration:
24+
25+
[source,java]
26+
----
27+
@Configuration
28+
@EnableWebSecurity
29+
class SecurityConfiguration {
30+
31+
@Bean
32+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
33+
return http
34+
.headers(headers -> headers
35+
.contentSecurityPolicy(policy -> policy.policyDirectives("default-src 'self'"))
36+
.xssProtection(xss -> xss.headerValue(ENABLED_MODE_BLOCK))
37+
)
38+
.build();
39+
}
40+
}
41+
----
42+
43+
This configuration adds two important security headers:
44+
45+
1. **Content-Security-Policy**: Restricts the sources from which content can be loaded. In this case, `default-src 'self'` means that the browser should only load resources from the same origin.
46+
47+
2. **X-XSS-Protection**: Enables the browser's built-in XSS filter. The value `1; mode=block` (represented by `ENABLED_MODE_BLOCK`) tells the browser to block the response if a XSS attack is detected.
48+
49+
== Thymeleaf Template
50+
51+
Thymeleaf provides built-in protection against XSS by automatically escaping HTML content. Let's look at our simple greeting template:
52+
53+
[source,html]
54+
----
55+
<!DOCTYPE html>
56+
<html xmlns:th="http://www.thymeleaf.org">
57+
<head>
58+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
59+
<title>Greeting</title>
60+
</head>
61+
<body>
62+
<p th:id="greet" th:utext="|Hello, ${name}!|"></p>
63+
</body>
64+
</html>
65+
----
66+
67+
The key part here is the use of `th:utext` attribute. Unlike `th:text` which automatically escapes HTML content, `th:utext` (unescaped text) renders HTML as-is. This means that if a user inputs `<script>alert('XSS')</script>`, it will be executed as code rather than displayed as text. This approach is used when you need to render HTML content, but it requires careful handling of user input to prevent XSS attacks.
68+
69+
== Controller Implementation
70+
71+
Our controller simply takes a name parameter and passes it to the Thymeleaf template:
72+
73+
[source,java]
74+
----
75+
@Controller
76+
class GreetResource {
77+
78+
@GetMapping("/greet")
79+
public String greet(@RequestParam String name, Model model) {
80+
model.addAttribute("name", name);
81+
82+
return "greet";
83+
}
84+
}
85+
----
86+
87+
== Verifying XSS Protection
88+
89+
We can verify that our XSS protection is working by checking that the appropriate security headers are present in the response:
90+
91+
[source,java]
92+
----
93+
@WebMvcTest(controllers = GreetResource.class, includeFilters = @Filter(classes = EnableWebSecurity.class))
94+
class GreetResourceTests {
95+
96+
@Autowired
97+
private MockMvcTester mvc;
98+
99+
@Test
100+
@DisplayName("Given XSS protection is enabled Then response header should contain information about X-XSS-Protection and Content-Security-Policy")
101+
void headers() {
102+
mvc.get()
103+
.uri("/greet?name={name}", "rashidi")
104+
.assertThat()
105+
.matches(status().isOk())
106+
.matches(header().string("Content-Security-Policy", "default-src 'self'"))
107+
.matches(header().string("X-XSS-Protection", "1; mode=block"));
108+
}
109+
}
110+
----
111+
112+
== Common XSS Vulnerabilities to Avoid
113+
114+
1. **Understanding the risks of `th:utext`**: As demonstrated in our example, the `th:utext` attribute in Thymeleaf renders unescaped HTML, which can lead to XSS vulnerabilities if not handled properly. While our example uses `th:utext` for demonstration purposes, in production applications you should use `th:text` instead unless you are absolutely sure the content is safe and HTML rendering is required.
115+
116+
2. **Disabling Content Security Policy**: The Content Security Policy is a powerful defense against XSS. Avoid disabling it or setting overly permissive policies.
117+
118+
3. **Trusting user input**: Always validate and sanitize user input before processing it, even if you're using automatic escaping.
119+
120+
== Conclusion
121+
122+
In this tutorial, we've seen how to implement XSS protection in a Spring Boot application using Spring Security and Thymeleaf. We've demonstrated how to add appropriate security headers through Spring Security configuration. We've also shown how Thymeleaf handles HTML content with `th:utext`, while highlighting the potential security implications and when to use the safer `th:text` alternative instead.
123+
124+
Remember that security is a multi-layered approach, and XSS protection is just one aspect of a comprehensive security strategy. Always keep your dependencies up to date and follow security best practices to ensure your application remains secure.

web-thymeleaf-xss/build.gradle.kts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
plugins {
2+
java
3+
id("org.springframework.boot") version "3.5.0"
4+
id("io.spring.dependency-management") version "1.1.7"
5+
}
6+
7+
group = "zin.rashidi"
8+
version = "0.0.1-SNAPSHOT"
9+
10+
java {
11+
toolchain {
12+
languageVersion = JavaLanguageVersion.of(21)
13+
}
14+
}
15+
16+
repositories {
17+
mavenCentral()
18+
}
19+
20+
dependencies {
21+
implementation("org.springframework.boot:spring-boot-starter-security")
22+
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
23+
implementation("org.springframework.boot:spring-boot-starter-web")
24+
implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
25+
testImplementation("org.springframework.boot:spring-boot-starter-test")
26+
testImplementation("org.springframework.security:spring-security-test")
27+
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
28+
}
29+
30+
tasks.withType<Test> {
31+
useJUnitPlatform()
32+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootProject.name = "web-thymeleaf-xss"

0 commit comments

Comments
 (0)