XSS란?
악의적인 사용자가 공격하려는 사이트에 스크립트를 넣는 기법으로, 이를 통해 쿠키나 세션 등 민감한 정보를 탈취할 수 있다.
공격 과정
- 악의적인 사용자가 사이트에 악성 스크립트를 저장
- 일반 사용자가 웹 사이트에 접속
- 스크립트 코드가 실행되고, 사용자의 정보가 악의적인 사용자에게 전달됨
1. Reflected XSS(반사형 XSS)
Reflected XSS는 악의적인 사용자가 악성 스크립트가 담긴 URL을 만들어 일반 사용자에게 전달하는 경우 발생하고, URL 주소 뒤에 붙은 쿼리에 악성 스크립트를 작성하여 전달한다.
공격 시나리오
- 악성 스크립트가 담신 URL을 일반 사용자에게 전달
- 일반 사용자가 URL을 클릭, 일반 사용자 브라우저에서 보안이 취약한 사이트로 요청을 전달
- 서버에서 요청에 담긴 파라미터 값을 검증, 인코딩 없이 페이지에 넣어 응답
- 일반 사용자의 브라우저가 서버에서 보낸 페이지를 그대로 해석하면서 파라미터에 있던 악성 코드가 실행됨
- 악성 스크립트를 통해 사용자 정보를 악의적인 사용자에게 전달
브라우저는 서버가 반환한 HTML/JS를 그대로 해석하기 때문에, 사용자가 보낸 값을 서버가 HTML 문서 안에 그대로 넣어주게 되면 그 값이 스크립트로 해석되어 실행될 수 있는데, 이런경우 공격에 매우 취약해진다.
예를 들어 검색 페이지가 있는데 사용자가 검색 값으로 입력한 값이 검증, 인코딩 없이 HTML 페이지에 그대로 넣어주게 되면 반사형 XSS가 발생하기 아주 좋은 환경이 된다.
2.Stored XSS(저장형 XSS)
Stored XSS는 악의적인 사용자가 보안이 취약한 서버에 악성 스크립트를 저장함으로써 발생한다.
서버에서 제공하는 게시판, 사용자 프로필등의 기능에 악의적으로 동작하는 스크립트를 그대로 저장한 후 사용자의 브라우저로 스크립트가 전달되어 문제가 발생한다.
공격 시나리오
- 악의적인 사용자가 보안이 취약한 사이트의 게시판에 악성 스크립트를 올림
- 일반 사용자가 악의적인 사용자가 작성한 게시글을 읽으면, 서버로부터 악성 스크립트가 담긴 게시물을 응답받음
- 일반 사용자 브라우저에서 응답 메시지를 실행하면 악성 스크립트가 실행됨
- 악성 스크립트를 통해 사용자 정보가 악의적인 사용자에게 넘어감
XSS 공격 방어 전략
악성 스크립트가 사용자의 페이지로 응답값으로 넘어와서 실행되는 것을 막기 위해서 상황에 맞는 방어 전략을 프로그램에 적용시켜야 한다.
1. 입력값 제한
<, >, ", ', `, &, /, = 같은 문자들이 악성 스크립트에 포함되기 때문에 이러한 문자를 입력하는 것을 사전에 차단하고, 한글/영문/숫자/공백 만 허용하는 방식으로 위험한 문자가 들어오지 않도록 하는 방법이다.
2. 입력값 치환
악성 스크립트에 포함될 수 있는 특수 문자를 HTML Entity로 치환해서 반환하게 되면 스크립트가 실행되는 것 자체를 막을 수 있기 때문에
특정 문자 HTML Entity로 변환하여 XSS 실행 차단
Spring Boot에서 응답을 직렬화하는 시점에서 HTML Entity로 치환하여 반환하도록 하는 코드를 적용하여 XSS 방어하도록 구현 해봤다.
HTML Entity로 치환하기 위해서 commons-text 라이브러리를 프로젝트에 추가해줬다.
// HTML escape
implementation 'org.apache.commons:commons-text:1.12.0'
JSON 응답으로 직렬화 할때 특정 문자(<, >, ", ', & …)를 HTML 엔티티로 변환하기 위한 HTML Escape 설정 클래스를 생성해줬다.
public class HTMLCharacterEscapes extends CharacterEscapes {
private final int[] asciiEscapes; //Escape 테이블
public HTMLCharacterEscapes() {
asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();
//JSON 직렬화 과정에서 해당 문자들이 있으면 Escape 로직을 적용
asciiEscapes['<'] = ESCAPE_CUSTOM;
asciiEscapes['>'] = ESCAPE_CUSTOM;
asciiEscapes['&'] = ESCAPE_CUSTOM;
asciiEscapes['"'] = ESCAPE_CUSTOM;
asciiEscapes['\''] = ESCAPE_CUSTOM;
asciiEscapes['('] = ESCAPE_CUSTOM;
asciiEscapes[')'] = ESCAPE_CUSTOM;
asciiEscapes['#'] = ESCAPE_CUSTOM;
}
@Override public int[] getEscapeCodesForAscii() { return asciiEscapes; }
@Override
public SerializableString getEscapeSequence(int ch) {
return new SerializedString(
StringEscapeUtils.escapeHtml4(Character.toString((char) ch))
);
}
}
JSON 응답을 직렬화할 때 HTML 특수 문자가 안전하게 Escape되도록 하기 위해, ObjectMapper를 커스터마이징하여 om.getFactory().setCharacterEscapes(new HTMLCharacterEscapes())로 위에서 정의한 HTMLCharacterEscapes를 등록한다.
이렇게 하면 응답 JSON에서 <, >, & 같은 문자가 자동으로 HTML 엔티티로 변환되어 브라우저에서 실행되지 않고 텍스트로만 출력된다.
@Configuration
public class JacksonConfig {
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper om = new ObjectMapper();
om.getFactory().setCharacterEscapes(new HTMLCharacterEscapes());
return om;
}
}
테스트 결과 JSON 형식의 요청 데이터에 HTML 특수 문자가 들어가 있으면 HTML Entity로 Esacape된 응답이 반환 되는 것을 확인할 수 있었다.
'BackEnd' 카테고리의 다른 글
Thread Pool과 DB Connection Pool의 상관 관계 (0) | 2025.09.30 |
---|