低版本java.io.File类00截断分析

2019-12-26 约 63 字 预计阅读 1 分钟

声明:本文 【低版本java.io.File类00截断分析】 由作者 caixiaozhi 于 2019-12-26 12:34:41 首发 先知社区 曾经 浏览数 300 次

感谢 caixiaozhi 的辛苦付出!

之前在测试过程中发现低版本的java环境存在00截断,可以用于实现文件后缀校验绕过。
先考虑一下下面这个案例是否存在任意文件下载:

示例中文件目录path不可被外界控制,但是使用外界可控的字符串taskID进行文件路径拼接。对File类进行实例化时,参数直接使用上述拼接后得到的字符串,且没有进行标准化处理和合法性验证。
可能有人会认为,在动态拼接路径后添加硬编码的文件扩展名,并且在下载文件前,验证文件是否存在,即使taskID中存在“../”等危险字符,考虑到硬编码的文件后缀,代码的执行还是会进入文件不存在的分支,从而避免任意文件下载。

但是真实情况并非如此,该代码段是否会造成任意文件下载,要考虑到应用程序运行时使用的java版本。

下面给出示例:

  • 示例一
    第一个示例运行在java1.6中,假设参数bytes为上文的taskID(外界可控参数),bytes后拼接硬编码的“.txt”。从运行结果可以看到,通过getCanonicalPath函数获取归一化后的真实路径,并不含我们拼接的“.txt”。这是由于参数bytes中含有0x00,且1.6版本的java中,File类对文件路径校验不够严格,最终生成的文件路径丢弃了0x00后的字符。

  • 示例二
    第二个示例运行在java1.8中,可以看到在该环境下,createNewFile函数抛出了异常,无法实现路径穿越。

看过案例后我们再来看一下1.8和1.6的java.io.File有什么不同。(不同操作系统下,FileSystem不同,windows系统使用WinNTFileSystem,类Unix系统下使用UnixFileSystem,下文的fs统一取WinNTFileSystem)

首先明确:java中的String类型不以‘\u0000’作为字符串结束标志,而C/C++中会以0x00等特定字符作为字符数组/串的结束标志。

先看java1.8的File类。
File类的几个构造函数,主要作用是对path和prefixLength这两个成员变量赋值,跟进normalize函数后,发现该函数主要是对文件路径进行处理,主要处理的是“\”和“/”这两个符号,由于这里不是这次分析的重点,就不详细展开。从构造函数可知,若实例化File类的字符串含有‘\u0000’(即ASCII:0x00),则成员变量path中也含有‘\u0000’,同时包含‘\u0000’之后的字符。

以createNewFile函数为例,该函数在真正执行创建文件前,进行了两次检查。第二次检查if(isInvalid())是这次要关注的重点,进入isInvalid函数。

isInvalid函数检查了是否含有‘\u0000’,若含有该字符则返回true,最终导致createNewFile函数抛出IO异常。

File类中大量函数在真正执行文件操作前都调用isInvalid函数进行校验,因此1.8版本中的java中不存在0x00截断问题。不止是File类,java1.8中的FileInputStream、FileOutputStream中,也间接调用了File类的isInvalid函数,提高了文件操作API的安全性。

Java1.6中的File类并没有检查是否含有0x00。

到此为止,可知1.6,1.8两个版本的java.io.File,java层的主要区别在于对文件操作前是否调用isInvalid()函数检查成员变量path中是否含有‘\u0000’。但是并没有解释在创建/删除文件时,为何成员变量中,‘\u0000’后的字符会被丢弃。这里需要进入native层进行分析,这里同样以createNewFile函数为例。

一系列检查通过后,createNewFile函数执行return fs.createFileExclusively(path),这里传入的path即为File类的成员变量path。该方法用于创建文件,逻辑如下:

  1. pathToNTPath函数,将路径转成宽字符类型的字符串(java使用的Unicode编码是宽字符,每个字符长度为2字节)。
  2. isReservedDeviceNameW函数,判断路径是否为系统保留设备名。
  3. 调用 CreateFileW 函数创建文件,使用了 CREATE_NEW 模式,仅仅在不存在该文件时才创建。
  4. 如果已经存在该文件,尽量不抛出异常,而是返回 false,此过程还会尝试读取该文件的属性,失败则抛IO异常。
    在将路径转换成宽字符形式时,由于C/C++中宽字节字符数组/串(wchar_t即WCHAR)会以0x0000为字符串结尾(如下示例)

    导致java层传递进来的String:path如果含有‘\u0000’,在

    WCHAR *pathbuf = pathToNTPath(env, path, JNI_FALSE);
    

    执行之后,pathbuf就不含有‘\u0000’之后的字符了。
    继续执行createFileW时使用了pathbuf参数,创建的文件的真实路径自然不包含‘\u0000’之后的字符。由此出现了00截断现象。

  5. 其他
    以java为开发语言的web应用程序中,大量使用MultipartFile实现文件上传,以下是个测试demo。

    上传文件后抓包修改filename字段,插入byte类型的数据00,发送后服务器报错。


    直接跟进fileupload.util.Streams.checkFileName,可见该函数对文件名进行了校验,若文件名中存在‘\u0000’则抛出异常,减少使用MultipartFile.getOriginalFilename()拼接文件名带来的风险。

    除此之外CommonsMultipartFile实现getOriginalFilename()函数时,返回值filename取最后一个文件分隔符后的字符串,也能缓解上传文件时跨路径上传的风险。

关键词:[‘安全技术’, ‘WEB安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now