[Spring Security] ์น ๋ณด์ ๊ณต๊ฒฉ #1: CSRF๊ณต๊ฒฉ๊ณผ XSS ๊ณต๊ฒฉ + ๋์ ์ ๋ต
์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฐ์ํ ์ ์๋ 4๊ฐ์ง ์ฃผ์ ๋ณด์ ๊ณต๊ฒฉ
: CSRF ๊ณต๊ฒฉ, XSS ๊ณต๊ฒฉ, ์ธ์ ๊ณ ์ ๊ณต๊ฒฉ, JWT ํ์ทจ ๊ณต๊ฒฉ์ ๋ํด ์์๋ณด๊ณ ,
์ด๋ฅผ ๋ฐฉ์ดํ ์ ์๋ Spring Security ๋๋ ์ผ๋ฐ์ ์ธ ๋์ ์ ๋ต์ ์ดํด๋ณด๊ณ ์ ํฉ๋๋ค.
๊ธ์ด ๊ธธ์ด์ ธ์ ๋ ํธ์ ๋๋์ด ์๋๋ค.
๐ CSRF ๊ณต๊ฒฉ
CSRF๋ Cross-Stie Request Forgery์ ์ค์๋ง๋ก "์ฌ์ดํธ ๊ฐ ์์ฒญ ์์กฐ"๋ฅผ ์๋ฏธํฉ๋๋ค.
์ธํฐ๋ท ์ฌ์ฉ์๊ฐ ์์ ์ ์์ง์๋ ๋ฌด๊ดํ๊ฒ ๊ณต๊ฒฉ์๊ฐ ์๋ํ ํ์๋ฅผ ํน์ ํ ์น ์ฌ์ดํธ์ ์์ฒญํ๋๋ก ๋ง๋๋ ๊ณต๊ฒฉ์ ๋๋ค.
CSRF ๊ณต๊ฒฉ์ด ์ฑ๊ณตํ๋ ค๋ฉด 3๊ฐ์ง ์กฐ๊ฑด์ ๋ง์กฑํด์ผ ํฉ๋๋ค.
- ์ฌ์ฉ์๋ ์๋ฒ์ ๋ก๊ทธ์ธ๋์ด ์๋ ์ํ์ฌ์ผ ํฉ๋๋ค.
- ํด์ปค(๊ณต๊ฒฉ์)๊ฐ ์ฌ์ฉ์์ ์ธ์ ์ ๋ณด๋ฅผ ์ฟ ํค์์ ์ฝ์ ์ ์์ด์ผ ํฉ๋๋ค.
- ํด์ปค(๊ณต๊ฒฉ์) ๋ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๋ ๋ฐฉ๋ฒ(API, ํ์ํ ๋งค๊ฐ๋ณ์ )์ ๋ฏธ๋ฆฌ ์๊ณ ์์ด์ผ ํฉ๋๋ค.
์๋๋ฆฌ์ค๋ฅผ ๋ณผ๊น์? ๋ค์๊ณผ ๊ฐ์ ์ํฉ์ด ์ ์ ๋์ด ์๋ค๊ณ ํฉ์๋ค.
- ํผํด์๋ ์ํ ์ฌ์ดํธ(bank.com)์ ๋ก๊ทธ์ธํ ์ํ์ ๋๋ค.
- ์ํ ์ฌ์ฉ์๋ POST /transfer ์์ฒญ์ผ๋ก ์๋ฒ์ ์ก๊ธ์ ์์ฒญํ ์ ์์ต๋๋ค.
- ํด์ปค๋ ์ด๊ฒ์ ์๊ณ ํผํด์ ๊ณ์ ์์ ์์ ์ ๊ณ์ข๋ก ๋์ ๋นผ๋ด๊ณ ์ ํฉ๋๋ค.
๐ฅ ํด์ปค๋ ๋ค์ ๋ฐฉ๋ฒ์ผ๋ก ๊ณต๊ฒฉํ ์ ์์ต๋๋ค.
<html>
<body onload="document.forms[0].submit()">
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="toAccount" value="hacker-account" />
<input type="hidden" name="amount" value="1000000" />
</form>
</body>
</html>
- ํด์ปค๋ ์์ ์ ๋ธ๋ก๊ทธ๋ ์ ์ฑ ์ฌ์ดํธ์ ์์ ๊ฐ์ ์ฝ๋๋ฅผ ์จ๊ฒจ ๋ก๋๋ค.
- ํด์ปค๋ ์ด ์ฌ์ดํธ๋ฅผ ํผํด์์๊ฒ ์ด๋ฉ์ผ์ด๋ ๊ด๊ณ ๋ฑ์ ํตํด ์ ๋ฌํ๊ณ , ํผํด์๊ฐ ์ฌ์ดํธ์ ๋ฐฉ๋ฌธํ๊ฒ ๋ง๋ญ๋๋ค.
- ํผํด์๊ฐ bank.com์ ์ด๋ฏธ ๋ก๊ทธ์ธ ๋์ด ์๋ ์ํ์์ ํด์ปค ์ฌ์ดํธ์ ์ ์ํ๋ฉด ์ ํผ์ด ์๋์ผ๋ก ์ ์ถ๋๋ฉฐ bank.com์ผ๋ก ์์ฒญ์ด ๊ฐ๋๋ค.
- bank.com์ผ๋ก ํด์ปค์ ๊ณ์ข(hacker-account)๋ก 1000000์ ์ก๊ธํ๋ผ๋ POST /transfer ์์ฒญ์ด ์ ์ก๋ฉ๋๋ค. ์ด๋ ํผํด์์ ์ธ์
์ฟ ํค๊ฐ ์๋์ผ๋ก ํฌํจ๋ฉ๋๋ค.
- ์ ์ธ์
์ฟ ํค๊ฐ ์๋์ผ๋ก ํฌํจ๋ ๊น์? HTTP ์ฟ ํค๋ ํน์ ๋๋ฉ์ธ์ ๋ํด ์ ์ฅ๋๊ณ ,
๋ธ๋ผ์ฐ์ ๋ ์ฌ์ฉ์๊ฐ ํด๋น ๋๋ฉ์ธ์ผ๋ก ์์ฒญ์ ๋ณด๋ผ ๋ ์ฟ ํค์ ์ถ์ฒ์ ์๊ด ์์ด ๊ทธ ๋๋ฉ์ธ์ ์ค์ ๋ ์ฟ ํค๋ฅผ ์๋์ผ๋ก ์ฒจ๋ถํ๊ธฐ ๋๋ฌธ์ ๋๋ค. - bank.com์์ ๋ก๊ทธ์ธํด์ ์ฟ ํค๋ฅผ ๊ฐ์ง๊ณ ์๊ณ , bank.com์ผ๋ก ์ก๊ธ ์์ฒญ์ ๋ณด๋ด๊ฒ ๋๋ ๋ธ๋ผ์ฐ์ ๋ ์ด์ ์ bank.com์์ ๋ฐ์ ์ฟ ํค๋ฅผ ํจ๊ป ๋ณด๋ ๋๋ค.
- ์ ์ธ์
์ฟ ํค๊ฐ ์๋์ผ๋ก ํฌํจ๋ ๊น์? HTTP ์ฟ ํค๋ ํน์ ๋๋ฉ์ธ์ ๋ํด ์ ์ฅ๋๊ณ ,
- bank.com ์๋ฒ๋ ์์ฒญ์ ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ์ธ์ ์ฟ ํค๊ฐ ๋ด๊ฒจ์์ผ๋ ์ ํฉํ ์์ฒญ์ด๋ผ ํ๋จํ๊ณ , ์ก๊ธ์ ์ฒ๋ฆฌํฉ๋๋ค.
๐๋์ ์ ๋ต
โ CSRF ํ ํฐ ์ฌ์ฉ
Spring์์๋ Spring Security์์ CSRF ๋ณดํธ ์ค์ ์ ํ์ฌ ์ด ๊ณต๊ฒฉ์ ๋ฐฉ์ดํ ์ ์์ต๋๋ค.
์ด ์ค์ ์ ์ฌ์ฉ์ ์ธ์ ์ ์์์ ๊ฐ, CSRF ํ ํฐ์ ์ ์ฅํ์ฌ ๋ชจ๋ ์์ฒญ๋ง๋ค ํด๋น ๊ฐ์ ํฌํจํ์ฌ ์ ์ก๋๋๋ก ํฉ๋๋ค.
์๋ฒ์์ ์์ฒญ์ ๋ฐ์ ๋๋ง๋ค ์ธ์ ์ ์ ์ฅ๋ ๊ฐ๊ณผ ๋น๊ตํ์ฌ ์ฌ๋ฐ๋ฅธ ์์ฒญ์ธ์ง ๊ฒ์ฆํฉ๋๋ค.
Spring Security์ CSRF ๋ณดํธ ํ๋ฆ
- ์ฌ์ฉ์๋ก๋ถํฐ ์ฒซ ์์ฒญ์ด ๋ค์ด์ฌ ๋ CsrfFilter๊ฐ ๋์
- ๋๋คํ ๊ฐ์ CsrfToken์ ์์ฑํ์ฌ ์ธ์ ์ ์ ์ฅ
- ์ฌ์ฉ์์๊ฒ CsrfToken์ ๋ณด๋. ์๋ ๋ ๊ฐ์ด ์ ์ก๋จ
- ${_csrf.token} → HTML ํผ์ผ๋ก hidden ํ๋๋ก ํฌํจ
- javascript๋ฅผ ํตํด X-CSRF-TOKEN ํค๋๋ก ์ค์
- ์ดํ ์ฌ์ฉ์๊ฐ POST, PUT, DELETE ์์ฒญ์ ๋ณด๋ด๋ฉด CsrfFilter๊ฐ ํ ํฐ์ ์ธ์
์ ์ ์ฅ๋ ๊ฐ๊ณผ ํ์ธ
- GET ๋ฑ์ ๋ค๋ฅธ ๋ฉ์๋๋ ์์ ํ ๋ฉ์๋๋ก ๊ฐ์ฃผํ์ฌ ๊ฒ์ฆ ์ ์ธ
- ๋ถ์ผ์น ์ 403 Forbidden ์๋ต
โ Referer Check(๋ฆฌํผ๋ฌ ์ฒดํฌ)
Referer๋ HTTP ์์ฒญ ํค๋ ์ค ํ๋์ ๋๋ค. ์ฌ์ฉ์ฆ๊ฐ ์ด๋ค ์ฌ์ดํธ์์ ์ด ์์ฒญ์ ๋ณด๋๋์ง ๋ํ๋ ๋๋ค.
์๋ฅผ ๋ค์ด ์ฌ์ฉ์๊ฐ ํด์ปค์ ๊ณต๊ฒฉ ์ฌ์ดํธ์ธ attack.com์์ ์ํ ์ฌ์ดํธ์ธ bank.com์ผ๋ก ์์ฒญ์ ๋ณด๋๋ค๋ฉด
์์ฒญ์ ๋ค์๊ณผ ๊ฐ์ด ๊ฐ ์ ์์ต๋๋ค.
POST /transfer
Host: http://bank.com
Referer: http://attack.com
์๋ฒ์์๋ Rererer ํค๋๋ฅผ ํ์ธํ ํ ํธ์คํธ์ ๋๋ฉ์ธ์ด ์ผ์นํ์ง ์์ผ๋ฉด
๋ค๋ฅธ ์ฌ์ดํธ์์ ์จ ์์ฒญ์ด๋ฏ๋ก ๊ฑฐ๋ถํฉ๋๋ค.
์ด ๋ฐฉ๋ฒ๋ง์ผ๋ก ๋ง์ CSRF ๊ณต๊ฒฉ์ ๋ฐฉ์ดํ ์ ์๋ค๊ณ ํฉ๋๋ค.
๐ XSS ๊ณต๊ฒฉ
XSS๋ Cross-Site Scripting์ผ๋ก
๊ด๋ฆฌ์๊ฐ ์๋, ๊ถํ์ด ์๋ ์ฌ์ฉ์๊ฐ ์น ์ฌ์ดํธ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฝ์ ํ๋ ๊ณต๊ฒฉ์ ๋๋ค.
๊ณต๊ฒฉ ๋ฐฉ๋ฒ์ ๋ฐ๋ผ 3๊ฐ์ง๋ก ๊ตฌ๋ถ๋ฉ๋๋ค.
๐ฅ Reflected XSS (๋ฐ์ฌํ ํฌ๋ก์ค์ฌ์ดํธ์คํฌ๋ฆฝํธ)
์ฌ์ฉ์์ ์์ฒญ์ด ์๋ฒ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ๋ก ์ ์ฅ๋์ง ์๊ณ , ์๋ต HTML์ ๊ทธ๋๋ก ๋ฐ์๋๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค.
์ด๋ ์ฌ์ฉ์์ ์์ฒญ์ ์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ํฌํจ๋์ด ์์๋ค๋ฉด, ์ฌ์ฉ์๊ฐ ์ค ๊ฐ์ด ๊ทธ๋๋ก ์นํ์ด์ง์ "๋ฐ์ฌ"๋์ด
๋ธ๋ผ์ฐ์ ์์ ์ ์ฑ ์คํฌ๋ฆฝํธ๋ก ์คํ์ด ๋ฉ๋๋ค.
<script> (์
์ฑ ์คํฌ๋ฆฝํธ ๋ด์ฉ) </script>
๐ฅ Stored XSS (์ ์ฅํ ํฌ๋ก์ค์ฌ์ดํธ์คํฌ๋ฆฝํธ)
์น ์๋ฒ์ ์ ์ฑ ์คํฌ๋ฆฝํธ๋ฅผ ์ ์ฅํ๋ ๊ณต๊ฒฉ ๋ฐฉ๋ฒ์ ๋๋ค.
ํด์ปค๊ฐ ์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ํฌํจ๋ ๊ฒ์๊ธ์ ์์ฑํ์ฌ ๊ฒ์ํ ๋ฑ ์ผ๋ฐ ์ฌ์ฉ์๊ฐ ์ ๊ทผํ ์ ์๋ ํ์ด์ง์ ์ ๋ก๋ํฉ๋๋ค.
์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ DB์ ์ ์ฅ๋๋๊ฑฐ์ฃ .
์ดํ ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ํด๋น ๊ฒ์๊ธ์ ์์ฒญํ๋ฉด ์๋ฒ์ ์ ์ฅ๋ ์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ์ฌ์ฉ์ ์ธก์์ ๋์ํ๊ฒ ๋ฉ๋๋ค.
์ฆ, ํด์ปค๊ฐ ํ ๋ฒ๋ง ์ฝ์ ํด๋, ๊ทธ ๋ฐ์ดํฐ๊ฐ ๋ ธ์ถ๋ ๋๋ง๋ค ํผํด์๊ฐ ์๊ธฐ๋ ์ง์์ ์ธ ์ํ ์์์ ๋๋ค.
๐ฅ DOM Based XSS (DOM ๊ธฐ๋ฐ ํฌ๋ก์ค์ฌ์ดํธ์คํฌ๋ฆฝํธ)
์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ์๋ฒ์ ์ํธ์์ฉ ์์ด ๋ธ๋ผ์ฐ์ ์์ฒด์์ ์คํ๋๋ ์ทจ์ฝ์ ์ผ๋ก,
ํ์ด์ง์ ํฌํจ๋์ด ์๋ ๋ธ๋ผ์ฐ์ ์ ์ฑ ์ฝ๋๊ฐ DOM ํ๊ฒฝ์์ ์คํ๋ฉ๋๋ค.
DOM(Document Object Model, ๋ฌธ์ ๊ฐ์ฒด ๋ชจ๋ธ): ๋ธ๋ผ์ฐ์ ๊ฐ ์น ํ์ด์ง๋ฅผ ๋ ๋๋ง ํ๋๋ฐ ์ฌ์ฉํ๋ ๋ชจ๋ธ๋ก HTML ๋ฐ XML ๋ฌธ์์ ์ ๊ทผํ๊ธฐ ์ํ ์ธํฐํ์ด์ค
์๋ฒ๋ฅผ ๊ฑฐ์น์ง ์๊ธฐ ๋๋ฌธ์ ๋ฐฑ์๋์์ ํด๊ฒฐํ ์ ์๋ ์ ํ์ ์๋๋๋ค.
๐๋์ ์ ๋ต
โ ์ ๋ ฅ๊ฐ ํํฐ๋ง๊ณผ ์ถ๋ ฅ ์ ์ด์ค์ผ์ดํ ์ฒ๋ฆฌ
์ฌ์ฉ์ ์ ๋ ฅ ์ค <script>, onload= ์ ๊ฐ์ด ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๋ ค๋ ํค์๋๊ฐ ์๋ ๊ฒฝ์ฐ
์ด๋ฐ ์ํํ ๋ฌธ์์ด์ ์ฐจ๋จํ๊ฑฐ๋ ํํฐ๋งํฉ๋๋ค.
๋ํ ์ฌ์ฉ์ ์ ๋ ฅ์ HTML, Javascript ๋ฑ์ ์ถ๋ ฅํ ๋ ์์ ํ ๋ฌธ์๋ก ๋ณํํด์ผ ํฉ๋๋ค.
<!-- ์ํ -->
<div>${userInput}</div>
<!-- ์์ (Thymeleaf, JSP ๋ฑ์ ์๋ ์ด์ค์ผ์ดํ ์ง์) -->
<div th:text="${userInput}"></div>
โ Content Security Policy (CSP)
CSP๋ ๋ธ๋ผ์ฐ์ ๋จ์์ ์คํฌ๋ฆฝํธ ์คํ์ ์ ํํ๋ ๋ณด์ ์ ์ฑ ์ ๋๋ค.
"์ด ํ์ด์ง์๋ ์ด ๋ฆฌ์์ค๋ค๋ง ํ์ฉํ๋ค"๋ฅผ ์ง์ํ๋ ๊ฒ์ ๋๋ค.
Spring Security์์ ์๋์ ๊ฐ์ด CSP๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; script-src 'self'")
)
);
return http.build();
}
์ด๋ฌ๋ฉด Spring Security๋ ์๋ต ํค๋์ ์๋์ฒ๋ผ ์ถ๊ฐํฉ๋๋ค.
Content-Security-Policy: default-src 'self'; script-src 'self'
- default-src 'self': ๊ธฐ๋ณธ์ ์ผ๋ก ๋ด ๋๋ฉ์ธ(self)์ ๋ฆฌ์์ค๋ง ํ์ฉ
- script-src 'self': ์คํฌ๋ฆฝํธ๋ ์ค์ง ๋ด ์๋ฒ(self)์์ ์ ๊ณต๋๋ ๊ฒ๋ง ์คํ ๊ฐ๋ฅ
- ์ธ๋ถ Javascript ํ์ผ์ด๋ HTML๋ด <script> ํ๊ทธ๊ฐ ์ธ๋ผ์ธ์ผ๋ก ์์ด๋ ์คํ๋์ง ์์ต๋๋ค.
- ์ ์ฑ ์คํฌ๋ฆฝํธ ์คํ์ ๋ง๊ธฐ ์ํด ์ค์ํ ์ค์ ์ ๋๋ค.
self ์ธ์๋ ๋ค์ํ ์ต์ ์ด ์์ต๋๋ค.
- nonce-{๋๋ค๊ฐ}: ํน์ nonce ๊ฐ์ ๊ฐ์ง ์ธ๋ผ์ธ ์คํฌ๋ฆฝํธ๋ง ํ์ฉ
- sha256-{ํด์๊ฐ}: ํน์ ํด์์ ์ผ์นํ๋ ์ธ๋ผ์ธ ์คํฌ๋ฆฝํธ๋ง ํ์ฉ
์ฐธ๊ณ ์๋ฃ
- https://devscb.tistory.com/123
- https://gyoogle.dev/blog/web-knowledge/CSRF%20&%20XSS.html
- https://4rgos.tistory.com/1
- https://www.skshieldus.com/blog-security/security-trend-idx-06