값을 입력 받을 때 XSS와 SQL Injection 공격이 발생했고, 이를 어떻게 방어했는지 정리한 글입니다.
사용 스택은 Java8, Spring 4.X, maven 입니다.
사용 외부 API는 도로명 주소 API 입니다.
평소 애플리케이션 플로우는 다음과 같습니다.
회원가입 또는, 배송지 등을 입력할 때 도로명 주소 검색 API를 사용해 도로명 주소를 검색합니다.
couldn't connect to host
만 응답합니다.XSS란?
악의적인 사용자가 웹 애플리케이션에 스크립트를 삽입하여 다른 사용자의 세션을 탈취하거나 사용자를 속이는 공격입니다.
예시
방어 방법
Interceptor를 사용하여 요청 데이터에 안전하지 않은 HTML 요소를 제거하고 허용된 HTML 요소만 허용하는 방법을 수행합니다.
저는 AntiSamy를 사용해 XSS 공격을 방지했습니다.
AntiSamy는 Java8 이상에서 동작하며 다음과 같은 특징이 있습니다.
1. dependency 추가
<dependency>
<groupId>org.owasp.antisamy</groupId>
<artifactId>antisamy</artifactId>
<version>1.7.5</version>
</dependency>
2. 정책 파일 확인
다음과 같이 ExternalLibrary에 각 정책 파일이 생성되었습니다. 이중 한 개를 택해서 적절히 커스텀하여 사용하시면 됩니다.
정책 예시
<attribute name="media">
<regexp-list>
<regexp value="[a-z]+" />
</regexp-list>
<literal-list>
<literal value="screen" />
</literal-list>
</attribute>
<attribute name="media">
: HTML 태그를 뜻합니다.<regexp-list>
: 정규식패턴 목록을 나타냅니다.<regexp value="[a-z]+" />
: 정규식을 나타냅니다. (영어만 가능)<literal-list>
: 리터럴 목록을 나타냅니다.<literal value="screen" />
: 해당하는 리터럴 값만 해당 태그에 속성이 될 수 있습니다.정리하면 media 태그엔 속성이 소문자 영어로만 이루어져야 하며, screen이라는 속성값만 가질 수 있습니다.
3. 인터셉터 적용
public class XssInterceptor implements HandlerInterceptor {
//정책 파일을 가져옵니다.
private static final ClassPathResource ANTISAMY_POLICY = new ClassPathResource("정책파일");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 입력된 검색어를 가져옵니다.
String input = request.getParameter("input");
//정책 파일로부터 정책 규칙을 생성합니다.
Policy policyInstance = Policy.getInstance(ANTISAMY_POLICY.getFile());
AntiSamy antiSamy = new AntiSamy(policyInstance);
//정책 파일을 기반으로 입력된 데이터를 스캔하고 정제합니다.
CleanResults cleanResults = antiSamy.scan(input);
//정제된 결과에서 발생한 오류가 있다면 필터링 합니다.
if (cleanResults.getNumberOfErrors() > 0) {
response.getWriter().write(" 검색할 수 없습니다.");
return false;
}
return true;
}
SQL Injection이란?
악의적인 사용자가 웹 애플리케이션의 입력 값을 통해 SQL 쿼리를 조작하여 데이터베이스에 대한 공격을 뜻합니다.
예시
방어 방법
Interceptor를 사용하여 요청 데이터를 검증하고 SQL 쿼리에 사용되는 예약어들을 제거합니다.
SQL Injection은 공격에 사용되는 예약어들을 필터링 하는 방식으로 막았습니다.
public class SqlInjectionInterceptor implements HandlerInterceptor {
//SQL 예약어 목록
private static final String[] SQL_ARRAY = {"OR", "SELECT", "INSERT", "DELETE", "UPDATE", "CREATE", "DROP", "EXEC",
"UNION", "FETCH", "DECLARE", "TRUNCATE"};
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 입력된 검색어를 가져옵니다.
String input = request.getParameter("input");
// 특수 문자 필터링
if (input.contains("%") || input.contains("=") || input.contains(">") || input.contains("<")) {
response.getWriter().write(" 검색할 수 없습니다.");
return false;
}
// SQL 예약어 필터링
for (String sql : SQL_ARRAY) {
if (input.toUpperCase().contains(sql)) {
response.getWriter().write("\"" + sql + "\"와(과) 같은 특정문자로 검색할 수 없습니다.");
return false;
}
}
return true;
}
제가 제시한 방법 외에도 Spring Security 등 여러 보안 모듈을 사용해서 공격을 방어할 수 있습니다.
보통 요청을 서버에서 처리하는 경우엔 XSS나 SQL Injection과 같은 공격에 대해 방어가 갖춰져있다고 생각합니다.
외부 API를 사용할 때 사용자의 요청을 그대로 전달하게 되면 저와 같은 어려움을 겪으실 수 있습니다. 따라서, 항상 요청을 검증하고 요청 내용을 방어적으로 사용하는 것이 바람직하다고 생각합니다.
https://business.juso.go.kr/addrlink/openApi/apiExprn.do
https://www.baeldung.com/spring-prevent-xss
https://forgiveall.tistory.com/604