현대 웹 환경에서는 무수한 편리한 기능과 양질의 컨텐츠를 제공하고 있다.

하지만 그런만큼 사용자의 개인정보 또한 중요한 역할을 하며 중요성이 커지고 있다.

이러한 개인정보와 보안 위협에 대해 안전한 웹 사용을 위해 방지가 필수적이다.

 

의도치 않는 어택커에 대한 공격으로 대표적인 것 XSSCSRF가 있다.

 

 

XSS

Cross Site Script 의 줄임말이지만 웹사이트 스타일시트 언어인 css와 겹쳐서 XSS이라고 부른다.

Javascript 등 스크립트를 실행할 수 있는 코드를 삽입하여 다른 사용자 등에게 공격자가 의도한 스크립트를 실행하게 하는 공격 방법이다.

주로 JavaScript를 이용해서 사용자의 쿠키 탈취, 세션 탈취, 피싱, 페이지 변조 등에 사용한다.

 

이러한 XSS에는 3가지 유형이 있다.

 

 

1. Reflected XSS

 

악성 스크립트가 웹 애플리케이션에서 피해자의 브라우저로 반사될 때 발생
공격자는 보통 스크립트가 포함된 링크를 클릭하게 하여 공격자가 원하는 사이트(피싱사이트 등)에 접근하게 하거나 세션 값을 유출 시키는 행위

 

-공격자가 URL에 스크립트를 삽입해 피해자에게 보냄
서버는 검증 없이 요청값을 그대로 응답에 반영한다 --> 악성 스크립트에 의한 피해발생

 

사용자가 검색창에 '고양이'를 검색하여 https://example.com/search?q=고양이 이렇게 페이지이동이 일어나는데
정상적인 결과라면 검색어 창에 '고양이'가 들어가고 고양이에 대한 정보를 보여줄 것이다.
하지만  https://example.com/search?q=<script>alert('XSS')</script>
이러한 악성 스크립트 방식으로 파라미터를 변조하게 보내게 되면 서버는 검증없이 검색어: <script>alert('XSS')</script> 이런 응답을 보내게 된다.
결국 자동으로 브라우저가 스크립트를 실행해서 팝업이 뜬다.

 

<예제 서버 코드 SpringBoot>

 

@Controller
@RequestMapping("/xss")
public class XSSController {

    //http://localhost:8070/xss/search?keyword=%3Cscript%3Ealert(%27XSS%27)%3C/script%3E

    @GetMapping("/search")
    public String search(@RequestParam(required = false) String keyword, Model model) {
        model.addAttribute("keyword", keyword);
        return "search";
    }


}

 

<예제 search.html 페이지>

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>XSS 검색</title>
</head>
<body>
<h2>검색 결과</h2>

<p>당신의 검색어는: <span th:utext="${keyword}"></span></p>
<!-- 이거는 타임리프에서 자동으로 이스케이프 처리해줘 의도적으로 깨드려줌 -->
<form method="get" action="/xss/search">
  <input type="text" name="keyword" placeholder="검색어 입력" />
  <button type="submit">검색</button>
</form>
</body>
</html>

 

<결과>

 

해당 방식에 대한 방어책으로는

 

서버에서 출력 시 HTML escape
사용자 입력 저장 전에 스크립트 제거 또는 이스케이프
&, < , > 등을 이스케이프 처리를 해준다. (&lt;, &gt;)

 

 

 

2. Stored XSS

 

저장형(Stored) 크로스 사이트 스크립트 공격은 공격자가 악의적인 스크립트를 삽입하여 서버에 저장되는 방식을 의미한다.

공격자가 악성 스크립트를 DB나 게시판 등에 저장하여 다른 사용자가 게시판 같은데서 열었다가 스크립트가 실행된다.
게시판 내용으로 <script>alert('너 해킹당함')</script> 이렇게 작성하고 
사용자 A가 게시글을 열람했을때 악성 스크립트가 실행되어 alert창이 뜨게 된다.

 

이 방식은 단발성도 아니고 피해 범위가 광범위하다.

 

<예제 서버 코드 SpringBoot>

 

@Controller
@RequestMapping("/comments")
public class CommentController {

    private final List<String> comments = new ArrayList<>();

    @GetMapping
    public String getComments(Model model) {
        comments.clear(); // 혹시나 이전 댓글 제거
        comments.add("asdadsdsadsadas<script>alert('Stored XSS!')</script>"); // XSS 시도
        model.addAttribute("comments", comments);
        return "comments";
    }

}

 

이 방식은 예제를 구현하는데 DB연결까지 가는것은 과하게 생각해서 그냥 comments 컬렉션에 값을 넣어서 임의로 동작하게 하였다.

 

<예제 comments.html 페이지>

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>댓글 목록</title>
</head>
<body>
<h2>💬 댓글</h2>

<form method="post" action="/comments">
    <input type="text" name="comment" placeholder="댓글을 입력하세요" style="width: 300px;" />
    <button type="submit">등록</button>
</form>

<hr/>

<ul>
    <li th:each="c : ${comments}">
        <span th:utext="${c}">댓글내용</span><!-- 기본적으로 escape 처리됨 -->
    </li>
</ul>

</body>
</html>

 

* th:utext 로 처리해주지 않으면 타임리프에서 알아서 이스케이프 처리를 해준다. (좋은 세상이다)

 

<결과 사진>

 

 

