XSS
- 쿠키 정보를 빼내서 좀비 피시로 만드는 것
- 공격자가 악의적인 의도로 script(자바 스크립트)를 작성해서 정보를 빼내는것
쿠키 - 사용자 정보를 클라이언트 pc에 저장해 놓는것 (ex. 자동 로그인)
1. Reflective XSS
공격자가 악성 스크립트가 포함된 URL을 클라이언트에게 노출시켜 클릭하도록 유도하여,
쿠키 정보를 탈취하거나 피싱 사이트, 불법 광고 사이트로 이동하게 한다.
2. Stored XSS
악성 스크립트를 DB에 저장하여 해당 DB 정보를 이용하는 애플리케이션을 통해,
시스템을 사용하는 모든 사용자들이 해당 스크립트를 실행하게 함으로써
사용자의 쿠키 정보를 탈취하거나 피싱사이트, 불법 광고 사이트로 이동하게 한다.
3. DOM XSS
DOM(Document Object Model)은 HTML, XML을 다루기 위한 프로그래밍 API로,
AJAX 프로그램에서 사용되는 자바스크립트를 이용하여 브라우저에게 수신된 데이터를 다시 잘라서
Document에 Write 하는 작업을 수행하는 경우 XSS 공격이 가능하게 한다. )
공통점
3개 다 java script를 이용하기 때문에 java script 만 잘 사용하면 막을 수 있음.
Solution
Lucy-XSS-FILTER 사용.
Lucy-XSS-FILTER 사용 방법
1. pom.xml에 dependency 추가
1
2
3
4
5 |
<dependency>
<groupId>com.navercorp.lucy</groupId>
<artifactId>lucy-xss</artifactId>
<version>1.6.3</version>
</dependency> |
cs |
2. src/main/resources -> File로 lucy-xss.xml 파일 생성
1
2 |
<config xmlns="http://www.nhncorp.com/lucy-xss" extends = "lucy-xss-superset.xml"></config>
|
cs |
3. src/main/resources -> File로 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 |
Reflective XSS, Stoard XSS, Dom XSS 막기
src/main/java-> XSSController.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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 |
package kr.co.hucloud.security.code.example.attack.xss.web;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import kr.co.hucloud.security.code.example.board.service.BoardService;
import kr.co.hucloud.security.code.example.board.vo.BoardListVO;
import kr.co.hucloud.security.code.example.common.util.SendMessage;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.nhncorp.lucy.security.xss.XssFilter;
@Controller
public class XSSController {
private BoardService boardService;
XssFilter filter = XssFilter.getInstance("lucy-xss-superset.xml");
public void setBoardService(BoardService boardService) {
this.boardService = boardService;
}
@RequestMapping("/attack/xss")
public String xss() {
return "attack/xss/xss";
}
@RequestMapping("/attack/xss/attack1")
public ModelAndView attack1(HttpServletRequest request) {
ModelAndView view = new ModelAndView("attack/xss/xss");
String requestedString = request.getParameter("script");
view.addObject("result",filter.doFilter(requestedString));
//view.addObject("result", requestedString);
view.addObject("requestedString1", requestedString);
return view;
}
@RequestMapping("/attack/xss/attack2")
public ModelAndView attack2(HttpServletRequest request) {
ModelAndView view = new ModelAndView("attack/xss/xss");
String id = request.getParameter("script");
id = filter.doFilter(id);
BoardListVO result = boardService.getBoardById(id);
String content = result.getList().get(0).getContent();
content = filter.doFilter(content);
view.addObject("result", content);
view.addObject("requestedString2", id);
return view;
}
@RequestMapping("/attack/xss/attack3")
public void attack3(HttpServletRequest request, HttpServletResponse response) {
String requestedString = request.getParameter("script");
requestedString = filter.doFilter(requestedString);
SendMessage.send(response, "{ \"result\" : \""+requestedString+"\", \"requestedString3\" : \""+requestedString+"\"}");
}
}
|
cs |
src/main/webapp/view/attack/xss -> xss.jsp 수정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 |
<script type="text/javascript">
$(document).ready(function() {
$("#executeBtn").click(function() {
$.post("/HuCloud/attack/xss/attack3", $("#formDOM").serialize(), function(data) {
var jsonObj = $.parseJSON($.trim(data));
//document.write(jsonObj.result);
alert(jsonObj.result);
});
});
});
</script> |
cs |
CSRF 막기
Xss 막는 방법으로 할 수 있으나, 완전히 막지는 못해
로그인 할 때 토큰 발급을 통해 완전히 막을 수 있다.
Token 설정
MemberServiceImpl.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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 |
package kr.co.hucloud.security.code.example.member.service.impl;
import java.util.List;
import java.util.UUID;
import javax.servlet.http.HttpSession;
import org.mindrot.jbcrypt.BCrypt;
import kr.co.hucloud.security.code.example.common.Session;
import kr.co.hucloud.security.code.example.encrypto.password.dao.EncryptoPasswordDAO;
import kr.co.hucloud.security.code.example.member.dao.MemberDAO;
import kr.co.hucloud.security.code.example.member.service.MemberService;
import kr.co.hucloud.security.code.example.member.vo.LoginVO;
import kr.co.hucloud.security.code.example.member.vo.MemberRegistryVO;
import kr.co.hucloud.security.code.example.member.vo.MemberVO;
public class MemberServiceImpl implements MemberService {
private MemberDAO memberDAO;
private EncryptoPasswordDAO encryptoPasswordDAO;
public void setMemberDAO(MemberDAO memberDAO) {
this.memberDAO = memberDAO;
}
public void setEncryptoPasswordDAO(EncryptoPasswordDAO encryptoPasswordDAO) {
this.encryptoPasswordDAO = encryptoPasswordDAO;
}
@Override
public void addMember(MemberRegistryVO memberVO) {
memberDAO.addMember(memberVO);
}
@Override
public boolean login(HttpSession session, LoginVO loginVO) {
MemberVO memberVO = null;
if(encryptoPasswordDAO.isExistsSaltColumn()) {
String salt = memberDAO.getSaltById(loginVO.getId());
String hashedPassword = BCrypt.hashpw(loginVO.getPassword(), salt);
loginVO.setPassword(hashedPassword);
}
memberVO = memberDAO.login(loginVO);
if(memberVO != null) {
session.setAttribute(Session.MEMBER, memberVO);
// Token 설정
session.setAttribute("_TOKEN_", UUID.randomUUID().toString());
}
return memberVO != null;
}
@Override
public List<MemberVO> getUserInfo(String parameter) {
return memberDAO.getUserInfo(parameter);
}
}
|
cs |
모든 form에서 공용으로 hidden type으로 token을 사용할 수 있도록
TokenModelAndView.class 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
package kr.co.hucloud.security.code.example.common.util;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.ModelAndView;
public class TokenModelAndView extends ModelAndView{
public void setToken(HttpSession session){
String tokenString = session.getAttribute("_TOKEN_").toString();
String tokenTag = "<input type=\"hidden\" name=\"token\" value=\""+tokenString+"\" />";
super.addObject("token", tokenTag);
}
}
|
cs |
write.jsp 에서 토큰이 잘 나오는지 출력해보기
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 |
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>${initParam.TITLE}</title>
<link rel="stylesheet" href="/HuCloud/resources/css/menu.css" />
<link rel="stylesheet" href="/HuCloud/resources/css/common.css" />
<script type="text/javascript" src="/HuCloud/resources/js/jquery-1.11.2.min.js"></script>
<script type="text/javascript" src="/HuCloud/resources/js/menu.js"></script>
<script type="text/javascript" src="/HuCloud/resources/js/tip.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#writeBtn").click(function() {
if($("#subject").val() == "") {
alert("제목을 입력하세요!");
$("#subject").focus();
return;
}
$("#writeForm").submit();
});
$(".csrf").click(function() {
$("#content").val("<form name='autoSubmit' action='/HuCloud/board/writeArticle' method='post' enctype='multipart/form-data'>\n<input type='hidden' name='subject' value='Hello!?' />\n<input type='hidden' name='content' value='자동글쓰기 공격은 대표적인 CSRF 공격이죠!' /><input type='file' name='file' />\n<input type='submit' />\n</form>\n<script>document.autoSubmit.submit();</ script>");
var autoCreate = $("#content").val();
autoCreate = autoCreate.replace("</ scr", "</scr");
$("#content").val(autoCreate);
});
});
</script>
</head>
<body>
<c:import url="/common/loginTop" />
<div class="wrapper">
<div class="vNav" style="float:left;">
<ul>
<li class="tip csrf" title="클릭하세요.">CSRF</li>
<li class="tip" data-tip="WebShell Upload 방지, 외부에서 접근할 수 없는 경로에 업로드 및 파일 확장자 체크">File Upload</li>
<li class="tip" data-tip="외부에서 접근 불가능하도록 처리.">File Download</li>
</ul>
</div>
<div class="w-spacer"> </div>
<div class="content" style="float:left;">
<b style="font-size: 30px;">Write ${ token }</b>
<form id="writeForm" name="writeForm" method="post" action="/HuCloud/board/writeArticle" enctype="multipart/form-data">
<!-- c:out ~~ 을 사용하면 페이지에 출력되기 때문에 쓰지마라-->
${token}
<table>
<tr>
<td>
<input type="text" name="subject" id="subject" size="50" style="width:550px;" placeholder="제목을 입력하세요." />
</td>
</tr>
<tr>
<td>
<textarea name="content" rows="20" id="content" cols="49" style="width:550px;" placeholder="내용을 입력하세요."></textarea>
</td>
</tr>
<tr>
<td>
<input type="file" name="file" />
</td>
</tr>
<tr>
<td align="right">
<input type="button" id="writeBtn" value="Save" />
</td>
</tr>
</table>
</form>
</div>
<div class="clear"></div>
</div>
<c:import url="/common/bottom" />
</body>
</html> |
cs |
session(로그인 사용자)으로 넘어오는 token과 현재 token이 같은지 비교
CSRFInterceptor.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
49
50 |
package kr.co.hucloud.security.code.example.common.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
//session(로그인 사용자)으로 넘어오는 token과 현재 token이 같은지 비교
public class CSRFInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// form에서 넘어오는 데이터가 get인지 post인지 확인
// get이라면 할꺼 해라.
if(request.getMethod().toUpperCase().equals("GET")){
return true;
}
// post이면 확인해라.
else{
String token = request.getParameter("token");
// 현재 페이지를 요청한 이전 페이지 주소 가져오기.
//String referer = request.getHeader("Referer");
String referer = "/HuCloud/member";
// 토큰이 없다면 이전 페이지로 가라.
if(token == null || token.length()==0){
response.sendRedirect(referer);
return false;
}
// session token과 비교하는 로직
else{
HttpSession session = request.getSession();
String sessionToken = session.getAttribute("_TOKEN_").toString();
// session token과 같지 않다면 이전 페이지로 가라.
if(!token.equals(sessionToken)){
response.sendRedirect(referer);
return false;
}
}
}
return true;
}
}
|
cs |
dispatcherServlet.xml에 CSRFInterceptor 등록
1
2
3
4
5
6
7
8
9
10
11 |
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<!-- 로그인, 등록 전에는 체크하지 말아라 -->
<mvc:exclude-mapping path="/member/login"/>
<mvc:exclude-mapping path="/member/registry"/>
<!-- 로그아웃 안되면 추가. -->
<mvc:exclude-mapping path="/member/logout"/>
<bean id="csrfInterceptor" class="kr.co.hucloud.security.code.example.common.interceptor.CSRFInterceptor" />
</mvc:interceptor>
</mvc:interceptors> |
cs |
댓글에도 토큰 추가 detail.jsp
1
2
3
4
5
6 |
<form id="replyForm" name="replyForm" style="text-align:right;" method="post" action="<c:url value='/reply/write' />">
${token}
<input type="hidden" name="boardId" value="${article.id}" />
<textarea name="content" id="conent" rows="10" placeholder="댓글을 입력하세요." style="width:100%"></textarea>
<input type="button" id="replyWriteBtn" value="댓글 등록" />
</form> |
cs |
BoardController.java 에 detail 메소드 -> TokenModelAndView 로 바꾸기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
@RequestMapping("/board/article/{id}")
public TokenModelAndView detail(@PathVariable String id, HttpSession session) {
BoardListVO boardList = boardService.getBoardById(id);
//ModelAndView view = new ModelAndView("board/detail");
TokenModelAndView view = new TokenModelAndView();
view.setToken(session);
view.setViewName("board/detail");
if(boardList != null) {
view.addObject("article", (BoardVO)boardList.getList().get(0));
List<ReplyVO> replyList = replyService.getAllReplyByBoardId(id);
view.addObject("replyList", replyList);
}
return view;
} |
cs |
모든 폼에 토큰이 들어가야 된다.
'IT > Secure Coding' 카테고리의 다른 글
Encrypt Password (0) | 2015.04.23 |
---|---|
File Upload, File DownLoad (0) | 2015.04.23 |
안행부 시큐어코딩 가이드 (0) | 2015.04.22 |
OWASP(Open Web Application Security Project) (0) | 2015.04.22 |
CERT (Computer Emergent Response Team) (0) | 2015.04.22 |