Jacksonの脆弱性に対するSpring Bootへの影響について。
2017/12/1にStruts2でJacksonの脆弱性(CVE-2017-7525)対応が行われて話題になっていたらしい。
JacksonはSpring Bootでも使われているので、試してみた。
どういう脆弱性かと言うと。
JacksonにはJSONをJavaのクラスに変換するObjectMapperというクラスがある。
これに関して、JSONの中にJavaのクラス名を書いておいてそれをインスタンス化する機能があるらしい。
が、(何でもかんでもインスタンス化されるとまずいので)インスタンス化できないクラスをブラックリストとして持っているようだ。
で、そのブラックリストに漏れがあったのかな?
生成するインスタンスによっては、そこにバイトコードを仕込んだりして任意のコードを実行する事が出来てしまうようだ。
この機能が有効になるのは、ObjectMapperインスタンスのenableDefaultTypingメソッドを呼んでおくか、JSONから変換するクラスに@JsonTypeInfoアノテーションを付けている場合。
で、Spring Bootの場合はデフォルトではObjectMapperのenableDefaultTypingメソッドは呼ばれていないようなので基本的に大丈夫っぽい。
アプリケーションで@JsonTypeInfoアノテーションを使っているかどうかは個別に要確認。
また、jackson-databind 2.8.10でこの脆弱性の対応が行われているが、(2017年12月時点で最新の)Spring Boot
1.5.9はそのバージョンなので大丈夫。
(自分が使い始めたSpring Boot 1.5.6はjackson-databind 2.8.9だった。この脆弱性が半端に修正されたバージョン)
Struts2の場合は、この機能がデフォルトで有効になっているので問題になったのではないかと思う。
参考: SecureSkyTechnologyさんのcve-2017-15095-check.groovy
以下のようなRestControllerを用意する。
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class IssueExampleController { public static class PolyWrapper { public Object v; }
@RequestMapping(path = "/issue/example") public String example(@RequestBody PolyWrapper body) { String message = (body.v == null) ? "null" : String.format("v = (%s)%s", body.v.getClass().getName(), body.v); System.out.println(message); return message; } }
これに以下のようなJSONを送る。
$ curl http://127.0.0.1:8080/issue/example -H 'Content-Type: application/json' -d '{ "v" : [ "java.util.logging.FileHandler", "/tmp/foobar.txt" ] }'
脆弱性の問題が無ければ、以下のようにJSONの配列(Java上はList)として変換される。
v = (java.util.ArrayList)[java.util.logging.FileHandler, /tmp/foobar.txt]
jackson-databind 2.8.9でenableDefaultTypingメソッドを呼んだObjectMapperインスタンスを試してみる。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration public class JacksonConfig { @Bean public Jackson2ObjectMapperBuilder objectMapperBuilder() { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder() { @Override public void configure(ObjectMapper objectMapper) { super.configure(objectMapper); objectMapper.enableDefaultTyping(); } }; return builder; } }
脆弱性確認用のJSONを送ると、以下のようにJSON内に書かれたクラスのインスタンスが生成されている。
v = (java.util.logging.FileHandler)java.util.logging.FileHandler@1c16605
ついでに、/tmp/foobar.txtファイル(JSONの中で記述されていたパス)も作られてた!完全にFileHandlerが実行されてるわ〜orz
jackson-databind 2.8.9で(ObjectMapperのenableDefaultTypingメソッドを呼ぶのではなく)JSONの変換先クラスに@JsonTypeInfoアノテーションを付けてみる。
import com.fasterxml.jackson.annotation.JsonTypeInfo;
public static class PolyWrapper { @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_ARRAY) public Object v; }
↓実行結果
v = (java.util.logging.FileHandler)java.util.logging.FileHandler@7a6d98a5
やはりアウト。
jackson-databind 2.8.10でObjectMapperのenableDefaultTypingメソッドを呼んだり@JsonTypeInfoアノテーションを付けたりすると、
JSON Parser error(HttpMessageNotReadableException)が発生し、以下のようなエラーが返ってきた。
当然、JSON内に書かれたクラス名のインスタンスは生成されない。
buildscript { ext { springBootVersion = '1.5.9.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } 〜
↓実行結果
{ "message" : "invalid JSON","line":1, "column" : 44, "fieldName" : "v", "detail" : "Invalid type definition for type Ljava/util/logging/FileHandler;: Illegal type (java.util.logging.FileHandler) to deserialize: prevented for security reasons" }
ObjectMapperのenableDefaultTypingメソッドを呼ばない・@JsonTypeInfoアノテーションを付けない状態だと、以下のように正常に処理される。
↓実行結果
v = (java.util.ArrayList)[java.util.logging.FileHandler, /tmp/foobar.txt]