Search This Blog

2013-05-01

Oracle JDK的UTF8解码缺陷

1 问题描述

最近在写编解码方面的一篇文章,写到实现的安全性的时候想举个例子,想起以前看到过Tomcat有过URL解析方面的安全漏洞,导致JSP文件直接被下载,没找到这个漏洞,却发现一个Tomcat目录遍历漏洞的报道:Directory traversal CVE-2008-2938 http://tomcat.apache.org/security-6.html
    Originally reported as a Tomcat vulnerability the root cause of this issue is that the JVM does not correctly decode UTF-8 encoded URLs to UTF-8.
    workaround fixing: CoyoteAdapter.java diff - svn.apache.org

报道说这是JVM的bug,但是描述不够详细,从修复代码也看不出修复了什么问题。我想弄清楚具体的原因,这样就可以在那篇文章提供确切的例子,所以继续看了些相关的报道。

2 问题查找

看到Apache Tomcat <= 6.0.18 UTF8 Directory Traversal Vulnerability - www.securityfocus.com里边有个例子URL,在Oracle JDK 7下解码那个URL试了试。我期待的结果是"..",生成的却是"??"(这里的?其实是\uFFFD)。又写了一些测试,还是这结果,猜测是被替换了。看了点源码,发现了问题所在。

2.1 测试代码

Pre[-]
System.out.println(java.net.URLDecoder.decode(
        "http://www.target.com/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/foo/bar", "UTF-8"));
System.out.println(String.format("binary string, .=%s, 0xc0=%s, 0xae=%s", Integer.toBinaryString('.'),
        Integer.toBinaryString(0xc0), Integer.toBinaryString(0xae)));
System.out.println(new String(new byte[] { (byte) 0xc0, (byte) 0xae }, "UTF-8"));
//
Charset charset = Charset.forName("UTF-8");
CharsetDecoder charsetDecoder = charset.newDecoder().onMalformedInput(CodingErrorAction.REPLACE)
        .onUnmappableCharacter(CodingErrorAction.REPLACE); // java.lang.StringCoding.StringDecoder.StringDecoder(Charset cs, String rcn) do it in this way
System.out.println(String.format("charset=%s, charsetDecoder=%s, charsetDecoder.replacement=%s",
        charset.getClass().getName(), charsetDecoder, charsetDecoder.replacement()));
charsetDecoder = charset.newDecoder().onMalformedInput(CodingErrorAction.REPORT)
        .onUnmappableCharacter(CodingErrorAction.REPORT);
ByteBuffer in = ByteBuffer.wrap(new byte[] { (byte) 0xc0, (byte) 0xae });
CharBuffer out = charsetDecoder.decode(in);
System.out.println(out.array());

2.2 输出结果

Pre[-]
D:\>"C:\Program Files\Java\jdk1.7.0_10\bin\java.exe" TempTest
http://www.target.com/????/????/????/foo/bar
binary string, .=101110, 0xc0=11000000, 0xae=10101110
??
charset=sun.nio.cs.UTF_8, charsetDecoder=sun.nio.cs.UTF_8$Decoder@1318bd3c, charsetDecoder.replacement=?
Exception in thread "main" java.nio.charset.MalformedInputException: Input length = 1
        at java.nio.charset.CoderResult.throwException(CoderResult.java:277)
        at java.nio.charset.CharsetDecoder.decode(CharsetDecoder.java:798)
        at TempTest.main(TempTest.java:42)

D:\>"C:\Program Files (x86)\Java\jdk1.5.0_06\bin\java.exe" TempTest
http://www.target.com/../../../foo/bar
binary string, .=101110, 0xc0=11000000, 0xae=10101110
.
charset=sun.nio.cs.UTF_8, charsetDecoder=sun.nio.cs.UTF_8$Decoder@61de33, charsetDecoder.replacement=?
.

3 结论

3.1 旧版本的JDK果然有问题

非法的UTF-8编码数据被当成正确的解码,即便是在CodingErrorAction.REPORT模式下也是这样。
CVE-2008-2938是2008年报道的问题,这时候JDK6都已经出来一年了。UTF-8, a transformation format of ISO 10646 # Security Considerations - tools.ietf.org明确提及这种问题的防范措施,这个文档的上一个版本是http://tools.ietf.org/html/rfc2279(1998年1月出的),也有这部分内容。

3.2 新版本的JDK也有点问题

)String类默认使用CodingErrorAction.REPLACE,并且无法自由配置,导致错误的输入跟正确的输入混淆。String类是个基础类,影响范围极广,包括上面例子里的java.net.URLDecoder。
CharsetDecoder允许通过方法调用及继承并覆盖方法来自定义规则,这个设计还是很灵活的。在需要严格验证用户输入数据的情况下,使用自行配置的CharsetDecoder来解码数据,当然还得使用新版本的JDK。

)最近迁移到了Linux,之前在Windows上压缩了一些包括中文名的目录及文件,在Linux上解压之后都是乱码。本来想写个Java程序解决问题的,悲剧的是一用new String或者String.getBytes(charset),很多字符就被替换为\uFFFD,又不想写C代码,只得作罢。
顺便一提,可以试试convmv,有一部分可能转不了,bash脚本+iconv配合一下就可以了。

=文章版本=

20130323
20130424
20130506 改为html格式

No comments:

Post a Comment