TL;DR
- X-Content-Type-Options
- MIME スニッフィングの無効化
- X-Frame-Options(XFO)
- フレーム表示を制限しクリックジャッキングを予防
- X-XSS-Protection
- XSSフィルタの有効/無効
- Content-Security-Policy (CSP)
- XSSなどの攻撃を軽減するセキュリティレイヤー
- Strict-Transport-Security (HSTS)
- HTTP の代わりに HTTPS による通信を行い中間者攻撃を低減
- Public-Key-Pins (HPKP)
- 偽造された証明書による中間者攻撃を低減
X-Content-Type-Options
MIME スニッフィングを無効化して、Content-Type で指定したタイプを強制的に使用させる。
IE は Content-Type だけでなくURLやコンテンツの内容からコンテンツタイプを独自判断する。
これにより Content-Type の指定が無視される結果となり、
例えば Content-Type: text/plain
であっても、HTML と解釈して中身のスクリプトを実行してしまうといったことが起こり得た。
この動作を抑止するには以下のレスポンスヘッダを付ける。
X-Content-Type-Options: nosniff
これは IE8以降から有効。 本ヘッダの弊害は少ないので常に付けておくのがベター。
X-Frame-Options(XFO)
対象のページを <frame>
や <iframe>
<object>
の中に表示することを許可するかどうかを指示する。
クリックジャッキング攻撃を防止する用途で利用できる。
クリックジャッキング攻撃とは対象のページを iframe 内に読み込み、害のなさそうなページの上に透明にして重ねて表示する。 害の無さそうなボタンを押したつもりが、透明にして上に重ねた iframe 内のボタンを押させるといった攻撃。
自身のページを、他ドメインページのフレーム内へ表示することを禁止するには以下のように指定する。
X-Frame-Options: SAMEORIGIN
設定可能なオプションは以下となる。
オプション | 説明 |
---|---|
DENY | フレーム表示を禁止 |
SAMEORIGIN | 同一ドメイン内に限りフレーム表示を許可 |
ALLOW-FROM uri | 指定された生成元に限り、フレーム表示を許可 |
SAMEORIGIN を基本とし、可能な場合には DENY にするのがベター。
X-XSS-Protection
IE8以降に導入された、XSSによる攻撃を緩和する XSS Filter 機能の有効/無効を設定する。
IEの場合は、XSS攻撃を検出すると「見込まれるクロスサイト・スクリプト攻撃を防ぐために、このページを変更しました」というアラートが出る。
この時対象のスクリプト部分を #
で置き変えて無効化する。
通常 XSS Filter 機能はブラウザのデフォルトで有効に設定されているはず(Chrome や Safari にも同様の機能がある)。
XSS Filter 機能を有効化するにはレスポンスヘッダーにて以下のように指定する。
X-XSS-Protection: 1; mode=block
以下の設定が可能。
設定 | 説明 |
---|---|
X-XSS-Protection: 0 | XSS Filter 機能を無効化する |
X-XSS-Protection: 1 | XSS Filter 機能を有効化する |
X-XSS-Protection: 1; mode=block | XSSを検知した場合にブラウザ上の表示をブロック(空表示)する |
ブラウザのデフォルトで有効に設定されているはずだが、設定する場合は X-XSS-Protection: 1; mode=block
で良いと思う。
Content-Security-Policy (CSP)
昔は X-Content-Security-Policy
であったが、最新ブラウザでは接頭辞のない Content-Security-Policy
がサポートされていく。
ページで読み込むことを許可するリソースを制御する XSS対策機能を設定する。
例えば以下のようにすると、自ドメインと指定したドメイン(google-analytics)のみの読み込みを許可する。
Content-Security-Policy: default-src 'self' www.google-analytics.com
スクリプトの実行に以下の制約が設けられる
- インラインスクリプトの実行禁止(HTML中の
<script>・・・</script>
) - スクリプト読み込み先の制限(
<script src="URI"></script>
の URI は自ドメインまたはホワイトリストに含まれる) eval()
関数などによる文字列をスクリプトとしての実行禁止
default-src
はデフォルト設定を表し、script-src
はスクリプト関連の権限を制御するディレクティブ、 style-src
で style 要素関連の権限を制御するディレクティブなどいろいろあり、個別に制約定義できる。
'self' とあるのは参照元リストで、以下が利用できる。
設定 | 説明 |
---|---|
'none' | 指定なし |
'self' | 同一オリジン。サブドメインは除外 |
'unsafe-inline' | インライン JavaScript および CSS を許可 |
'unsafe-eval' | eval などの text-to-JavaScript の仕組みを許可 |
複雑なので詳細はこちら。
制約違反が有った場合、指定したURLに JSON形式で報告させることができる。
Content-Security-Policy: default-src 'self'; report-uri https://example.com/csp-report
report-uri
にレポートが送信されるようになる。制約を適用せず、監視だけを目的とする場合には Content-Security-Policy-Report-Only
を使う。
Content-Security-Policy-Report-Only: default-src 'self'; report-uri https://example.com/csp-report
ページ単位の指定の場合には meta タグでも可。
<meta http-equiv="Content-Security-Policy" content="default-src https://cdn.example.net; child-src 'none'">
Upgrade-Insecure-Requests
HTTPS 時にページ中にHTTPのリンクなどがあった場合、ブラウザで Mixed Contents の警告となる。
常時SSLへの移行仮定で、ページ中にHTTPのリンクが残っている場合に、HTTPS と読み替えてくれる機能がある。
クライアントがこの機能に対応している場合には、リクエストヘッダに以下が入っている。
Upgrade-Insecure-Requests: 1
クライアントが対応している場合には以下のレスポンスヘッダにてクライアントに読み替え指示できる。
Content-Security-Policy: upgrade-insecure-requests
Strict-Transport-Security (HSTS)
HTTP Strict Transport Security(HSTS)は、HTTPS 通信を強制するようブラウザに伝達するセキュリティ機能。
ブラウザは初めて HTTPS 接続をしたときに Strict-Transport-Security ヘッダがあると、Strict Transport Security 機能が有効になり、そのサイトへの接続は自動的に HTTPS 接続を試みるようになる(HTTP接続でこのヘッダが付いていたとしてもブラウザは無視する)。
常時SSLの場合でも、初めてのサイトへの接続にはユーザ利便性のため HTTP 接続を許容する場合がある(直後にHTTPSへリダイレクトする)。 この場合に HSTS を設定することで、それ以降の接続で自動的に HTTPS が使われることになる。
たまに HTTP 接続しているのに、なぜか HTTPS 接続になると困っている人がいるが、たいていこのヘッダが原因。
設定には以下のヘッダを指定する。
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
各オプションは以下
オプション | 説明 |
---|---|
max-age | HSTS設定をブラウザが記憶する秒数 |
includeSubDomains | HSTS設定をサブドメインに適用する場合に指定 |
preload | HSTSプリロードリスト登録時に必要 |
HSTS プリロードリストは HSTS Preload List Submission で管理されており、申請により特定ドメインの接続がはじめから HTTPS 接続となる対象のリスト。
主要ブラウザではここで管理されているリストの内容で、初回の HTTP アクセスも自動的に HTTPS 接続を強制することができる。
Public-Key-Pins (HPKP)
Public Key Pinning Extension for HTTPは、偽造された証明書による中間者攻撃を防ぐための機能。
レスポンスヘッダに本物の証明書の公開鍵のハッシュ値を Base64 にして付けておくことでブラウザが実際のサーバから送信されてくる証明書の公開鍵データのハッシュ値と比較して、不正な証明書の利用を検知する。
以下のように指定する。
Public-Key-Pins: pin-sha256="base64=="; max-age=5184000; includeSubDomains; report-uri="https://www.example.net/hpkp-report"
base64== の箇所に公開鍵ダイジェストの Base64 エンコード値を設定する。
report-uri
で不正な証明書の利用を検知した場合に指定したURLに JSON形式で報告させることができる。
Public-Key-Pins-Report-Only にするとレポートのみを行う。
Public-Key-Pins-Report-Only: pin-sha256="base64=="; max-age=5184000; includeSubDomains; report-uri="https://www.example.net/hpkp-report"
設定
フロントに Apache がいれば Apache で設定すれば良い。
サーブレットの場合は、tomcat で事前定義されたサーブレットフィルター HttpHeaderSecurityFilter
が予め用意してあるのでこれを使うとよい。
- Strict-Transport-Security
- X-Frame-Options
- X-Content-Type-Options
以下のような実装になっている。
package org.apache.catalina.filters; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; /** * Provides a single configuration point for security measures that required the * addition of one or more HTTP headers to the response. */ public class HttpHeaderSecurityFilter extends FilterBase { private static final Log log = LogFactory.getLog(HttpHeaderSecurityFilter.class); // HSTS private static final String HSTS_HEADER_NAME = "Strict-Transport-Security"; private boolean hstsEnabled = true; private int hstsMaxAgeSeconds = 0; private boolean hstsIncludeSubDomains = false; private String hstsHeaderValue; // Click-jacking protection private static final String ANTI_CLICK_JACKING_HEADER_NAME = "X-Frame-Options"; private boolean antiClickJackingEnabled = true; private XFrameOption antiClickJackingOption = XFrameOption.DENY; private URI antiClickJackingUri; private String antiClickJackingHeaderValue; // Block content sniffing private static final String BLOCK_CONTENT_TYPE_SNIFFING_HEADER_NAME = "X-Content-Type-Options"; private static final String BLOCK_CONTENT_TYPE_SNIFFING_HEADER_VALUE = "nosniff"; private boolean blockContentTypeSniffingEnabled = true; @Override public void init(FilterConfig filterConfig) throws ServletException { super.init(filterConfig); // Build HSTS header value StringBuilder hstsValue = new StringBuilder("max-age="); hstsValue.append(hstsMaxAgeSeconds); if (hstsIncludeSubDomains) { hstsValue.append(";includeSubDomains"); } hstsHeaderValue = hstsValue.toString(); // Anti click-jacking StringBuilder cjValue = new StringBuilder(antiClickJackingOption.headerValue); if (antiClickJackingOption == XFrameOption.ALLOW_FROM) { cjValue.append(':'); cjValue.append(antiClickJackingUri); } antiClickJackingHeaderValue = cjValue.toString(); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (response.isCommitted()) { throw new ServletException(sm.getString("httpHeaderSecurityFilter.committed")); } // HSTS if (hstsEnabled && request.isSecure() && response instanceof HttpServletResponse) { ((HttpServletResponse) response).setHeader(HSTS_HEADER_NAME, hstsHeaderValue); } // anti click-jacking if (antiClickJackingEnabled && response instanceof HttpServletResponse) { ((HttpServletResponse) response).setHeader( ANTI_CLICK_JACKING_HEADER_NAME, antiClickJackingHeaderValue); } // Block content type sniffing if (blockContentTypeSniffingEnabled && response instanceof HttpServletResponse) { ((HttpServletResponse) response).setHeader(BLOCK_CONTENT_TYPE_SNIFFING_HEADER_NAME, BLOCK_CONTENT_TYPE_SNIFFING_HEADER_VALUE); } chain.doFilter(request, response); } @Override protected Log getLogger() { return log; } @Override protected boolean isConfigProblemFatal() { // This filter is security related to configuration issues always // trigger a failure. return true; } public boolean isHstsEnabled() { return hstsEnabled; } public void setHstsEnabled(boolean hstsEnabled) { this.hstsEnabled = hstsEnabled; } public int getHstsMaxAgeSeconds() { return hstsMaxAgeSeconds; } public void setHstsMaxAgeSeconds(int hstsMaxAgeSeconds) { if (hstsMaxAgeSeconds < 0) { this.hstsMaxAgeSeconds = 0; } else { this.hstsMaxAgeSeconds = hstsMaxAgeSeconds; } } public boolean isHstsIncludeSubDomains() { return hstsIncludeSubDomains; } public void setHstsIncludeSubDomains(boolean hstsIncludeSubDomains) { this.hstsIncludeSubDomains = hstsIncludeSubDomains; } public boolean isAntiClickJackingEnabled() { return antiClickJackingEnabled; } public void setAntiClickJackingEnabled(boolean antiClickJackingEnabled) { this.antiClickJackingEnabled = antiClickJackingEnabled; } public String getAntiClickJackingOption() { return antiClickJackingOption.toString(); } public void setAntiClickJackingOption(String antiClickJackingOption) { for (XFrameOption option : XFrameOption.values()) { if (option.getHeaderValue().equalsIgnoreCase(antiClickJackingOption)) { this.antiClickJackingOption = option; return; } } throw new IllegalArgumentException( sm.getString("httpHeaderSecurityFilter.clickjack.invalid", antiClickJackingOption)); } public String getAntiClickJackingUri() { return antiClickJackingUri.toString(); } public boolean isBlockContentTypeSniffingEnabled() { return blockContentTypeSniffingEnabled; } public void setBlockContentTypeSniffingEnabled( boolean blockContentTypeSniffingEnabled) { this.blockContentTypeSniffingEnabled = blockContentTypeSniffingEnabled; } public void setAntiClickJackingUri(String antiClickJackingUri) { URI uri; try { uri = new URI(antiClickJackingUri); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } this.antiClickJackingUri = uri; } private static enum XFrameOption { DENY("DENY"), SAME_ORIGIN("SAMEORIGIN"), ALLOW_FROM("ALLOW-FROM"); private final String headerValue; private XFrameOption(String headerValue) { this.headerValue = headerValue; } public String getHeaderValue() { return headerValue; } } }
web.xml で以下のように読み込む。
<filter> <filter-name>HttpHeaderSecurityFilter</filter-name> <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class> </filter> <filter-mapping> <filter-name>HttpHeaderSecurityFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Spring Security の場合は以下がサポートされている。
- X-Content-Type-Options
- X-Frame-Options(XFO)
- X-XSS-Protection
- Strict-Transport-Security (HSTS)
@EnableWebSecurity @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .headers() .cacheControl() .contentTypeOptions() .hsts() .frameOptions() .xssProtection() .and() ...; } }