vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

Angularのセキュリティ対策について調べてみる その2

はじめに

前回の続きです。

今回はContent security policy(CSP)、Offline template compilerについて追いかけてみました。

Content security policy(CSP)について

CSPは、画像やJavascriptなどのファイルを読み込むことのできるサイト(ドメイン)を指定するためのものです。

もしこれらのファイルを同一ドメインのみから読み込む場合、CSPで同一ドメインのみに制限をかけることで、
万一クロスサイトスクリプティング(XSS)の対応漏れがあったとしても、
(外部のスクリプトファイルが無効なため)危険を回避できる可能性が高まります。

で、このCSPを有効にするには、Webサーバーから Content-Security-Policy HTTP ヘッダを返す必要があります。

今回はSpring boot(Spring Framework)を使用するため、
Spring Securityで設定を行うことになります。

Spring Securityを使用する

※今回はあくまでCSPの確認を行うことができれば良い。という方針で設定しています。
Spring Securityの正しい使い方については公式ドキュメントや本記事の参照リンクなどを確認してください。

今回は以前作成したプロジェクトを流用することにします。

といってもやっているのはコントローラークラスのルーティング設定ぐらいですが。

ではまずSpring Securityを使えるようにしてみます。

build.gradle

buildscript {
    ext {
        springBootVersion = '1.5.4.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}
dependencies {
    compile('org.springframework.boot:spring-boot-starter-security')
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}
  • dependenciesにSpring Securityを追加するだけです。
    これを起動すると、Spring Securityが有効になってトップページからログインを求められるようになります。
    (ユーザー名: User、パスワードはSpring bootのプロジェクト起動中にログに出力されます)

Configクラスの作成

このままだとログインが必要ないページでも全てログインしないと表示できなくなるので問題です。
また、(今回は使用しませんが)ユーザーのパスワードが、ビルドごとに変更されるのも不便ですね。

このような設定を行うため、xmlファイルまたはclassを作成します。
(今回はclassを使用することにしました)

WebSecurityConfig.java

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 1. ユーザーの設定.
        auth
                .inMemoryAuthentication()
                .withUser("user")
                .password("1234")
                .roles("USER");
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        // 2. ページの閲覧権限の設定.
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/menulist").authenticated();
    }
}
  • Spring SecurityのConfigクラスとして認識させるためには、 WebSecurityConfigurerAdapter の継承、
    @Configuration 、 @EnableWebSecurity の付与が必要です。
  • 1でユーザー名、パスワードを設定し、2でページ(トップ、/menulist)の閲覧権限を指定しています。
  • (上記では仮に /menulist をログイン中でしか表示できないようにしていますが)
    今回は全てのユーザーに表示して良いページしかないため、1、2は無くても問題ありません。

X-Frame-Options

実はこの状態で下記のような iframe を含んだHTMLを表示させようとしても、
iframe がブロックされ、表示されません。

main-page.component.html

< img src='https://pbs.twimg.com/media/DC_pK4OUMAAmy_B.jpg' />
< img src='assets/img/sake.jpg' />
< iframe srcdoc="< img src='https://pbs.twimg.com/media/DC_pK4OUMAAmy_B.jpg' />">< /iframe>

それは、Spring Securityではデフォルトで X-Frame-Options がDENYに設定され、iframeが無効になっているためです。

有効にするためにはConfigクラスの configure(HttpSecurity http) で設定を変更します。

WebSecurityConfig.java

〜省略〜
    @Override
    public void configure(HttpSecurity http) throws Exception {
        〜省略〜
        http
                .headers()
                .frameOptions()
                .sameOrigin();
    }
}
  • これで同一ドメインから追加された iframe は有効になり表示されるようになります。
  • ただし iframe が不要なのであればDENYのままの方が良いですね。

CSPの設定

さていよいよCSPです。
こちらはデフォルトでは有効にならないため、 configure(HttpSecurity http) で設定を行います。

WebSecurityConfig.java

〜省略〜
    @Override
    public void configure(HttpSecurity http) throws Exception {
    〜省略〜
        http
            .headers()
            .contentSecurityPolicy("script-src 'self' 'unsafe-eval'; img-src 'self';");
    }
}
  • スクリプト、画像を同一ドメインのみ有効にしています。
  • 各要素は ; (セミコロン)で区切られます。
  • AngularでCSPを使用する場合、'self'以外に'unsafe-eval'(文字列からコードを生成する eval() や類似メソッドの使用許可)を指定しておく必要があります。
    これが抜けていると、 vendor.bundle.js でエラーが発生し、ページが表示されません。
  • スクリプト、画像の他にドメインを制限したい場合は下記などを参照してください。
    Content Security Policy (CSP) - Web セキュリティ - MDN

Offline template compilerについて

【個人的理解に基づく意訳】

Offline template compilerを使うことで、Template Injectionと呼ばれる脆弱性(下記参照)の回避とパフォーマンスの向上が見込まれます。

プロダクトレベル、特にテンプレートがユーザーデータを含む場合、動的にテンプレートを生成するのではなく、
Offline template compilerを使ってください。

Angularではテンプレートを信頼しているため、動的に生成したテンプレートに含まれる問題に対して組み込みのセキュリティ機能が働かないためです

動的にフォームを生成する必要がある場合は、Dynamic Formsのガイドページを参照してください。

ということですので、Dynamic Formsについてもおいおい調べてみたいと思います(そんなのばっかりですね。。。)。

サーバー側のXSS対策について

【個人的理解に基づく意訳】

Angularで構築したWebアプリケーションに、サーバー側からテンプレート言語(Spring Frameworkだと JSP や Thymeleaf ?)を使ってテンプレートのコードを入れてしまうと、
XSSによってWebアプリケーションが自由にコントロールされてしまう危険があります。

これを防ぐため、サーバー側からテンプレート言語を使って渡す値をエスケープ処理し、 サーバー側でAngularのテンプレートを生成するのを避けてください。

フロント側はAngularに任せて、サーバー側からAngularCLIで生成したHTMLなどにあれこれするのはやめてね。ということでしょうか。

参照