이방식에 대한 방어책으로 주로 DB에 저장되기 전에 스크립트를 제거하는 등 필터링 작업을 거친다.

 

3. DOM XSS

 

이 방식은 서버와 상관없이 클라이언트 코드에서 발생한다.

보통 클릭을 하였을때 페이지 이동 및 URL 변경이 이루어질때 주로 발생한다.
서버를 거치지않고, 스크립트가 동적으로 DOM을 조작하면서 발생한다.

 

<예제 서버 코드 SpringBoot>

@Controller
public class DomXSSController {

    @GetMapping("/dom-xss")
    public String domXssTest() {
        return "dom-xss"; // dom-xss.html 렌더링
    }
}

 

이 방식은 서버 코드는 매핑역할만 할뿐 딱히 간섭을 하지 않는다.

 

<예제 dom-xss.html 페이지>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h2>DOM Test</h2>
    <p id="output"></p>


    <!--
    http://localhost:63342/RealLastfoms3/src/main/resources/templates/dom-xss.html?keyword=%3Cimg%20src=x%20onerror=alert(%27DOM_XSS%27)%3E

    -->
    <script>
        //쿼리 파라미터 중 keyword 값을 가져옴
        const params = new URLSearchParams(window.location.search);
        const keyword = decodeURIComponent(params.get("keyword"));
        document.getElementById("output").innerHTML = "검색어: " + keyword;

    </script>
</body>
</html>

 

이 방식은 정상적으로 들어갔을때 XSS 공격 재현이 안되었다. 

아마도 프레임워크나 브라우저단에서 방지해주는 듯하다. 그래서 그냥 html 자체로 올려서 테스트를 해봤다.

 

 

 

이 방식은 보통 innerHTML을 잘못사용 하여서 발생한다.

innerHTML 대신 textContent를 사용한다.

 

 

 

전체적으로 모든 방식에 대해 적절한 CSP 정책을 적용하거나, JS 필터링 라이브러리를 사용한다.

* CSP (Content Security Policy) : CSS를 막기 위해 도입된 브라우저에게 "이 웹사이트에서는 어떤 콘텐츠를 어디서 불러올 수 있는지"를 알려주는 보안 정책

 

 

CSRF(Cross Site Request Forgery, 크로스 사이트 요청 위조)

웹 보안 취약점 중 하나로, 사용자가 인증된 상태인 것을 악용하여 
악성 사용자에 의한 해킹 요청을 보내도록 유도하는 것이다.

 

⬇️ CSRF에 대한 예제 시나리오

1. 사용자가 로그인한 상태로 어떤 은행 사이트에 접속 중임.

2. 공격자가 사용자 를 속여서 악성 사이트에 접속하게 함.

3. 그 악성 사이트가 <img src="https://bank.com/transfer?to=attacker&amount=1000"> 이런 요청을 자동으로 날림.

4. 쿠키 기반 인증이기 때문에, 자동으로 로그인된 세션으로 요청이 날아감.

5. 서버는 그 요청이 사용자의 정상 요청인지 모름 → 돈이 이체됨.

 

주로 이러한 문제는 서버가 악성 사용자인지 정상 사용자인지 판단하지 못해서 발생한다.
얘는 XSS와는 다르게 정상 요청을 위조하는 것이고 XSS는 악성 스크립트를 삽입하는 것이다. (다른 개념)

 

- 방어 방법

 

1. 중요한 요청은 POST 요청으로 변경하여 CSRF 토큰을 사용한다.

핵심은 CSRF 공격은 쿠키 기반 인증만으로는 서버가 진짜 사용자인지 확인할 수 없기 때문에 CSRF 토큰을 통해 검증을 이룬다.

 

CSRF(Cross-Site Request Forgery) 공격을 방지하기 위해,
서버가 클라이언트에게 **랜덤한 고유값(토큰)**을 발급하고,
요청할 때마다 이 값을 같이 보내게 해서 요청의 정당성을 검증하는 방식.
즉, 내가 발급한 페이지에서 온 안전한 요청이 맞는가를 확인하는 것.

 

클라이언트는 <input type="hidden" name="_csrf" value="AbC123Xyz!!">
이러한 코드를 서버 요청시에 포함시켜서 요청을 한다. 이 토큰은 서버가 생성한 임의값으로 사용자의 세션/쿠키와 매칭되어 있다.
그 후 서버는 요청 안에 있는 csrf 토큰과 서버가 발급해둔 토큰 값을 비교하여 요청을 허가/불허가 한다.

 

Spring Security를 사용하면 form제출시에는 자동적용이 되고 Ajax같은 요청시에는 수동으로 넣어줘야 한다.

 

2. SameSite 쿠키 설정을 통하여 외부 사이트에서는 쿠키를 자동 전송하지 못하게 한다.

 

 

 

- 나는 한때 이런 생각을 했다. "csrf토큰을 탈취헤서 사용하면 되지 않냐?"

현실적으로 많이 힘든 이유가 있었다.

 

브라우저는 다른 도메인(출처)의 JS 코드가 현재 페이지의 DOM이나 쿠키, 로컬스토리지 등을 접근하지 못하게 막는다.


즉 어택커의 악성 페이지에서는 우리 사이트의 CSRF 토큰 값에 접근할 수 없다.


또한 CSRF 토큰은 서버에서 세션이나 사용자 쿠키랑 매핑해서 관리한다.


즉, 토큰 값을 복사하여 사용해도 세션이 다르면 거부된다.

+ Recent posts