[Spring] XSS 방지하기 - OWASP AntiSamy Project
XSS (Cross Site Script) 방지하기 - OWASP AntiSamy Project
2017년 웹보안의 가장 큰 위협 10가지 !! (OWASP 발표)
- A1: Injection (인젝션)
- A2: Broken Authentication and session management (취약한 인증과 세션 관리)
- A3:
Cross-Site Scripting
(크로스 사이트 스크립팅) - A4: Broken Access Control (취약한 접근 제어)
- A5: Security Misconfiguration (보안설정오류)
- A6: Sensitive Data Exposure (민감 데이터 노출)
- A7: Insufficient Attack Protection (불충분한 공격 보호)
- A8: Cross-Site Request Forgery (크로스 사이트 요청 변조) (CSRF)
- A9: Using Components with Known vulnerabilities (알려진 취약점이 있는 컴포넌트 사용)
- A10: Unprotected APIs (보호되지 않은 API)
이 Post에서는 OWASP 라이브러리를 사용
하여 A3에 해당하는 XSS를 방어하기 위한 방법
을 적었다.
1. XSS
1-1. XSS란?
웹페이지에서 사용자 입력값을 악용
하여 코드를 심어 악의적인 코드를 수행하도록
하는 공격이다.
쿠키정보 탈취, 악성코드 감염, 웹 페이지 변조 등의 공격을 수행할 수 있다.
1-2. 취약점
HTML을 자유롭게 저장하고 보여주는 기능을 만들었다면, XSS 보안을 고려해야한다.
누군가 악의적인 코드를 심어 해를 가할 수 있기 때문이다.
대체로 HTML/JavaScript
를 사용하도록 허용할 경우 발생할 것이다.
예1)
<script>alert("...");</script>
예2)
<a href="javascript:alert("...");" >hello?</a>
1-3. 어떻게 방어하지?
보통은 사용자 입력값을 직접 정규식등을 사용해서 검출해내야 할 것이다.
하지만, 우리가 보안전문가가 아닌 이상 모든 경우의 수를 다 알아내고 코딩하기엔 무리가 있다.
1-4. "도와줘요! OWASP !"
OWASP(The Open Web Application Security Project)
는 오픈소스 웹 애플리케이션 보안 프로젝트
이다. 주로 웹에 관한 정보노출, 악성 파일 및 스크립트, 보안 취약점 등을 연구한다. 전문가 집단에서 만든 라이브러리를 사용하여 보안취약점을 방어해보자!
2. Dependency
만들어 공개해준 라이브러리를 감사히 활용해보자.
Gradle
build.gradle
compile 'org.owasp.antisamy:antisamy:1.5.10'
Maven (공식사이트에는 Maven으로 다음과 같이 문서화되어있다.)
pom.xml
<dependency> <groupId>org.owasp.antisamy</groupId> <projectId>antisamy</projectId> </dependency>
3. 정책 선택
- 라이브러리(의존성파일)가 잘 연결이 되었다면, 어느 정책파일을 적용할 것인지를 하나 정해야한다.
- 설명은 https://owasp.org/www-project-antisamy/ 의 Stage 2 - Choosing a base policy file 를 참조한다.
- 보안 전문가가 아닌이상 다 꼼꼼히 알고 따지고 할 수 없을 것으로 보인다.
- 대충! 일단
antisamy-myspace.xml
를 적용하기로 했다. 그게 어딨는데?- 의존성파일로 연결된
antisamy-1.5.10.jar
파일 안에는 다음 정책파일들이 존재한다는 것을 확인. (IDE의 도움을 받아서 확인)- antisamy.xml
- antisamy-anythinggoes.xml
- antisamy-ebay.xml
- antisamy-myspace.xml
- antisamy-slashdot.xml
- antisamy-tinymce.xml
- jar 파일 안에 있는 해당 xml파일을 읽을 수 있는 환경이라면 오케이! 하지만,
- 그렇지 않다면 파일을 복사하여 원하는 곳으로 옴겨서 사용하자
- 의존성파일로 연결된
4. 구현
간단한 예제
import org.owasp.validator.html.*; String someHTML = "<script> alert(0); </script> how are you?" File policyFile = new File("/path/to/policy/antisamy-something.xml") Policy policy = Policy.getInstance( policyFile ); AntiSamy as = new AntiSamy(policy); CleanResults cr = as.scan(someHTML); String xssRemovedHTML = cr.getCleanHTML();
설명
정책파일(.xml)로부터
Policy
를 객체를 생성한다.File policyFile = new File("/path/to/policy/antisamy-something.xml") Policy policy = Policy.getInstance( policyFile );
Policy
객체로AntiSamy
객체를 생성한다.AntiSamy as = new AntiSamy(policy);
String 형식의
html
문을 검사(scan
)한다. 검사결과인CleanResults
객체를 받는다.String someHTML = "<script> alert(0); </script> how are you?" CleanResults cr = as.scan(someHTML);
검사결과 다음 자료들을 얻을 수 있다.
검출된 XSS위험요소 수
int numberOfErrors = cr.getNumberOfErrors();
검출된 XSS위험요소에 대한 경고문들
List<String> errorMessages = cr.getErrorMessages();
검출된 XSS위험요소를 자동제거한 HTML문
String xssRemovedHTML = cr.getCleanHTML();
Spring 예제
Service
@Service public class ValidatorForXSS { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Value("${customizer.xss.policy.file}") String filePathForPolicy = null; String resourceClassPathForPolicy = "classpath:antisamy-something.xml"; Policy policy = null; @PostConstruct void before(){ logger.info("======================================== <XSS-POLICY>"); if (policy == null) policy = tryLoadPolicyFromFileSystem(filePathForPolicy); if (policy == null) policy = tryLoadPolicyFromResources(resourceClassPathForPolicy); if (policy == null) logger.warn("[XSS-POLICY] Unfortunately Failed to load XSS Policy File."); logger.info("======================================== </XSS-POLICY>"); } private Policy tryLoadPolicyFromResources(String classPath){ if (classPath == null || classPath.isEmpty()) return null; Policy policy = null; try{ ResourceLoader resourceLoader = new DefaultResourceLoader(); InputStream policyInputStream = resourceLoader.getResource(classPath).getInputStream(); policy = Policy.getInstance(policyInputStream); }catch(IOException ioe){ logger.error("[XSS-POLICY] !CAUTION! Failed to load application resources - " +classPath, ioe); }catch(PolicyException pe){ logger.error("[XSS-POLICY] !CAUTION! Failed to load wrong file. - " +classPath, pe); } if (policy != null) logger.info("[XSS-POLICY] Complete load policy from application resources - " +classPath); return policy; } private Policy tryLoadPolicyFromFileSystem(String filePath){ if (filePath == null || filePath.isEmpty()) return null; Policy policy = null; try{ File policyFile = new File(filePath); policy = Policy.getInstance(policyFile); }catch(Exception e){ logger.error("[XSS-POLICY] !CAUTION! Failed to load policy file from file path you defined - " +filePath, e); } if (policy != null) logger.info("[XSS-POLICY] Complete load policy from file system - " +filePath); return policy; } public boolean validateXSS(String html){ CleanResults cr = analysisHTML(html); if (cr != null) return cr.getNumberOfErrors() == 0; return true; } public List<String> getXSSErrorMessages(String html){ CleanResults cr = analysisHTML(html); if (cr != null) return cr.getErrorMessages(); return Collections.emptyList(); } public CleanResults analysisHTML(String html) { if (policy == null) return null; if (html == null || html.isEmpty()) return null; CleanResults cr = null; try{ AntiSamy antiSamy = new AntiSamy(policy); cr = antiSamy.scan(html); }catch(Exception e){ logger.error("[XSS-POLICY] Error ocurred during scanning html.", e); } return cr; } }
Main
@Service class SomeServiceImpl extends SomeService { @Autowired private ValidatorForXSS validatorForXSS; public void doSome(String html){ if (validatorForXSS.validateXSS(html)){ throw new Exception("No! XSS!"); } } }
5. Reference - 참조
OWASP AntiSamy Project: https://wiki.owasp.org/index.php/Category:OWASP_AntiSamy_Project#tab=How_do_I_get_started_3F
OWASP AntiSamy: https://owasp.org/www-project-antisamy/
OWASP Top 10: https://m.blog.naver.com/PostView.nhn?blogId=pentamkt&logNo=221130373787