Spring/Spring Security

[Spring Security] 4-2. 보안: Security HTTP Response Headers

noahkim_ 2023. 10. 14. 01:24

1. Default Security Headers

Spring Security는 기본적으로 보안에 관련된 HTTP Response Header를 제공합니다.

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-XSS-Protection: 0

 

2. Cache Control

기본 캐싱 전략
  • Spring Security는 기본적으로 사용자의 내용을 보호하기 위해 캐싱을 비활성화 합니다.
  • 악의적인 공격자가 로그아웃 후 뒤로가기 버튼으로 사용자의 민감정보를 보지 못하게 하려는 것입니다.

 

기본 캐시 제어 헤더
Cache-Control: no-cache, no-store, max-age=0, must-revalidate 
Pragma: no-cache 
Expires: 0
  • 브라우저에게 해당 페이지나 리소스를 캐시하지 말라고 지시합니다.

 

사용자 정의 캐시 제어 헤더
  • 하지만 만약 애플리케이션에서 캐시 제어 헤더를 제공한다면 Spring Security는 사용자 정의 헤더를 우선시합니다.
  • 이런 전략은 정적 리소스가 캐시되는데 주로 사용됩니다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // ...
            .headers(headers -> headers
                .cacheControl(cache -> cache.disable())
            );
        return http.build();
    }
}

 

3. Content Type Options

content-sniff
  • 브라우저가 응답의 Content-Type을  추론하는 기능입니다.
  • 브라우저는 Body 부분을 읽어 추론합니다
  • 과거에는 브라우저에서 정확한 Content-Type을 얻기 위해 사용하였습니다.
    • 올바르지 않은 Content-Type 값이 전달되는 경우에 대한 방법입니다.

 

문제점
  • 악의적인 사용자의 XSS 공격에 노출될 수 있습니다.
  • HTTP Request의 Body 에 악의적인 스크립트를 작성하여 요청하면 서버는 해당 스크립트를 읽게 됩니다.

 

해결책
X-Content-Type-Options: nosniff
  • 기본적으로 Spring Security는 XSS 공격의 노출을 피하기 위해 no-sniff 설정으로 응답헤더를 제공합니다.
  • Content-Type도 올바르게 작성되어야 정상적으로 요청이 인식됩니다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.headers(headers -> headers
				.contentTypeOptions(contentTypeOptions -> contentTypeOptions.disable())
			);
		return http.build();
	}
}

 

4. HTTP Strict Transport Security (HSTS)

  • 모든 요청이 HTTPS 프로토콜로 이루어져야 함을 강제하는 보안 헤더입니다.
    • 중간자 공격을 방지하기 위해 사용됩니다. (Man-in-the-Middle attack)
    • HTTP 요청은 리다이렉션되어 HTTPS 프로토콜로 재요청 됩니다.
  • 옵션을 사용하여 서버는 브라우저에게 추가적인 HTTPS 프로토콜 사용을 알릴 수 있습니다.

 

적용 조건
  • 브라우저는 내장된 신뢰할 만한 CA 리스트를 통해 서버의 SSL 인증서가 유효한지 확인합니다.
    • 해당 서버의 SSL 인증서가 자신의 CA 화이트리스트에 포함되어있는지 확인합니다.
  • HSTS 헤더는 HTTPS 응답에만 삽입됩니다.

 

적용 방법
  • 브라우저의 HSTS preload 리스트에 호스트를 등록합니다.
  • 응답에 Strict-Transport-Security 헤더를 추가합니다.

 

기본 설정
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  •  브라우저에 도메인을 1년간 HSTS 호스트로 취급하도록 지시합니다
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.headers(headers -> headers
				.httpStrictTransportSecurity(hsts -> hsts
					.includeSubDomains(true)
					.preload(true)
					.maxAgeInSeconds(31536000)
				)
			);
		return http.build();
	}
}

 

옵션 지시자
  • includeSubDomains : 브라우저에게 '서브도메인도 HSTS 도메인으로 취급함'을 지시합니다.
  • preload : 브라우저에 해당 도메인이 HSTS 도메인으로 미리 로드되어야 함을 지시합니다.

 

5. X-Frame-Options

  • 브라우저가 embedded 페이지 태그의 렌더링을 막는 응답헤더입니다.
  • 웹사이트에 프레임이 있는것이 보안 이슈를 발생시킬 수 있습니다.

 

Clickjacking
  • 자신의 의도가 아닌 클릭으로 공격자가 만들어둔 로직이 동작하여 피해를 입는 공격패턴
    • CSS style을 현란하게 해서 사용자가 의도치않게 특정 컴포넌트를 클릭하게 하여 피해가 발생함
    • 로그인된 사용자가 버튼을 눌러 이체 권한을 공격자에게 부여하도록 함

 

방어법
  • CSP (Content Security Policy)
  • X-Frame-Options
    • DENY : 모든 frame 태그 사용 금지
    • SAMEORIGIN : 현재 페이지와 같은 origin으로 이동하는 frame 태그만 허용
    • ALLOW-FROM (origin) : 특정 origin으로 이동하는 frame 태그만 허용
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.headers(headers -> headers
				.frameOptions(frameOptions -> frameOptions
					.sameOrigin()
				)
			);
		return http.build();
	}
}

 

 

6. X-XSS-Protection

  • 페이지 로딩중 XSS 공격을 감지하는 보안 헤더입니다.
  • 취약점으로 인해 최신 브라우저에서 지원하지 않습니다. (CSP 사용 권장)
