SpringBootを使うときに最低限やっておきたいセキュリティ対策


SpringBootを使うと書くコード量が激減して大変便利ですが、デフォルトの定義に頼りすぎると意外なところで内部情報をサクッと暴露してくれたりするので油断できません。

そこで、SpringBootを弄ってみて気がついたリスキーな箇所とその対応策を挙げていきたいと思います。
（あくまで気がついた範囲なので、全部カバーできているわけではないです！）

# Spring Boot Actuator編

Actuatorを有効化すると様々な情報がREST APIとして提供されるので大変便利ですが、内部情報を晒したりシャットダウンすることも可能になるので外部からは見えないように対策したいところです。

## 使用するなら外部からはアクセスできないようにする

* SpringBootアプリケーションがlistenするポート番号を直接外部に公開しない
* アプリケーションへのアクセスは、Webサーバを経由するようにし、且つActuatorのパスに対するアクセスをブロックする

```Actuatorが提供するREST_API
/shutdown
/trace
/autoconfig
/configprops
/metrics
/metrics/*
/mappings
/info
/health
/env
/env/*
/beans
/dump
```

例えば、nginxなら以下のように特定のパスへのリクエストを拒否するようにすれば良いでしょう。

```nginx.confより一部抜粋
        ・・・
        location /health {
            return 404;
        }
        ・・・
```

## X-Application-ContextをHTTPレスポンスヘッダに付与させない

actuatorを有効にすると、全てのHTTPリクエストに対するレスポンスヘッダに"X-Application-Context"ヘッダが付与されるようになります。

このヘッダ情報は、アプリケーションを稼働しているProfile情報とListenポート番号を暴露するため、できれば無効化しておきたいところです。

### 対策案1. SpringBootの設定で無効化する

SpringBoot 1.1.5以上であれば`application.properties`に以下の設定を追加することで、無用なヘッダ情報が付加されるのを防ぐことができます（YAMLでも可）。

```application.properties
management.add-application-context-header=false
```

### 対策案2. Webサーバで無効化する

本来であればSpringBoot側の設定変更でどうにかしたいところですが、古いバージョンを使っていたり、何らかの事情でどうしてもアプリケーション側で対応できない場合は、プロキシとして使用しているWebサーバ側での対処を検討します。
利用するWebサーバに依存するところではありますが、nginxであれば`HttpHeadersMoreModule`、Apacheであれば`mod_headers`を利用して`X-Application-Context`ヘッダ情報を書き換えるよいでしょう。

## 参考
http://docs.spring.io/spring-boot/docs/1.2.5.RELEASE/reference/htmlsingle/#production-ready
https://github.com/spring-projects/spring-boot/issues/1308
http://wiki.nginx.org/HttpHeadersMoreModule
http://httpd.apache.org/docs/2.2/en/mod/mod_headers.html
http://httpd.apache.org/docs/2.4/en/mod/mod_headers.html


# RestController編

## デフォルトのエラーレスポンスは使わない

`@RestController`を使用したControllerへの不適切なアクセスを行うと、`ErrorAttribute`インタフェースの実装クラスを返します。
SpringBootは、特に設定をしないとデフォルトで`org.springframework.boot.autoconfigure.web.DefaultErrorAttributes`を返すので、実装次第では内部情報を推測できる情報が暴露されることになります。

```エラー例.json
{
   "timestamp" : 1426615606,
   "exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
   "status" : 400,
   "error" : "Bad Request",
   "path" : "/welcome",
   "message" : "Required String parameter 'name' is not present"
}
```

出典：http://stackoverflow.com/questions/29106637/modify-default-json-error-response-from-spring-boot-rest-controller

設定次第ではスタックトレースまで吐き出してくれるので、せめてエラーの事実までに留めて詳細情報は表示させたくないところです。

### 対策案. ErrorAttributesを独自に実装する

そのアプリケーションの特性に応じたレスポンスを設計するのがよいと思いますが、ここでは`DefaultErrorAttributes`を継承したカスタムクラスを作って、最低限外部には晒してほしくない情報を返さないようにしてみました。

```CustomErrorAttributes.java
public class CustomErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
                                                  boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
        errorAttributes.put("timestamp", new Date());
        addStatus(errorAttributes, requestAttributes);
        // 単に詳細情報を出力させないようにしただけです
        // addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
        // addPath(errorAttributes, requestAttributes);
        return errorAttributes;
    }

    // 継承元の実装をそのまま踏襲しています。
    private void addStatus(Map<String, Object> errorAttributes,
                           RequestAttributes requestAttributes) {
        Integer status = getAttribute(requestAttributes,
                "javax.servlet.error.status_code");
        if (status == null) {
            errorAttributes.put("status", 999);
            errorAttributes.put("error", "None");
            return;
        }
        errorAttributes.put("status", status);
        try {
            errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
        }
        catch (Exception ex) {
            // Unable to obtain a reason
            errorAttributes.put("error", "Http Status " + status);
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
        return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
    }
}
```

Bean定義を上記で作成したクラスで上書きます。

```Hogeconfig.java

    ・・・
    @Bean
    public ErrorAttributes errorAttributes() {
        return new CustomErrorAttributes();
    }
    ・・・

```

内部情報を推測できる情報は返さなくなったので、デフォルトよりはマシになったと言えるでしょう。

```CustomErrorAttributesの返却例.json
{
    timestamp: 1440346172908
    status: 404
    error: "Not Found"
}
```