Share
## https://sploitus.com/exploit?id=B38D80B7-A75E-532F-A0D7-84FA50BFF413
# Lucy XSS Filter for Spring Boot
> ๋ค์ด๋ฒ Lucy XSS Filter๋ฅผ ์ฌ์ฉํ ๊ฐ๋ ฅํ XSS ๊ณต๊ฒฉ ๋ฐฉ์ด ์๋ฃจ์
Spring Boot ์ ํ๋ฆฌ์ผ์ด์
์ ์ฆ์ ์ ์ฉ ๊ฐ๋ฅํ XSS ํํฐ๋ง ๋ชจ๋์
๋๋ค. GET, POST, PUT, DELETE, PATCH ๋ฑ ๋ชจ๋ HTTP ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ์ JSON body๋ฅผ ์๋์ผ๋ก ํํฐ๋งํฉ๋๋ค.
## ์ฃผ์ ๊ธฐ๋ฅ
โ
**๋ชจ๋ HTTP ๋ฉ์๋ ์ง์**
- GET, POST, PUT, DELETE, PATCH ๋ฑ ๋ชจ๋ ๋ฉ์๋์ XSS ํํฐ๋ง ์ ์ฉ
- URL ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ, Form ๋ฐ์ดํฐ, JSON Request Body ๋ชจ๋ ์ง์
โ
**HTML ์ํฐํฐ ๋ณํ**
- ์ํํ ๋ฌธ์๋ฅผ HTML ์ํฐํฐ๋ก ๋ณํ: `` โ `>`
- ๋ฐ์ดํฐ ์์ค ์์ด ์์ ํ๊ฒ ๋ณด์กด
โ
**์ค๋งํธ ํํฐ๋ง**
- ํ์ผ ์
๋ก๋(multipart/form-data)๋ ์๋ ์ ์ธ
- ์ด์ค ์นํ ๋ฐฉ์ง ๋ก์ง ๋ด์ฅ
โ
**๊ฒ์ฆ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ**
- ๋ค์ด๋ฒ์์ ๊ฐ๋ฐํ Lucy XSS Filter ์ฌ์ฉ
- ํ๋ก๋์
ํ๊ฒฝ์์ ๊ฒ์ฆ๋ ์์ ์ฑ
## ๊ธฐ์ ์คํ
- Spring Boot 3.3.2+
- OpenJDK 21
- Gradle 8.5
- Lucy XSS Servlet Filter 2.0.1
## ๋น ๋ฅธ ์์
### 1. ํ๋ก์ ํธ์ ํ์ผ ๋ณต์ฌ
๋ค์ ํ์ผ๋ค์ ๋น์ ์ Spring Boot ํ๋ก์ ํธ์ ๋ณต์ฌํ์ธ์:
```
src/main/java/com/lg/common/xss/
โโโ config/
โ โโโ FilterConfig.java # Filter ๋ฑ๋ก ์ค์
โ โโโ XssRequestBodyAdvice.java # JSON body ํํฐ๋ง
โโโ filter/
โ โโโ XssEscapeUtil.java # HTML ์ํฐํฐ ๋ณํ ์ ํธ
โ โโโ XssProtectionFilter.java # XSS ๋ฐฉ์ด Filter
โ โโโ XssRequestWrapper.java # Request Wrapper
โโโ src/main/resources/
โโโ lucy-xss-superset.xml # Lucy XSS ์ค์ ํ์ผ
```
### 2. ์์กด์ฑ ์ถ๊ฐ
`build.gradle`์ Lucy XSS ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํ์ธ์:
```gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.navercorp.lucy:lucy-xss-servlet:2.0.1'
}
// ์ปดํ์ผ๋ฌ ์ต์
์ถ๊ฐ (๋งค๊ฐ๋ณ์ ์ด๋ฆ ๋ณด์กด)
tasks.withType(JavaCompile).configureEach {
options.compilerArgs alert('xss')' -> '<script>alert('xss')</script>'
```
## ํ๋ก์ ํธ ๊ตฌ์กฐ
```
lucy_filter/
โโโ src/main/java/com/lg/common/xss/
โ โโโ XssFilterApplication.java # ๋ฉ์ธ ์ ํ๋ฆฌ์ผ์ด์
(ํ
์คํธ์ฉ)
โ โโโ config/
โ โ โโโ FilterConfig.java # โจ Filter ๋ฑ๋ก ์ค์
โ โ โโโ XssRequestBodyAdvice.java # โจ JSON body ํํฐ๋ง
โ โโโ filter/
โ โ โโโ XssEscapeUtil.java # โจ HTML ์ํฐํฐ ๋ณํ ์ ํธ
โ โ โโโ XssProtectionFilter.java # โจ XSS ๋ฐฉ์ด Filter
โ โ โโโ XssRequestWrapper.java # โจ Request Wrapper
โ โโโ controller/
โ โโโ TestController.java # ํ
์คํธ์ฉ Controller
โโโ src/main/resources/
โ โโโ application.properties # ์ ํ๋ฆฌ์ผ์ด์
์ค์
โ โโโ lucy-xss-superset.xml # โจ Lucy XSS Filter ์ค์
โโโ build.gradle # Gradle ๋น๋ ํ์ผ
โโโ test-xss.ps1 # PowerShell ํ
์คํธ ์คํฌ๋ฆฝํธ
โจ = ํ์ ํ์ผ (๋ค๋ฅธ ํ๋ก์ ํธ์ ๋ณต์ฌํด์ผ ํจ)
```
## ์ํคํ
์ฒ
### ํํฐ๋ง ํ๋ฆ
```
HTTP Request
โ
XssProtectionFilter (Servlet Filter)
โโโ multipart/form-data? โ Skip (ํ์ผ ์
๋ก๋)
โโโ Already filtered? โ Skip (์ด์ค ์นํ ๋ฐฉ์ง)
โโโ XssRequestWrapper
โโโ URL ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ํํฐ๋ง
โโโ Form ๋ฐ์ดํฐ ํํฐ๋ง
โ
XssRequestBodyAdvice (RequestBodyAdvice)
โโโ JSON Request Body ํํฐ๋ง
โ
Controller
โ
Response (ํํฐ๋ง๋ ๋ฐ์ดํฐ)
```
### ์ฃผ์ ์ปดํฌ๋ํธ
| ์ปดํฌ๋ํธ | ์ญํ | ํํฐ๋ง ๋์ |
|---------|------|------------|
| **XssProtectionFilter** | Servlet Filter๋ก ๋ชจ๋ ์์ฒญ์ ๊ฐ๋ก์ฑ | - |
| **XssRequestWrapper** | Request Wrapper๋ก ํ๋ผ๋ฏธํฐ ํํฐ๋ง | URL ์ฟผ๋ฆฌ, Form ๋ฐ์ดํฐ |
| **XssRequestBodyAdvice** | RequestBodyAdvice๋ก JSON body ํํฐ๋ง | JSON Request Body |
| **XssEscapeUtil** | HTML ์ํฐํฐ ๋ณํ ์ ํธ | ๋ชจ๋ String ๊ฐ |
## ์ฌ์ฉ ์์
### GET ์์ฒญ (URL ํ๋ผ๋ฏธํฐ)
**์์ฒญ:**
```bash
curl "http://localhost:8080/api/user?name=alert('xss')"
```
**ํํฐ๋ง ๊ฒฐ๊ณผ:**
```json
{
"name": "<script>alert('xss')</script>"
}
```
### POST ์์ฒญ (JSON)
**์์ฒญ:**
```bash
curl -X POST http://localhost:8080/api/user \
-H "Content-Type: application/json" \
-d '{"name":"alert(\"xss\")","email":"test@test.com"}'
```
**ํํฐ๋ง ๊ฒฐ๊ณผ:**
```json
{
"name": "<script>alert("xss")</script>",
"email": "test@test.com"
}
```
### POST ์์ฒญ (Form Data)
**์์ฒญ:**
```bash
curl -X POST http://localhost:8080/api/user \
-d "name=" \
-d "email=test@test.com"
```
**ํํฐ๋ง ๊ฒฐ๊ณผ:**
```json
{
"name": "<img src=x onerror=alert(1)>",
"email": "test@test.com"
}
```
## ์ค์ ์ปค์คํฐ๋ง์ด์ง
### 1. ํน์ URL ์ ์ธํ๊ธฐ
`FilterConfig.java`๋ฅผ ์์ ํ์ฌ ํน์ URL์ ํํฐ๋ง์์ ์ ์ธํ ์ ์์ต๋๋ค:
```java
@Bean
public FilterRegistrationBean xssFilterRegistration() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(xssProtectionFilter);
registrationBean.addUrlPatterns("/*");
// ํน์ URL ์ ์ธ
registrationBean.addInitParameter("excludeUrls", "/api/public/*,/health");
registrationBean.setOrder(1);
registrationBean.setName("xssProtectionFilter");
return registrationBean;
}
```
### 2. ํ์ฉํ HTML ํ๊ทธ ์ค์
`lucy-xss-superset.xml`์ ์์ ํ์ฌ ์์ ํ HTML ํ๊ทธ๋ฅผ ํ์ฉํ ์ ์์ต๋๋ค:
```xml
```
### 3. ๋ก๊น
๋ ๋ฒจ ์กฐ์
`application.properties`์์ ๋ก๊น
๋ ๋ฒจ์ ์กฐ์ ํ ์ ์์ต๋๋ค:
```properties
# XSS ํํฐ ๋ก๊น
(DEBUG๋ก ์ค์ ์ ๋ชจ๋ ํํฐ๋ง ๋ด์ญ ์ถ๋ ฅ)
logging.level.com.lg.common.xss=INFO
# ํํฐ๋ง ์ ํ ๊ฐ์ ํ์ธํ๋ ค๋ฉด DEBUG๋ก ๋ณ๊ฒฝ
logging.level.com.lg.common.xss=DEBUG
```
## ํ
์คํธ
### Windows PowerShell
ํ๋ก์ ํธ์ ํฌํจ๋ `test-xss.ps1` ์คํฌ๋ฆฝํธ๋ก ๋ชจ๋ HTTP ๋ฉ์๋๋ฅผ ํ
์คํธํ ์ ์์ต๋๋ค:
```powershell
.\test-xss.ps1
```
### ๊ฐ๋ณ ํ
์คํธ
```powershell
# GET ํ
์คํธ
curl.exe "http://localhost:8080/api/test?name=alert('xss')&message=Hello"
# POST JSON ํ
์คํธ
curl.exe -X POST http://localhost:8080/api/test -H "Content-Type: application/json" -d "{\"name\":\"alert('xss')\",\"message\":\"Hello\"}"
# POST Form ํ
์คํธ
curl.exe -X POST http://localhost:8080/api/test/form -d "name=alert('xss')" -d "message=Hello"
# PUT ํ
์คํธ
curl.exe -X PUT http://localhost:8080/api/test/1 -H "Content-Type: application/json" -d "{\"title\":\"\",\"content\":\"Test\"}"
# DELETE ํ
์คํธ
curl.exe -X DELETE "http://localhost:8080/api/test/1?reason=alert('xss')"
# PATCH ํ
์คํธ
curl.exe -X PATCH http://localhost:8080/api/test/1 -H "Content-Type: application/json" -d "{\"status\":\"\"}"
```
## ์คํ ๋ฐฉ๋ฒ (ํ
์คํธ ํ๋ก์ ํธ)
### 1. Gradle ๋น๋
```bash
./gradlew clean build
```
### 2. ์ ํ๋ฆฌ์ผ์ด์
์คํ
```bash
./gradlew bootRun
```
๋๋
```bash
java -jar build/libs/lucy_filter-0.0.1-SNAPSHOT.jar
```
### 3. ์ ์ ํ์ธ
- ๊ธฐ๋ณธ ํฌํธ: `http://localhost:8080`
- ํฌ์ค ์ฒดํฌ: `http://localhost:8080/api/test/health`
## ์ฃผ์ ํน์ง ์์ธ ์ค๋ช
### 1. ์ด์ค ์นํ ๋ฐฉ์ง
`XssRequestWrapper`์์ `XSS_FILTERED_ATTRIBUTE` ์์ฑ์ ์ฌ์ฉํ์ฌ ์ด๋ฏธ ํํฐ๋ง๋ ์์ฒญ์ธ์ง ํ์ธํฉ๋๋ค:
```java
private static final String XSS_FILTERED_ATTRIBUTE = "XSS_FILTERED";
public XssRequestWrapper(HttpServletRequest request) {
super(request);
// ์ด์ค ์นํ ๋ฐฉ์ง๋ฅผ ์ํ ํ๋๊ทธ ์ค์
request.setAttribute(XSS_FILTERED_ATTRIBUTE, true);
}
public static boolean isAlreadyFiltered(HttpServletRequest request) {
return request.getAttribute(XSS_FILTERED_ATTRIBUTE) != null;
}
```
### 2. ํ์ผ ์
๋ก๋ ์ ์ธ
`Content-Type`์ด `multipart/form-data`์ธ ๊ฒฝ์ฐ ํํฐ๋ง์ ๊ฑด๋๋๋๋ค:
```java
private boolean isMultipartRequest(HttpServletRequest request) {
String contentType = request.getContentType();
if (!StringUtils.hasText(contentType)) {
return false;
}
return contentType.toLowerCase().startsWith(MULTIPART_FORM_DATA);
}
```
### 3. JSON Body ํํฐ๋ง
`RequestBodyAdvice`๋ฅผ ๊ตฌํํ์ฌ JSON Request Body์ ๋ชจ๋ String ํ๋๋ฅผ ํํฐ๋งํฉ๋๋ค:
```java
@ControllerAdvice
public class XssRequestBodyAdvice extends RequestBodyAdviceAdapter {
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
MethodParameter parameter, Type targetType,
Class> converterType) {
if (body instanceof Map) {
filterMap((Map) body);
} else {
filterObject(body);
}
return body;
}
}
```
## ๋ก๊ทธ ํ์ธ
์ ํ๋ฆฌ์ผ์ด์
์คํ ์ ๋ค์๊ณผ ๊ฐ์ ๋ก๊ทธ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค:
```
2026-01-23 14:30:00 - XSS Protection Filter initialized successfully
2026-01-23 14:30:00 - XSS Filter test: 'alert('xss')' -> '<script>alert('xss')</script>'
2026-01-23 14:30:15 - Applying XSS filter to GET request: /api/test
2026-01-23 14:30:15 - XSS filtered [param: name] 'alert('xss')' -> '<script>alert('xss')</script>'
2026-01-23 14:30:20 - Applying XSS filter to POST request: /api/test
2026-01-23 14:30:20 - XSS filtered [JSON field: name] 'alert('xss')' -> '<script>alert('xss')</script>'
2026-01-23 14:30:25 - Skipping XSS filter for multipart request: POST /api/test/upload
```
## ์ฑ๋ฅ ๊ณ ๋ ค์ฌํญ
### ํํฐ๋ง ์ค๋ฒํค๋
- **URL ํ๋ผ๋ฏธํฐ/Form ๋ฐ์ดํฐ**: ๋งค์ฐ ๋ฎ์ (< 1ms)
- **JSON Body**: ๋ฎ์ (1-5ms, ๋ฐ์ดํฐ ํฌ๊ธฐ์ ๋ฐ๋ผ ๋ค๋ฆ)
- **๋์ฉ๋ ์์ฒญ**: JSON body๊ฐ ํฐ ๊ฒฝ์ฐ ํํฐ๋ง ์๊ฐ ์ฆ๊ฐ ๊ฐ๋ฅ
### ์ต์ ํ ํ
1. **ํน์ URL ์ ์ธ**: ์ฑ๋ฅ์ด ์ค์ํ API๋ ํํฐ๋ง์์ ์ ์ธ
2. **๋ก๊น
๋ ๋ฒจ ์กฐ์ **: ํ๋ก๋์
ํ๊ฒฝ์์๋ INFO ๋ ๋ฒจ ์ฌ์ฉ
3. **ํํฐ ์์**: `FilterConfig`์์ `setOrder(1)`๋ก ๊ฐ์ฅ ๋จผ์ ์คํ๋๋๋ก ์ค์
## ์ฃผ์์ฌํญ
1. **์ฑ๋ฅ**: ๋ชจ๋ ์์ฒญ์ ํํฐ๋ง์ด ์ ์ฉ๋๋ฏ๋ก ๋์ฉ๋ ํธ๋ํฝ ํ๊ฒฝ์์๋ ์ฑ๋ฅ ํ
์คํธ ํ์
2. **ํ์ฉ ํ๊ทธ ์ค์ **: ๋น์ฆ๋์ค ์๊ตฌ์ฌํญ์ ๋ง๊ฒ `lucy-xss-superset.xml` ์์ ํ์
3. **JSON ์๋ต**: ํํฐ๋ง๋ ๋ฐ์ดํฐ๋ HTML ์ํฐํฐ๋ก ๋ณํ๋์ด ๋ฐํ๋จ
4. **๋ฐ์ดํฐ๋ฒ ์ด์ค**: ํํฐ๋ง๋ ๋ฐ์ดํฐ๊ฐ ๊ทธ๋๋ก ์ ์ฅ๋๋ฏ๋ก, ์ ์ฅ ์ ๋์ฝ๋ฉ์ด ํ์ํ ๊ฒฝ์ฐ ๋ณ๋ ์ฒ๋ฆฌ ํ์
## FAQ
### Q1. ์ด๋ฏธ ์ ์ฅ๋ ๋ฐ์ดํฐ์๋ ์ ์ฉ๋๋์?
์๋์. XSS ํํฐ๋ HTTP ์์ฒญ ์์ ์๋ง ์๋ํฉ๋๋ค. ์ด๋ฏธ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ ๋ณ๋๋ก ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
### Q2. React/Vue ๊ฐ์ ํ๋ก ํธ์๋ ํ๋ ์์ํฌ์ ํจ๊ป ์ฌ์ฉํด๋ ๋๋์?
๋ค, ๊ฐ๋ฅํฉ๋๋ค. ํ๋ก ํธ์๋ ํ๋ ์์ํฌ์ XSS ๋ฐฉ์ด์ ํจ๊ป ์ฌ์ฉํ์ฌ ๋ค์ธต ๋ฐฉ์ด๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค.
### Q3. HTML ์ํฐํฐ๋ฅผ ์๋ณธ์ผ๋ก ๋๋๋ฆฌ๋ ค๋ฉด?
Spring์ `HtmlUtils.htmlUnescape()` ๋๋ Apache Commons์ `StringEscapeUtils.unescapeHtml4()`๋ฅผ ์ฌ์ฉํ์ธ์.
### Q4. ํน์ ํ๋๋ง ํํฐ๋งํ๊ฑฐ๋ ์ ์ธํ ์ ์๋์?
`XssRequestBodyAdvice`๋ฅผ ์์ ํ์ฌ ํน์ ํ๋๋ฅผ ์ ์ธํ๊ฑฐ๋, ์ด๋
ธํ
์ด์
๊ธฐ๋ฐ ํํฐ๋ง์ ๊ตฌํํ ์ ์์ต๋๋ค.
### Q5. REST API๊ฐ ์๋ ์ผ๋ฐ MVC์์๋ ์๋ํ๋์?
๋ค, Servlet Filter ๊ธฐ๋ฐ์ด๋ฏ๋ก Spring MVC, Thymeleaf ๋ฑ ๋ชจ๋ Spring ์น ์ ํ๋ฆฌ์ผ์ด์
์์ ์๋ํฉ๋๋ค.
## ๊ธฐ์ฌ
์ด ํ๋ก์ ํธ๋ ์คํ์์ค์
๋๋ค. ๋ฒ๊ทธ ๋ฆฌํฌํธ, ๊ธฐ๋ฅ ์ ์, Pull Request๋ฅผ ํ์ํฉ๋๋ค.
## ๋ผ์ด์ ์ค
์ด ํ๋ก์ ํธ๋ ํ์ต ๋ฐ ์ฐธ๊ณ ์ฉ์ผ๋ก ์ ๊ณต๋ฉ๋๋ค. ์์ ๋กญ๊ฒ ์ฌ์ฉํ๊ณ ์์ ํ ์ ์์ต๋๋ค.
## ์ฐธ๊ณ ์๋ฃ
- [๋ค์ด๋ฒ Lucy XSS Filter ๊ณต์ ๋ฌธ์](https://github.com/naver/lucy-xss-filter)
- [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
- [Spring Security XSS Protection](https://docs.spring.io/spring-security/reference/features/exploits/headers.html#headers-xss-protection)
---
**Made with โค๏ธ for secure Spring Boot applications**