사이트 간 스크립팅으로 웹 사이트에서 관리자가 아닌 제3자가 악성 스크립트를 삽입하여 의도치않은 명령을 실행시키는 웹 취약점이다.
제 3자가 마음대로
javascript를 이용하여 내가 웹에서 로그인할 때 누른 키보드 정보를 axios등을 이용해서 제3자의 server로 전송할 수 도있다.
이로 인해 사용자는 다음과 같은 피해를 입을 수 있다.
크게 3가지로 분류가 가능하다.
공격자가 웹 서버에 악성 스크립트를 저장시켜 이후 다른 사용자가 해당 데이터를 조회함으로써 악성 스크립트를 실행시키는 방식
공격자가 악성 스크립트를 서버에 저장시키지 않고 사용자에게 직접 절달하는 방식
다음과 같은 링크로 공격자가 보안이 취약한 사이트를 찾아 해당 사이트의 링크로
이메일, sns등을 통해 사용자의 접근을 유도할 수 있다.
www.대충보안이취약한사이트.com?대충화면그려주는변수=<script>alert('공격')</script>
악성 스크립트가 서버가 아닌 브라우저에서 실행되는 방식
spring boot에서 Stored Xss에 대한 보안 대책으로 간단한 WhiteList 필터링을 구현해보자
// spring boot web
implementation 'org.springframework.boot:spring-boot-starter-web'
// OWASP ESAPI
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
// Apache Commons Text
implementation 'org.apache.commons:commons-text:1.12.0'
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
RequestParam과 RequestBody에 대한 WhiteList를 구현하기 위한 샘플
@RestController
public class XssTestController {
@GetMapping("/xss")
public String getXss(@RequestParam("content") String content) {
return content;
}
@Data
public static class Xss {
private String content;
}
@PostMapping("/xss")
public String addXss(@RequestBody Xss xss) {
return xss.content;
}
}
앱을 실행해서 root 접근 후 각 버튼을 눌러보면 alert이 실행된다.
이제 버튼 하나씩 alert이 실행되지 않게 처리를 해보자.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="title">hello world</div>
<button onclick="getTest()">Get Test 버튼</button>
<button onclick="postTest()">Post Test 버튼</button>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
const getTest = () => {
axios.get("http://localhost:8080/xss?content="+"<img src='i' onerror=\"alert('test');\">")
.then(result => document.querySelector("#title").innerHTML = result.data);
};
const postTest = () => {
axios.post("http://localhost:8080/xss", {
"content": "<img src='i' onerror=\"alert('test');\">"
}).then((result) => document.querySelector("#title").innerHTML = result.data);
};
</script>
</body>
</html>
악성 스크립트를 필터링해줄 헬퍼
import org.apache.commons.text.StringEscapeUtils;
import org.owasp.html.PolicyFactory;
import org.owasp.html.Sanitizers;
public class XssHelper {
private static final PolicyFactory POLICY = Sanitizers.FORMATTING
.and(Sanitizers.STYLES)
.and(Sanitizers.LINKS)
.and(Sanitizers.IMAGES)
.and(Sanitizers.TABLES)
.and(Sanitizers.BLOCKS);
public static String sanitizeXSS(String value) {
if(value == null) {
return null;
}
String passingValue = StringEscapeUtils.unescapeHtml4(value);
return StringEscapeUtils.unescapeHtml4(POLICY.sanitize(passingValue));
}
}
RequestParam으로 전달되는 악성 스크립트를 걸러주는 필터
적용 후 다시 root페이지의 Get Test 버튼을 눌러보면 작동하지 않는 것을 볼 수 있다.
import io.martin.xsstest.helper.XssHelper;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class XssFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response);
}
@Override
public void destroy() {
}
private static class XSSRequestWrapper extends HttpServletRequestWrapper {
public XSSRequestWrapper(HttpServletRequest servletRequest) {
super(servletRequest);
}
@Override
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = XssHelper.sanitizeXSS(values[i]);
}
return encodedValues;
}
@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
return XssHelper.sanitizeXSS(value);
}
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
return XssHelper.sanitizeXSS(value);
}
}
}
RequestBody로 전달되는 문자열타입의 악성 스크스크립트를 걸러줄 역직렬화기
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import io.martin.xsstest.helper.XssHelper;
import java.io.IOException;
public class XssStringDeserializer extends StdDeserializer<String> {
public XssStringDeserializer() {
super(String.class);
}
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
return XssHelper.sanitizeXSS(jsonParser.getValueAsString());
}
}
역직렬화기를 ObjectMapper에 추가
import io.martin.xsstest.databind.XssStringDeserializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer customJsonDeserializer() {
return builder -> builder.deserializerByType(String.class, new XssStringDeserializer());
}
}
모두 적용했다면 root 페이지의 버튼을 눌러도 alert이 안 뜨는 것을 볼 수 있다.
이상 아주 간단한 WhiteList 필터링을 구현해봤다.