Spring AOP를 이용한 XSS 공격 방지

2018. 5. 6. 22:39Web/Spring

Spring AOP를 이용한 Xss 공격 방지


lucy-xss를 이용하여 간단하게 게시글이나 댓글에 XSS공격을 방지하는 기능을 구현하였습니다. 그런데 보통 홈페이지는 게시판과 댓글 기능이 하나만 들어가있는 경우는 드뭅니다. 그말은 즉, xss관련 코드가 2개 이상이 중복되어 구현됩니다. 그래서 spring AOP를 이용하여 XSS 관련 코드를 하나의 클래스 파일에 구현해 게시물,댓글 등의 코드에 중복되어 구현되지 않게 하였습니다.






pom.xml



1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- lucy xss  -->
<dependency>
    <groupId>com.navercorp.lucy</groupId>
    <artifactId>lucy-xss</artifactId>
    <version>1.6.3</version> 
</dependency>
 
<!-- aop weaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.8</version>
</dependency>
cs




lucy-xss-superset.xml




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8"?>
 
<config xmlns="http://www.nhncorp.com/lucy-xss"
    extends="lucy-xss-default.xml">
 
    <elementRule>
        <element name="body" disable="true" />
        <element name="embed" disable="true" />
        <element name="iframe" disable="true" />
        <element name="meta" disable="true" />
        <element name="object" disable="true" />
        <element name="script" disable="true" />
        <element name="style" disable="true" />
    </elementRule>
    
    <attributeRule>
        <attribute name="data" base64Decoding="true">
            <notAllowedPattern><![CDATA[(?i:s\\*c\\*r\\*i\\*p\\*t)]]></notAllowedPattern>
            <notAllowedPattern><![CDATA[&[#\\%x]+[\da-fA-F][\da-fA-F]+]]></notAllowedPattern>
        </attribute>
        <attribute name="src" base64Decoding="true">
            <notAllowedPattern><![CDATA[(?i:s\\*c\\*r\\*i\\*p\\*t)]]></notAllowedPattern>
            <notAllowedPattern><![CDATA[&[#\\%x]+[\da-fA-F][\da-fA-F]+]]></notAllowedPattern>
        </attribute>
        <attribute name="style">
            <notAllowedPattern><![CDATA[(?i:e\\*x\\*p\\*r\\*e\\*s\\*s\\*i\\*o\\*n)]]></notAllowedPattern>
            <notAllowedPattern><![CDATA[&[#\\%x]+[\da-fA-F][\da-fA-F]+]]></notAllowedPattern>
        </attribute>
    </attributeRule>
 
</config>
cs

차단 할 댓글,게시글의 패턴을 설정하는 파일입니다. 파일 위치는 src/main/resources 디렉토리 밑에 넣어주면 됩니다.






BeforeAdvice.java



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Service
@Aspect
public class BeforeAdvice {
    private static final Logger logger = LoggerFactory.getLogger(BeforeAdvice.class);
    
    @Before("PointcutCommon.insertReplyPointCut()")
    public void beforeInsertReplyAdvice(JoinPoint jp) {
        logger.info("[beforeAdviceLog] : "+jp.getSignature().getName()+"()호출");
        Object[] obj=jp.getArgs();
        for(int i=0;i<obj.length;i++) {
            if(obj[i] instanceof ReplyDTO) {
                ReplyDTO rdto=(ReplyDTO)obj[i];
                String commentContent=rdto.getR_content();
                XssFilter xssFilter=XssFilter.getInstance("lucy-xss-superset.xml");
                String filterCommentContent=xssFilter.doFilter(commentContent);
                Pattern p=Pattern.compile("<!-- Not Allowed Tag Filtered -->");
                Matcher m=p.matcher(filterCommentContent);
                String xssResult="";
                if(m.find()){
                    xssResult="비정상적인 댓글입니다.";
                }else{
                    xssResult=commentContent;
                }
                if(!commentContent.equals(xssResult)) {
                    logger.info("[beforeAdviceLog] : "+commentContent+"에서 "+xssResult+"로 댓글 내용 변경");
                }
                rdto.setR_content(xssResult);
            }else if(obj[i] instanceof ReplyPhotoDTO) {
                ReplyPhotoDTO rpdto=(ReplyPhotoDTO)obj[i];
                String commentContent=rpdto.getR_content();
                XssFilter xssFilter=XssFilter.getInstance("lucy-xss-superset.xml");
                String filterCommentContent=xssFilter.doFilter(commentContent);
                Pattern p=Pattern.compile("<!-- Not Allowed Tag Filtered -->");
                Matcher m=p.matcher(filterCommentContent);
                String xssResult="";
                if(m.find()){
                    xssResult="비정상적인 댓글입니다.";
                }else{
                    xssResult=commentContent;
                }
                if(!commentContent.equals(xssResult)) {
                    logger.info("[beforeAdviceLog] : "+commentContent+"에서 "+xssResult+"로 댓글 내용 변경");
                }
                rpdto.setR_content(xssResult);
            }
        }
    }
}
cs


@Before 어노테이션을 적용하여 프록시가 중간에 과정을 가로채게 됩니다. (Pointcut설정은 insertBoard,insertReply 등 게시물,댓글의 삽입 메소드의 이름을 통일하여 모두 적용되게 합니다. pointcut 소스는 생략합니다. 필요하시면 댓글 남겨주세요!) 만약 댓글을 삽입하던 게시물을 삽입하던 pointcut에 걸리게 되면 모두 이 AOP 메소드가 먼저 실행되게 됩니다.(@Before 어노테이션에 의해서 진짜 메소드가 실행되기 전에 먼저 aop의 메소드가 실행되게 된다.)


그렇다면 어떻게 사용자가 작성한 content를 가져오게 되는가? 보통 게시물을 쓰거나 댓글을 쓰게 되면 DB에 삽입하는 메소드의 매개변수로 작성한 content가 들어오게 됩니다. 그 매개변수를 JoinPoint의 객체의 getArgs()라는 메소드로 메소드의 매개변수를 가져오게 됩니다. getArgs( )는 object 형태로 매개변수를 가져오게 되므로 반복문을 통해 instanceof 로 원하는 매개변수를 얻어와서 만약 xss공격이 포함된 content 면 @Before클래스의 메소드 내에서 해당 매개변수를 바꿔서 다시 비즈니스 로직을 실행하는 것입니다. ex)<script>alert('xss attack !')</script>라는 댓글을 작성하면 이 클래스에서 '비정상적인 댓글입니다." 라고 매개변수를 바꿔줍니다. 그러면 비즈니스 로직에서는 변경된 댓글을 이용해 DB에 삽입되게 됩니다.



<실행과정>


1. XssFilter xssFilter=XssFilter.getInstance("lucy-xss-superset.xml"); => (싱글톤 패턴이 적용된) XssFilter 객체를 가져옵니다. resources 밑에 xml파일                                                                                             을 이용

2. String filterCommentContent=xssFilter.doFilter(commentContent); => 사용자가 작성한 댓글 내용에 doFilter( )를 이용하여 XSS코드가 포함 되어있는지                                                                                               filtering 합니다. 여기에서 '<' 같은 태그문자들은 '&lt','&gt' 등으로 변환됩니다.                                                                                               여기서 filtering되면 해당 content내에 <!-- Not Allowed Tag Filtered-->라는 문                                                                                               자가 포함된 문자열로 바뀌게 됩니다.

3. Pattern p=Pattern.complie("<!-- Not Allowed Tag Filtered -->"); => 정규식 패턴식을 정의


4. Matcher m=p.matcher(filterCommentContent); => 정의한 정규식패턴이 들어가 있다면 m.find( )는 true를 리턴하게 됩니다.




마치며..


혹시라도 읽어 보시다가 틀린 내용이 있다면 지적해주세요.. 혹시 이번 구현코드가 필요하시다면 여기서 생략한 pointcut코드 비즈니스 로직 코드등 댓글로 남겨주시면 보내드리겠습니다~!