我们发布了一份仅总结要点、便于阅读的摘要文章。推荐想要先了解概况,或者时间不充裕的人阅读。
2017 年 12 月 1 日,Struts2 的安全更新已发布。 发布前,邮件列表中就有消息称这与 Jackson(Java 中流行的 JSON 库)的漏洞有关,笔者所在公司的内部系统和工具中也使用了 Jackson,因此笔者也一直关注具体内容。
实际发布的内容修复了以下两个安全问题。只有 S2-055 受到 Jackson 组件 jackson-databind 漏洞的影响。
REST 插件此前已内置了使用 JSON-lib 的 handler 和使用 Jackson 的 handler,似乎用户可以自行选择。 S2-054 将默认 handler 切换为 Jackson,S2-055 进一步将旧版 Jackson 更新至最新版,这似乎是本次修复的全貌。
那么,CVE-2017-7525 到底是一个什么样的漏洞?笔者平时在 Java 中处理 JSON 时也使用 Jackson,因此在 12 月 2、3 日的周末调查了这个问题,本文便是其成果。
用于验证示例代码的笔者环境:
Adam Caudill 先生的博客中公开了 CVE-2017-7525 的说明。
用笔者自己的话大致总结一下:jackson-databind 提供了将 JSON 映射到 Java 对象的功能(ObjectMapper 类)。
这里通过调用 ObjectMapper.enableDefaultTyping(),可以根据 JSON 中自行嵌入的类名进行映射。
“可以指定输入 JSON 中的类名”这一点,可能已经有人感到不安,而 CVE-2017-7525 正是这种不安被言中了。
在进入漏洞说明之前,先解释一下为什么当初要实现这样的功能。
请参考以下示例代码了解 jackson-databind 反序列化的基本使用方法。(本文中的 Jackson 示例代码使用了 Groovy。使用 @Grab 可以方便地切换 jackson-databind 的版本。)
在上述示例代码中,直接可以将 "animal" 键映射到 Animal 类。 那么,在以下情况下又会如何呢?```java class Zoo { Animal animal; }
abstract class Animal { String name; protected Animal() { } }
class Dog extends Animal { double barkVolume; Dog() { } }
class Cat extends Animal { boolean likesCream; int lives; Cat() { } }
在这种配置下,"animal"键的内容会出现指向Dog类和指向Cat类两种情况。因此,需要额外的信息来确定具体使用哪个类进行映射。
为了解决这个问题,jackson-databind内置了可以将映射类名嵌入JSON的自有处理机制。
例如如下方式,将"animal"键的内容改成数组,并在第一个元素中指定类名。```
{"animal":["Dog",{"name":"dog1","barkVolume":1.2}]}
これにより ObjectMapper.readValue() は "animal" キーの中身が Dog クラスだと認識してマッピングを行います。
もちろん、そのままでは "animal" キーの中身がもともと配列だったのか、jackson-databind独自のクラス名情報が含まれたものなのか、判別できません。
これを切り替えるのが ObjectMapper.enableDefaultTyping() メソッドになります。
他にも @JsonTypeInfo アノテーションをクラスに定義する方法もあります。詳細は以下のJacksonドキュメントを参照してください。
実際に ObjectMapper.enableDefaultTyping() メソッドを使ったサンプルコードを次に示します。
以上見てきたように、クラス名に続いてそのプロパティをJSONで与えることで、ある程度制限はあるものの、任意のクラスを任意のプロパティで生成することが可能となります。 これを悪用したのがCVE-2017-7525の脆弱性で、きっかけとなったのは恐らく次のレポートと思われます。
JacksonなどJavaでよく使われているserialize/deserializeライブラリについて、クラス名などの操作で任意コード実行につながる危険性がレポートされており、実際にどのようなクラスが危険か具体的なクラス名がリストアップされています。
これを受けてのものか分かりませんが、日付的には上記リポジトリの1st commitの直後に、jackson-databind で以下のIssueが立てられ、対応が始まりました。
実際にこの脆弱性を突くようなJSONデータとJavaコードはどのようなものでしょうか? このIssueで対応された jackson-databind 2.8.9 のテストコードにヒントがあります :
このテストコードを元に、動作確認できるよう調整したサンプルコードを次に示します。
@Grab で 2.8.9 を指定して実行すると your jackson version IS SAFE to CVE-2017-7525 と表示されます。これは 2.8.9 の修正によりインスタンス化されるクラス名指定でblacklist検査が追加されたことによります。
ここで @Grab に 2.8.8 を指定して実行すると、以下のように出力されます。```
your jackson version MAY NOT BE SAFE to CVE-2017-7525
com.fasterxml.jackson.databind.JsonMappingException: N/A
at [Source:
{
"id" : 124,
"obj" : [
"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
{
"transletBytecodes" : [ "AAIAZQ==" ],
"transletName" : "a.b",
"outputProperties" : { }
}
]
}
; line: 9, column: 28] (through reference chain: Bean1599["obj"]->com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl["outputProperties"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:277)
(...)
at org.codehaus.groovy.tools.GroovyStarter.main(GroovyStarter.java:128)
Caused by: java.lang.NullPointerException
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$1.run(TemplatesImpl.java:401)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.defineTransletClasses(TemplatesImpl.java:399)
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:451)
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486)
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties(TemplatesImpl.java:507)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.fasterxml.jackson.databind.deser.impl.SetterlessProperty.deserializeAndSet(SetterlessProperty.java:116)
... 30 more
null
在输出结果中,有一行显示 `at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$1.run(TemplatesImpl.java:401)`。
在这个示例代码中,虽然抛出了NullPointerException,但从方法名来看,可以推测出可能发生了某种含有副作用的过程。
要构造出能够成功执行代码的JSON,看来还需要进一步调查。
本文在这里暂且只介绍到这,但如果其他网站发布了进一步调查的文章,我们也会在此处进行补充。
那么,关键的黑名单是在哪里实现的呢?就是以下这个类。
* https://github.com/FasterXML/jackson-databind/blob/jackson-databind-2.8.9/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java#L51
实际上,在2.8.9版本时,这个黑名单似乎存在遗漏。该问题就是CVE-2017-15095。
### 改善黑名单的CVE-2017-15095应对
关于黑名单遗漏的改善,首先在 https://github.com/FasterXML/jackson-databind/issues/1680 中添加了 `s.add("com.sun.rowset.JdbcRowSetImpl");`。
在随后发布了2.9.0之后,又在 https://github.com/FasterXML/jackson-databind/issues/1737 中添加了以下黑名单检查。```java
// [databind#1737]; JDK provided
s.add("java.util.logging.FileHandler");
s.add("java.rmi.server.UnicastRemoteObject");
// [databind#1737]; 3rd party
s.add("org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor");
s.add("org.springframework.beans.factory.config.PropertyPathFactoryBean");
s.add("com.mchange.v2.c3p0.JndiRefForwardingDataSource");
s.add("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource");
こうして 2.8.10 / 2.9.1 がリリースされ、CVE-2017-15095 への対応完了となっています。
2.8.10 でのblack list動作をチェックするテストコード: