Christmas Pikachu XSS 예방
개발일지/스프링

XSS 예방

ZI_CO 2022. 12. 19.

XSS(크로스 사이트 스크립트) 란?

  • 검증되지 않은 입력 값으로 인해 사용자의 웹 브라우저에서 의도하지 않은 악성 스크립트가 실행되는 취약점
  • 외부 입력이 동적 웹 페이지 생성에 사용될 경우, 전송된 동적 웹 페이지를 열람하는 접속자의 권한으로 부적절한 스크립트가 수행되는 취약점
  • 공격을 통해 사용자의 개인정보 및 쿠키정보 탈취, 악성코드 감염, 웹 페이지 변조 등이 발생
  • 공격 대상은 서버가 아니라, 클라이언트이다.

 

https://github.com/naver/lucy-xss-servlet-filter

 

GitHub - naver/lucy-xss-servlet-filter

Contribute to naver/lucy-xss-servlet-filter development by creating an account on GitHub.

github.com

 

 

 

의존성 설정

<dependency>
	<groupId>com.navercorp.lucy</groupId>
	<artifactId>lucy-xss-servlet</artifactId>
	<version>2.0.0</version>
</dependency>

 

 

 

lucy-xss-servlet-filter-rule.xml

<?xml version="1.0" encoding="UTF-8"?>

<config xmlns="http://www.navercorp.com/lucy-xss-servlet">
    <defenders>
        <!-- XssPreventer 등록 -->
        <defender>
            <name>xssPreventerDefender</name>
            <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssPreventerDefender</class>
        </defender>

        <!-- XssSaxFilter 등록 -->
        <defender>
            <name>xssSaxFilterDefender</name>
            <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssSaxFilterDefender</class>
            <init-param>
                <param-value>lucy-xss-superset-sax.xml</param-value>   <!-- lucy-xss-filter의 sax용 설정파일 -->
                <param-value>false</param-value>        <!-- 필터링된 코멘트를 남길지 여부, 성능 효율상 false 추천 -->
            </init-param>
        </defender>

        <!-- XssFilter 등록 -->
        <defender>
            <name>xssFilterDefender</name>
            <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssFilterDefender</class>
            <init-param>
                <param-value>lucy-xss.xml</param-value>    <!-- lucy-xss-filter의 dom용 설정파일 -->
                <param-value>false</param-value>         <!-- 필터링된 코멘트를 남길지 여부, 성능 효율상 false 추천 -->
            </init-param>
        </defender>
        
        <defender>
            <name>xssFilterDefender</name>
            <class>com.navercorp.lucy.security.xss.servletfilter.defender.XssFilterDefender</class>
            <init-param>
                <param-value>lucy-xss.xml</param-value>    <!-- lucy-xss-filter의 dom용 설정파일 -->
                <param-value>false</param-value>         <!-- 필터링된 코멘트를 남길지 여부, 성능 효율상 false 추천 -->
            </init-param>
        </defender>
        
        
    </defenders>

    <!-- default defender 선언, 필터링 시 지정한 defender가 없으면 여기 정의된 default defender를 사용해 필터링 한다. -->
    <default>
        <defender>xssPreventerDefender</defender>
    </default>

    <!-- global 필터링 룰 선언 -->
    <global>
        <!-- 모든 url에서 들어오는 globalParameter 파라메터는 필터링 되지 않으며
                또한 globalPrefixParameter1로 시작하는 파라메터도 필터링 되지 않는다.
                globalPrefixParameter2는 필터링 되며 globalPrefixParameter3은 필터링 되지 않지만
                더 정확한 표현이 가능하므로 globalPrefixParameter2, globalPrefixParameter3과 같은 불분명한 표현은 사용하지 않는 것이 좋다. -->
        <params>
            <param name="globalParameter" useDefender="false" />
            <param name="globalPrefixParameter1" usePrefix="true" useDefender="false" />
            <param name="globalPrefixParameter2" usePrefix="true" />
            <param name="globalPrefixParameter3" usePrefix="false" useDefender="false" />
        </params>
    </global>

    <!-- url 별 필터링 룰 선언 -->
    <url-rule-set>

        <!-- url disable이 true이면 지정한 url 내의 모든 파라메터는 필터링 되지 않는다. -->
        <!-- <url-rule>
            <url disable="true">/login/login/loginAjax</url>
        </url-rule> -->

    </url-rule-set>
</config>

 

 

 

lucy-xss-sax.xml

<?xml version="1.0" encoding="UTF-8"?>

<config xmlns="http://www.nhncorp.com/lucy-xss"
	extends="lucy-xss-default-sax.xml">

	<elementRule>
		<element name="body" disable="true" /> <!-- <BODY ONLOAD=alert("XSS")>, <BODY BACKGROUND="javascript:alert('XSS')"> -->
		<element name="embed" disable="true" />
		<element name="iframe" disable="true" /> <!-- <IFRAME SRC=”http://hacker-site.com/xss.html”> -->
		<element name="meta" disable="true" />
		<element name="object" disable="true" />
		<element name="script" disable="true" /> <!-- <SCRIPT> alert(“XSS”); </SCRIPT> -->
		<element name="style" disable="true" />
		<element name="link" disable="true" />
		<element name="base" disable="true" />
	</elementRule>
	
	<attributeRule>
		<attribute name="data" base64Decoding="true">
			<notAllowedPattern><![CDATA[(?i:s\\*c\\*r\\*i\\*p\\*t\\*:)]]></notAllowedPattern>
			<notAllowedPattern><![CDATA[(?i:d\\*a\\*t\\*a\\*:)]]></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[(?i:d\\*a\\*t\\*a\\*:)]]></notAllowedPattern>
			<notAllowedPattern><![CDATA[&[#\\%x]+[\da-fA-F][\da-fA-F]+]]></notAllowedPattern>
		</attribute>
		<attribute name="style">
			<notAllowedPattern><![CDATA[(?i:j\\*a\\*v\\*a\\*s\\*c\\*r\\*i\\*p\\*t\\*:)]]></notAllowedPattern>
			<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>
		<attribute name="href">
  		 <notAllowedPattern><![CDATA[(?i:j\\*a\\*v\\*a\\*s\\*c\\*r\\*i\\*p\\*t\\*:)]]></notAllowedPattern>
 		</attribute>
	</attributeRule>

</config>

 

 

네이버 lucy 한계 .. 필요하다면 직접 스크립트 단에서 막아야 한다. form 태그에서 가능하다. 하지만 요청 json 형태로 들어오면 불가능 !!!

 

 

 

board.js

let index = {
	init: function() {
		$("#btn--save").bind("click", () => {
			this.save();
		});
		
		$("#btn--delete").bind("click", () => {
			this.deleteById();
		});
		
		$("#btn--update").bind("click", () => {
			this.update();
		});
		
		$("#btn-reply-save").bind("click", () => {
			this.replySave();
		});
		
	}, 
	save : function() {
		let xCheckTitle = XSSCheck( $("#title").val());
		
		let data = {
			title: xCheckTitle, 
			content: $("#content").val()
		};
		
		// ajax 통신 요청 
		$.ajax({
			type:"POST", 
			url: "/api/board", 
			data: JSON.stringify(data),
			contentType : "application/json; charset=utf-8", 
			dataType: "json"
		}).done(function(data, textSatus, xhr) {
			if(data.status == "OK") {
				alert("글쓰기 성공");
				location.href = "/";
			}
		}).fail((error) => {
			console.log(error);
			alert(error.responseJSON.error);
			
		} );
		
	}, 
	deleteById: function () {
		let id = $("#board-id").val();
		// 통신 ---> ajax 
		$.ajax({
			type: "DELETE", 
			url: "/api/board/" + id
		}).done(function(data, textStatus, xhr) {
			if(data.status == "OK") {
				alert("글 삭제가 완료 되었습니다");
				location.href = "/";
			}
		}).fail(function(error) {
			alert("글 삭제하기에 실패 하였습니다");
		}); 
	}, 
	update: function() {
		// HTML 태그에 직접 속성을 정의할 수 있다. 규칙은 data-* 
		// data-* 값을 가지고 오기 위해서 Jquery --> (선택자).attr("data-[id]")
		let boardId = $("#board-id").attr("data-id");
		
		let data = {
			title : $("#title").val(), 
			content : $("#content").val()
		}
		
		$.ajax({
			type: "PUT", 
			url : "/api/board/" + boardId, 
			data : JSON.stringify(data), 
			contentType: "application/json; charset=utf-8",
			dataType: "json"	
		}).done(function(data, textStatus, xhr) {
			if(data.status) {
				alert("글 수정이 완료 되었습니다");
				location.href = "/";
			}
		}).fail(function(error) {
			console.log(error);
			alert("글 수정에 실패 하였습니다");
		}); 
		
	}, 
	replySave : function() {
		
		let replyData = {
			boardId: $("#board-id").val(),  // fk (board pk ) 
			content: $("#content").val() 
		};
		// ajax 통신 요청 
		$.ajax({
			type:"POST", 
			url: `/api/board/${replyData.boardId}/reply`,  
			data: JSON.stringify(replyData),
			contentType : "application/json; charset=utf-8", 
			dataType: "json"
		}).done(function(data, textSatus, xhr) {
			if(data.status == "OK") {
				alert("댓글 작성이 완료 되었습니다");
				location.href = `/board/${replyData.boardId}`;
			}
		}).fail((error) => {
			console.log(error);
			alert("댓글 작성에 실패 하였습니다");
		} );
	}, 
	replyDelete: function(boardId, replyId) {
		
		$.ajax({
			type: 'DELETE', 
			url: `/api/board/${boardId}/reply/${replyId}`,
			dataType: 'json' 
		}).done(function(resData) {
			if(resData.status == "OK") {
				alert("댓글 삭제 성공 하였습니다");
				location.href = `/board/${boardId}`	
			}
			
		}).fail(function(error) {
			alert("댓글 삭제 실패 ");
		});
		 
	}
}

function XSSCheck(str, level) {
    if (level == undefined || level == 0) {
        str = str.replace(/\<|\>|\"|\'|\%|\;|\(|\)|\&|\+|\-/g,"");
    } else if (level != undefined && level == 1) {
        str = str.replace(/\</g, "&lt;");
        str = str.replace(/\>/g, "&gt;");
    }
    return str;
}

index.init();

 

댓글