xssAndCsrf

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ์˜ XSS(Cross Site Scripting)์™€ CSRF(Cross Site Request Forgery) ํ•ด๊ฒฐ๋ฐฉ์•ˆ

Reference

  1. Prevent Cross-Site Scripting (XSS) in a Spring Application : https://www.baeldung.com/spring-prevent-xss

  2. A Guide to CSRF Protection in Spring Security: https://www.baeldung.com/spring-security-csrf

XSS

  • xss๋Š” ๋ฐ”๋กœ ์‹คํ–‰๋˜๋Š” reflected xss๊ฐ€ ์žˆ๊ณ , db์— ์ €์žฅ๋œ ํ›„ ์‹คํ–‰๋˜๋Š” stored xss๊ฐ€ ์žˆ๋‹ค. ๋ณดํ†ต xss๋ฅผ ๋งํ•˜๋ฉด stored xss๋ฅผ ๋งํ•œ๋‹ค.

  • stored xss์˜ ์˜ˆ์‹œ๋กœ, ๊ณต๊ฒฉํ•  ๋Œ€์ƒ ํŽ˜์ด์ง€์˜ ๋Œ“๊ธ€์— ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์จ๋†“๊ณ , ๋Œ“๊ธ€์ด db์— ์ €์žฅ๋˜๋ฉด ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ๊ทธ ํŽ˜์ด์ง€๋ฅผ ๋กœ๋”ฉํ–ˆ์„ ๋•Œ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

  • ์ด๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ•„ํ„ฐ๋งํ•จ์œผ๋กœ์จ ํ•ด๊ฒฐ๊ฐ€๋Šฅํ•˜๋‹ค.

  • ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋Š” ์•„๋ž˜์˜ ๋ฐฉ๋ฒ•์„ ์ง€์›ํ•œ๋‹ค. **CSP(Content Security Policy)**๋Š” xss์™€ data injection์„ ๋ง‰์•„์ฃผ๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ๋งŒ๋“œ๋Š” ํ•˜๋‚˜์˜ ๋ ˆ์ด์–ด์ด๋‹ค.

@Configuration
public class SecurityConf extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .headers()
          .xssProtection()
          .and()
          .contentSecurityPolicy("script-src 'self'");
    }
}

