# CSRF(Cross Site Request Forgery)
CSRF ๊ณต๊ฒฉ(Cross Site Request Forgery)์ ์น ์ดํ๋ฆฌ์ผ์ด์ ์ทจ์ฝ์ ์ค ํ๋๋ก ์ธํฐ๋ท ์ฌ์ฉ์(ํฌ์์)๊ฐ ์์ ์ ์์ง์๋ ๋ฌด๊ดํ๊ฒ ๊ณต๊ฒฉ์๊ฐ ์๋ํ ํ์(์์ , ์ญ์ , ๋ฑ๋ก ๋ฑ)๋ฅผ ํน์ ์น์ฌ์ดํธ์ ์์ฒญํ๊ฒ ๋ง๋๋ ๊ณต๊ฒฉ์ ๋๋ค.
CSRF๋ฅผ ํตํด ํด์ปค๋ ํฌ์์์ ๊ถํ์ ๋์ฉํ์ฌ ์ค์ ๊ธฐ๋ฅ์ ์คํํ๋ ๊ฒ์ด ๊ฐ๋ฅํฉ๋๋ค. ์๋ฅผ๋ค์ด, ํ์ด์ค๋ถ์ ํฌ์์์ ๊ณ์ ์ผ๋ก ๊ด๊ณ ์ฑ ๊ธ์ ์ฌ๋ฆฌ๋ ๊ฒ์ด ๊ฐ๋ฅํด ์ง๋๋ค. (๋ฌผ๋ก ํ์ด์ค๋ถ์ CSRF ๊ณต๊ฒฉ์ ๋ํด ์ ๋์์ ํ์๊ฒ ์ง๋ง, ์ด๋ฒ ๊ธ์์ ํผํด ์๋น์ค = ํ์ด์ค๋ถ์ผ๋ก ์ค๋ช ํ๊ฒ ์ต๋๋ค.)
์กฐ๊ธ ๋ ์ค๋ช ํ์๋ฉด, CSRF๋ ํด์ปค๊ฐ ์ฌ์ฉ์์ ์ปดํจํฐ๋ฅผ ๊ฐ์ผ์ํค๊ฑฐ๋ ํ์ด์ค๋ถ ์๋ฒ๋ฅผ ํดํน์ ํด์ ์ด๋ค์ง๋ ๊ณต๊ฒฉ์ ์๋๋๋ค. ๊ทธ๋์ CSRF ๊ณต๊ฒฉ์ด ์ด๋ค์ง๋ ค๋ฉด ๋ค์ ์กฐ๊ฑด์ด ๋ง์กฑ๋์ด์ผ ํฉ๋๋ค.
- ์์กฐ ์์ฒญ์ ์ ์กํ๋ ์๋น์ค(ํ์ด์ค๋ถ)์ ํฌ์์๊ฐ ๋ก๊ทธ์ธ ์ํ
- ํฌ์์๊ฐ ํด์ปค๊ฐ ๋ง๋ ํผ์ฑ ์ฌ์ดํธ์ ์ ์
์ธ๋ป ๋ณด๋ฉด ์ด ๋ ์กฐ๊ฑด์ ๋ค ๋ง์กฑํ๊ธฐ๊ฐ ์ด๋ ค์ธ ๊ฒ ๊ฐ์ง๋ง ์๊ฐ์ฒ๋ผ ๋๋ฌธ ์ผ์ ์๋๋๋ค. ์๋ฅผ๋ค์ด ํ์ด์ค๋ถ, ๋ค์ด๋ฒ, ๊ตฌ๊ธ ๋ฑ์ ์ ๋ช ์ฌ์ดํธ๋ ๋ณดํต PC์์ ์๋ ๋ก๊ทธ์ธ์ ํด๋์ ๊ฒฝ์ฐ๊ฐ ๋ง๊ณ ํผ์ฑ ์ฌ์ดํธ๋ ํผ์ฑ ๋ฉ์ผ, ์๋ ์ฌ์ดํธ(?) ๋ฑ์ ํตํด ์ ์๋ ์ ์์ต๋๋ค. ๋ํ ํฌ์์๊ฐ ํด์ปค๊ฐ ๋ง๋ ํผ์ฑ ์ฌ์ดํธ๋ฅผ ํ์ง ์๋๋ผ๋ ํด์ปค๊ฐ XSS ๊ณต๊ฒฉ์ ์ฑ๊ณตํ ์ ์ ์ฌ์ดํธ๋ฅผ ํตํด CSRF ๊ณต๊ฒฉ์ด ์ํ๋ ์ ๋ ์์ต๋๋ค.
CSRF๊ฐ ํํด์ง๋ ์๋๋ฆฌ์ค๋ฅผ ๊ทธ๋ฆผ์ผ๋ก ๊ทธ๋ ค๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค. ์ด๋ฏธ์ง๋ ์ง์ ๋ง๋ค๊ธฐ ๊ท์ฐฎ์ ๊ด๊ณ๋ก OWASP์ ๋ฆฌ๋๋ก ๊ทผ๋ฌดํ์๋ ๋ถ์ ๋ธ๋ก๊ทธ์์ ๋ฐ์ท ํ์์ต๋๋ค. (์น์ ํ์ง ์์ง๋ง ๊ทธ๋๋ง ์ ์ผ ์ดํดํ๊ธฐ ์ฌ์ด ์ด๋ฏธ์ง์ธ ๊ฒ ๊ฐ๋ค์.)
[์ด๋ฏธ์ง ์ถ์ฒ : http://www.bluekaizen.org]
์ข ๋ ์ดํดํ๊ธฐ ์ฝ๊ฒ ์์ CSRF ๊ณต๊ฒฉ์ฝ๋๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ๋ฌผ๋ก ๋ง๋ ์๋๋ ์ด์ผ๊ธฐ์ง๋ง, ํ์ด์ค๋ถ์ ๊ธ์ ์ธ ๋ ์๋ ์ฝ๋์ ๊ฐ์ ํผ์ด ์ ์ก๋๋ค๊ณ ์๋ฅผ ๋ญ์๋ค. ํผ์ฑ ์ฌ์ดํธ์ ๋๊ฐ์ด ํ์ด์ค๋ถ์ ๊ธ์ฐ๊ธฐ๋ฅผ ์์ฒญํ๋ ํผ์ด ์จ๊ฒจ์ ธ ์๊ณ , ๊ทธ ๋ด์ฉ์ผ๋ก ๊ฐ์ ํ๋ฉด 10๋ง์์ ์ค๋ค๋ ์ฌ๊ธฐ์ฑ ๊ด๊ณ ๋ฅผ ๋ณธ๋ฌธ์ผ๋ก ์ ํ์ ธ ์์ต๋๋ค. ํฌ์์๋ ํผ์ฑ ์ฌ์ดํธ์ ์ ์ํจ์ผ๋ก์จ ๋ณธ์ธ์ ํ์ด์ค๋ถ ๊ณ์ ์ผ๋ก ํด๋น ๊ธ์ด ๋ฑ๋ก๋๊ฒ ๋ฉ๋๋ค.
ํผ์ฑ ์ฌ์ดํธ์ ํฌํจ๋ ์ฝ๋
<form action="http://facebook.com/api/content" method="post">
<input type="hidden" name="body" value="์ฌ๊ธฐ ๊ฐ์
ํ๋ฉด ๋ 10๋ง์ ๋๋ฆฝ๋๋ค." />
<input type="submit" value="Click Me"/>
</form>
์์ ๊ณต๊ฒฉ์ ํตํด ํฌ์์์ ํ์น๋ค์ ์น๊ตฌ๊ฐ ์ฌ๋ฆฐ ๊ธ์ด๋ ์์ฌ์์ด ์์ ๋์ด๊ฐ ์๋ ์๊ฒ ์ฃ . ์ด๋ฐ ๋์ฐํ ์ผ์ ๋ง๊ธฐ ์ํด ๋ํ์ ์ธ CSRF ๊ณต๊ฒฉ ๋ฐฉ์ด ๋ฐฉ๋ฒ๋ค์ ๋ช ๊ฐ์ง ์ดํด๋ด ์๋ค. ๋ํ์ ์ผ๋ก ๋ค์ 2๊ฐ์ง ๋ฐฉ์ด๊ธฐ๋ฒ์ด ์์ต๋๋ค.
- Referrer ๊ฒ์ฆ
- Security Token ์ฌ์ฉ (A.K.A CSRF Token)
์ผ๋ฐ์ ์ผ๋ก CSRF ๊ณต๊ฒฉ ๋ฐฉ์ด๋ ์กฐํ์ฑ(HTTP GET Method) ๋ฐ์ดํฐ์๋ ๋ฐฉ์ด ๋์์ ๋์ง ์๊ณ , ์ฐ๊ธฐ/๋ณ๊ฒฝ์ด ๊ฐ๋ฅํ POST, PATCH, DELETE Method์๋ง ์ ์ฉํ๋ฉด ๋ฉ๋๋ค. ๋ฌผ๋ก ์ ๋ง ์ค์ํ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๊ฑฐ๋ GET์ ํตํด ์ฐ๊ธฐ/๋ณ๊ฒฝ ๋ฑ์ ๋์์ ํ๋ค๋ฉด GET Method์๋ ๋ฐฉ์ด๋ฅผ ํด์ผํ ์ ๋ ์์ต๋๋ค.
# Referrer ๊ฒ์ฆ
Back-end ๋จ์์ request์ referrer๋ฅผ ํ์ธํ์ฌ domain (ex. *.facebook.com) ์ด ์ผ์นํ๋ ์ง ๊ฒ์ฆํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก referrer ๊ฒ์ฆ๋ง์ผ๋ก ๋๋ถ๋ถ์ CSRF ๊ณต๊ฒฉ์ ๋ฐฉ์ดํ ์ ์์ต๋๋ค. ํ์ง๋ง ๊ฐ์ ๋๋ฉ์ธ ๋ด์ ํ์ด์ง์ XSS ์ทจ์ฝ์ ์ด ์๋ ๊ฒฝ์ฐ CSRF ๊ณต๊ฒฉ์ ์ทจ์ฝํด์ง ์ ์์ต๋๋ค. domain ๋จ์ ๊ฒ์ฆ์์ ์ข ๋ ์ธ๋ฐํ๊ฒ ํ์ด์ง ๋จ์๊น์ง ์ผ์นํ๋์ง ๊ฒ์ฆ์ ํ๋ฉด ๋๋ฉ์ธ ๋ด์ ํ ํ์ด์ง์์์ XSS ์ทจ์ฝ์ ์ ์ํ CSRF ๊ณต๊ฒฉ์ ๋ฐฉ์ดํ ์ ์์ต๋๋ค.
# Security Token ์ฌ์ฉ (A.K.A CSRF Token)
Referrer ๊ฒ์ฆ์ด ๋ถ๊ฐํ ํ๊ฒฝ์ด๋ผ๋ฉด, Security Token๋ฅผ ํ์ฉํ ์ ์์ต๋๋ค. ์ฐ์ ์ฌ์ฉ์์ ์ธ์ ์ ์์์ ๋์ ๊ฐ์ ์ ์ฅํ๊ณ ์ฌ์ฉ์์ ์์ฒญ ๋ง๋ค ํด๋น ๋์ ๊ฐ์ ํฌํจ ์์ผ ์ ์ก์ํต๋๋ค. ์ดํ Back-end ๋จ์์ ์์ฒญ์ ๋ฐ์ ๋๋ง๋ค ์ธ์ ์ ์ ์ฅ๋ ํ ํฐ ๊ฐ๊ณผ ์์ฒญ ํ๋ผ๋ฏธํฐ์ ์ ๋ฌ๋๋ ํ ํฐ ๊ฐ์ด ์ผ์นํ๋ ์ง ๊ฒ์ฆํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ์ด ๋ฐฉ๋ฒ๋ ๊ฒฐ๊ตญ ๊ฐ์ ๋๋ฉ์ธ ๋ด์ XSS ์ทจ์ฝ์ ์ด ์๋ค๋ฉด CSRF ๊ณต๊ฒฉ์ ์ทจ์ฝํด์ง๋๋ค. ์๋๋ ๊ฐ๋ตํ ์ํ ์ฝ๋์ ๋๋ค.
// ๋ก๊ทธ์ธ์, ๋๋ ์์
ํ๋ฉด ์์ฒญ์ CSRF ํ ํฐ์ ์์ฑํ์ฌ ์ธ์
์ ์ ์ฅํ๋ค.
session.setAttribute("CSRF_TOKEN",UUID.randomUUID().toString());
// ์์ฒญ ํ์ด์ง์ CSRF ํ ํฐ์ ์
ํ
ํ์ฌ ์ ์กํ๋ค
<input type="hidden" name="_csrf" value="${CSRF_TOKEN}" />
// ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ๋ csrf ํ ํฐ ๊ฐ
String param = request.getParameter("_csrf");
// ์ธ์
์ ์ ์ฅ๋ ํ ํฐ ๊ฐ๊ณผ ์ผ์น ์ฌ๋ถ ๊ฒ์ฆ
if (request.getSession().getAttribute("CSRF_TOKEN").equals(param)) {
return true;
} else {
response.sendRedirect("/");
return false;
}
# Double Submit Cookie ๊ฒ์ฆ
Double Submit Cookie ๊ฒ์ฆ์ Security Token ๊ฒ์ฆ์ ํ ์ข ๋ฅ๋ก ์ธ์ ์ ์ฌ์ฉํ ์ ์๋ ํ๊ฒฝ์์ ์ฌ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ์ ๋๋ค. ์น๋ธ๋ผ์ฐ์ ์ Same Origin ์ ์ฑ ์ผ๋ก ์ธํด ์๋ฐ์คํฌ๋ฆฝํธ์์ ํ ๋๋ฉ์ธ์ ์ฟ ํค ๊ฐ์ ํ์ธ/์์ ํ์ง ๋ชปํ๋ค๋ ๊ฒ์ ์ด์ฉํ ๋ฐฉ์ด ๊ธฐ๋ฒ์ ๋๋ค. ์คํฌ๋ฆฝํธ ๋จ์์ ์์ฒญ ์ ๋์ ๊ฐ์ ์์ฑํ์ฌ ์ฟ ํค์ ์ ์ฅํ๊ณ ๋์ผํ ๋์ ๊ฐ์ ์์ฒญ ํ๋ผ๋ฏธํฐ(ํน์ ํค๋)์๋ ์ ์ฅํ์ฌ ์๋ฒ๋ก ์ ์กํฉ๋๋ค. ์๋ฒ๋จ์์๋ ์ฟ ํค์ ํ ํฐ ๊ฐ์ ํ๋ผ๋ฏธํฐ์ ํ ํฐ ๊ฐ์ด ์ผ์นํ๋ ์ง๋ง ๊ฒ์ฌํ๋ฉด ๋ฉ๋๋ค. ์๋ฒ์ ๋ฐ๋ก ํ ํฐ ๊ฐ์ ์ ์ฅํ ํ์๊ฐ ์์ด ์์์ ์ดํด๋ณธ ์ธ์ ์ ์ด์ฉํ ๊ฒ์ฆ๋ณด๋ค ๊ฐ๋ฐ ๊ณต์๊ฐ ์ ์ ํธ์ ๋๋ค. ํผ์ฑ ์ฌ์ดํธ์์๋ ๋๋ฉ์ธ์ด ๋ฌ๋ผ facebook.com ์ฟ ํค์ ๊ฐ์ ์ ์ฅํ์ง ๋ชปํ๋ฏ๋ก (์กฐ๊ธ ์ ์ ์ธ๊ธํ Same Origin ์ ์ฑ ) ๊ฐ๋ฅํ ๋ฐฉ์ด ๊ธฐ๋ฒ์ ๋๋ค. ์๋๋ ์ํ ์ฝ๋์ ๋๋ค.
/**
* Generate 256-bit BASE64 encoded hashes
*
* @see https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Synchronizer_.28CSRF.29_Tokens
* @return {string}
*/
var generateCsrfToken = function() {
function generateRandomString(length) {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for(var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
return btoa(generateRandomString(32));
}
// ์ฟ ํค ์
ํ
var setCookie = function (cname, cvalue) {
document.cookie = cname + "=" + cvalue + ";path=/";
}
// ๋ชจ๋ ajax ์์ฒญ ์ ์ฟ ํค ๋ฐ header์ ํ ํฐ ๊ฐ์ ๊ฐ์ด ์ ๋ฌ
jQuery.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
var csrfToken = generateCsrfToken();
setCookie('CSRF_TOKEN', encodeURIComponent(csrfToken));
xhr.setRequestHeader("_csrf", csrfToken);
}
}
});
// ํค๋๋ก ์ ๋ฌ๋ csrf ํ ํฐ ๊ฐ
String paramToken = request.getHeader("_csrf");
// ์ฟ ํค๋ก ์ ๋ฌ๋ csrf ํ ํฐ ๊ฐ
String cookieToken = null;
for (Cookie cookie : request.getCookies()) {
if ("CSRF_TOKEN".equals(cookie.getName())) {
cookieToken = URLDecoder.decode(cookie.getValue(), "UTF-8");
// ์ฌ์ฌ์ฉ์ด ๋ถ๊ฐ๋ฅํ๋๋ก ์ฟ ํค ๋ง๋ฃ
cookie.setPath("/");
cookie.setValue("");
cookie.setMaxAge(0);
response.addCookie(cookie);
break;
}
}
// ๋ ๊ฐ์ด ์ผ์นํ๋ ์ง ๊ฒ์ฆ
if (cookieToke.equals(paramToken)) {
return true;
} else {
return false;
}
Reference
- https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
- https://itstory.tk/entry/CSRF-๊ณต๊ฒฉ์ด๋-๊ทธ๋ฆฌ๊ณ -CSRF-๋ฐฉ์ด-๋ฐฉ๋ฒ [๋'s IT Story]