X-XSS-Protection: 0
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.headers(headers -> headers
				.xssProtection(xss -> xss
					.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK)
				)
			);
		return http.build();
	}
}

 

7. Content Security Policy

  • 브라우저가 특정 리소스를 로드하거나 실행할 수 있는 규칙을 정의하는 응답헤더
  • 브라우저에게 웹 애플리케이션이 로드할것으로 예상되는 소스에 대한 정보를 제공합니다.
  • 추가적인 보안 레이어를 두는 효과를 주어 웹 관련 공격을 방어하는데 효과적입니다.

 

Content-Security-Policy
Content-Security-Policy: script-src https://trustedscripts.example.com
Content-Security-Policy: script-src https://trustedscripts.example.com; report-uri /csp-report-endpoint/
  • script-src : 스크립트를 로드할 수 있는 화이트리스트를 지정하는 지시어
  • report-url : 보안정책 위반을 보고할 uri를 지정하는 지시어
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.headers(headers -> headers
				.contentSecurityPolicy(csp -> csp
					.policyDirectives("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
				)
			);
		return http.build();
	}
}

 

Content-Security-Policy-Report-Only
  • 웹 애플리케이션 작성자와 관리자가 보안 정책을 강제하는 대신 모니터링할 수 있도록 하는 기능을 제공합니다.
  • 보안 정책에 위반되는 리소스 로드 시도가 발생하면, 이러한 시도는 차단되지 않고 브라우저는 해당 위반에 대한 보고만 생성합니다
Content-Security-Policy-Report-Only: script-src 'self' https://trustedscripts.example.com; report-uri /csp-report-endpoint/

 

8. Referrer Policy

  • 웹 애플리케이션에서 사용자의 이전 페이지 정보를 포함하고 있는 "referrer" 필드를 관리하는 메커니즘입니다.
  • 브라우저가 리소스나 페이지를 요청할 때 Referer 헤더에 포함할 URL의 일부 또는 전체를 결정하는 방법을 지정하는 데 사용됩니다.
    • 사용자가 이전에 있던 페이지(출처) 정보를 목적지 웹사이트나 페이지에 알리도록 브라우저에 지시합니다.

 

Referer header
  • 해당 요청을 생성한 URL을 포함하는 헤더입니다.
  • 로깅, 캐시 최적화, 보안등의 목적으로 사용됩니다.

 

정책
  • same-origin
    • 같은 출처의 요청에 대해서만 전체 경로를 Referer 헤더에 포함합니다.
    • 다른 출처로의 요청에는 Referer 헤더를 전송하지 않습니다.
  • strict-origin
    • 같은 출처나 다른 출처에 상관없이 origin만 Referer 헤더에 포함합니다.
    • HTTPS에서 HTTP로의 요청 시 Referer 헤더를 전송하지 않습니다.
  • strict-origin-when-cross-origin
    • 같은 출처의 요청에 대해서는 전체 URL을 포함합니다.
    • 다른 출처로의 요청에는 오리진만 포함합니다.
    • HTTPS에서 HTTP로의 요청에 대해서는 Referer 헤더를 전송하지 않습니다.
  • origin-when-cross-origin
    • 같은 출처의 요청에 대해서는 전체 URL을 포함합니다.
    • 다른 출처로의 요청에 대해서는 오리진만 포함합니다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.headers(headers -> headers
				.referrerPolicy(referrer -> referrer
					.policy(ReferrerPolicy.SAME_ORIGIN)
				)
			);
		return http.build();
	}
}

 

 

9. Custom Headers

Static Headers

  • 변하지 않는 고정된 값을 가진 HTTP 헤더를 의미합니다
  • 사용자 정의 헤더는 어플리케이션 내에서 특정 기능이나 정보를 전달하기 위해 사용될 수 있습니다.
    • 일반적으로 "X-" 접두어는 비표준 또는 사용자 정의 헤더를 나타내는 데 사용됩니다.
    • 예를 들면, 특정 보안 정책이나 사용자의 세션 정보, 요청에 대한 추가 정보 등을 포함할 수 있습니다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.headers(headers -> headers
				.addHeaderWriter(new StaticHeadersWriter("X-Custom-Security-Header","header-value"))
			);
		return http.build();
	}
}

 

Headers Writer

  •  사용자는 사용자 정의 HeadersWriter 인스턴스를 생성하거나 HeadersWriter의 사용자 정의 구현을 제공할 수 있습니다
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.headers(headers -> headers
				.addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN))
			);
		return http.build();
	}
}
 

DelegatingRequestMatcherHeaderWriter 

  • 특정 조건에 맞는 요청에만 HTTP 헤더를 추가하고 싶을 때의 상황을 설명합니다.
  • DelegatingRequestMatcherHeaderWriter는 특정 요청에 대해서만 헤더를 작성하는 기능을 제공합니다.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		RequestMatcher matcher = new AntPathRequestMatcher("/login");
		DelegatingRequestMatcherHeaderWriter headerWriter =
			new DelegatingRequestMatcherHeaderWriter(matcher,new XFrameOptionsHeaderWriter());
		http
			// ...
			.headers(headers -> headers
				.frameOptions(frameOptions -> frameOptions.disable())
				.addHeaderWriter(headerWriter)
			);
		return http.build();
	}
}
 
 
 
 
출처