CSRF(XSRF)

  • ์ฟ ํ‚ค๋ฅผ ์ด์šฉํ•œ ๊ณต๊ฒฉ์œผ๋กœ, ์‹ค์ œ ์‚ฌ์ดํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ฟ ํ‚ค๋ฅผ ๋ธŒ๋ผ์šฐ์ €์— ์ €์žฅํ•œ ํ›„, ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ๊ฐ€ ์ด ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ด ์‹ค์ œ ์‚ฌ์ดํŠธ๋กœ ์š”์ฒญํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. (์ฟ ํ‚ค๋Š” ์š”์ฒญ์— ์ž๋™์œผ๋กœ ํฌํ•จ๋˜๋Š” ํŠน์„ฑ์„ ์ด์šฉ) ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” jwt๋Š” csrf๋ฅผ ๋ง‰์„ ํ•„์š”๊ฐ€ ์—†๋‹ค.

  • ์˜ˆ์‹œ๋กœ, ์‹ค์ œ ์‚ฌ์ดํŠธ์™€ ๋น„์Šทํ•˜๊ฒŒ ์œ„์กฐํ•œ ์‚ฌ์ดํŠธ๋ฅผ ๋งŒ๋“ค์–ด์„œ ํ”ผํ•ด์ž๊ฐ€ ์ด ์œ„์กฐ ํŽ˜์ด์ง€์— ์ ‘์†ํ•˜๋ฉด ํด๋ฆญ ํ˜น์€ ํŽ˜์ด์ง€ ๋กœ๋”ฉ๊ณผ ๋™์‹œ์— ์‹ค์ œ ์‚ฌ์ดํŠธ๋กœ ํ”ผํ•ด์ž๊ฐ€ ์œ„์กฐ๋œ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋ฐฉ์‹์ด๋‹ค.

  • ์•„๋ž˜๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง๊ณผ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ๋ฐฉ์‹์˜ csrf๋ฅผ ๋ง‰๋Š” ๋ฐฉ์‹์ด๋‹ค.(spring security 4.x์ด์ƒ ๋ฒ„์ „์—์„œ๋Š” csrf๊ฐ€ default๋กœ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋‹ค.)

    • ์ฒซ๋ฒˆ์งธ๋กœ ์Šคํ”„๋ง๊ณผ ํ†ตํ•ฉ๋œ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋ Œ๋”๋ง๋œ html์— csrf token์„ ์ ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด ์žˆ๋‹ค.

      • form ์ „์†ก ๋ฐฉ์‹

        ๋ชจ๋“  form์— ์•„๋ž˜์˜ hidden input์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

        <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

        ์ด๋Š” ๋ชจ๋“  form์— ์œ„์˜ hidden input์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์กด์žฌํ•œ๋‹ค.

        ๋˜ํ•œ multipartํ˜•์‹์œผ๋กœ ์ „์†กํ•ด์•ผ ํ•  ๊ฒฝ์šฐ ์š”์ฒญ ํŽ˜์ด๋กœ๋“œ์— ์žˆ๋Š” _csrfํ•„๋“œ๋ฅผ ์Šคํ”„๋ง์ด ์ธ์‹ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด ๋•Œ Multipartfilter๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๋ถ€ํŠธ๊ฐ€ ์•„๋‹Œ ์Šคํ”„๋ง์—์„œ Multipartfilter๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” tomcat์—์„œ MultipartParsing์„ allowํ•˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.

        ์•„๋ž˜๋Š” ์™ธ์žฅ tomcat์˜ MultipartParsing์„ allowํ•˜๋Š” context.xml์„ค์ •์ด๋‹ค.

        <Context allowCasualMultipartParsing="true">
            <WatchedResource>WEB-INF/web.xml</WatchedResource>
                <-- ... -->
        </Context>
      • json ์ „์†ก ๋ฐฉ์‹

        root html์— ๋‹ค์Œ์˜ meta tag๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. (jquery๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.)

        <meta name="_csrf" content="${_csrf.token}"/>
        <meta name="_csrf_header" content="${_csrf.headerName}"/>

        root ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์— ๋ชจ๋“  XHR request(XMLHttpRequest)์— ์„ ํ–‰ํ•˜๋Š” ์„ค์ •์„ ํ•ด์ค€๋‹ค.

        $(document).ajaxSend(function(e, xhr, options) {
            xhr.setRequestHeader(header, token);
        });

        root html์— ์œ„์˜ 2๋ฒˆ์˜ meta tag์™€ 3๋ฒˆ์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋งŒ ์„ค์ •ํ•˜๋ฉด json์ „์†ก ๋ฐฉ์‹์€ form์ „์†ก ๋ฐฉ์‹๊ณผ ๋‹ฌ๋ฆฌ ๋”ฐ๋กœ csrf token์„ ์„ค์ •ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

    • ๋‘๋ฒˆ์งธ๋Š” Stateless Spring API๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ csrf token์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

      • ์ฟ ํ‚ค์—์„œ csrf token ๊บผ๋‚ด์„œ ์‚ฌ์šฉํ•˜๊ธฐ

        ๋ฐฑ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” html์„ ๋ Œ๋”๋งํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์œ„์—์„œ ์„ค๋ช…ํ–ˆ๋˜ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค. ๋”ฐ๋ผ์„œ ์ฟ ํ‚ค์— CSRF Token์„ ๋‹ด๋Š”๋‹ค. CookieCsrfTokenRepository.withHttpOnlyFalse()์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋„ฃ์œผ๋ฉด cookieHttpOnly ๋ผ๋Š” CookieCsrfTokenRepository ์˜ ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๊ฐ€ false๋กœ ์„ค์ •๋œ๋‹ค. httponlycookie๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ฟ ํ‚ค์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์„ ๋ง‰๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ํด๋ผ์ด์–ธํŠธ์—์„œ ์ฟ ํ‚ค์— ๋“ค์–ด ์žˆ๋Š” csrf token์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋ฏ€๋กœ false๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.(์ด ํ—ค๋”๋ฅผ ์ง€์›ํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ €์— ํ•œํ•ด์„œ HttpOnlyCookie๊ฐ€ true์ด๋ฉด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ฟ ํ‚ค๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.) HttpOnly cookie์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ •๋ณด : https://www.whitehatsec.com/glossary/content/httponly-session-cookie

        @Configuration
        public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
            @Override
            public void configure(HttpSecurity http) throws {
                http
                    .csrf()
                    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
            }
        }

        ๋‘ ๋ฒˆ์งธ๋กœ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์„œ๋ฒ„ ์ธก ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋ชจ๋“  ์š”์ฒญ์— ์ ์šฉ๋˜๋Š” ์„ค์ •์„ ํ•ด์ค€๋‹ค. ์Šคํ”„๋ง์—์„œ๋Š” ํ—ค๋”์˜ X-XSRF-TOKEN์„ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฐ›๋Š”๋ฐ, multipart์š”์ฒญ์œผ๋กœ _csrfํ•„๋“œ์— ๋„ฃ๋Š” ๊ฒƒ์„ ๋ฐ›์„ ์ˆ˜๋„ ์žˆ๋‹ค.

        const csrfToken = document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1');
        
        fetch(url, {
            method: 'POST',
            body: /* data to send */,
            headers: { 'X-XSRF-TOKEN': csrfToken },
        })

Last updated