从底层代码看待00截断这个漏洞

作为一个或许已经过气了的漏洞,已经很少在实战场景中再去使用他了。但是其背后的原理,值得我们学习。

从源码角度学习PHP00截断

版本问题

php 5.3.4中php修复了0字符

示例代码

1.php:

1.txt

存在漏洞的版本会成功解析,即将1.txt\000.jpg变成为1.txt并成功包含

PHP源码分析

我们使用PHP5.2.17去分析:

php去解析1.php文件,主要的执行流程在Zend/zend.czend_execute_scripts函数中。该函数首先通过zend_compile_file获取1.php文件的内容,然后调用zend_execute解析读取到的文件内容。

zend_compile_file函数首先调用open_file_for_scanning去读取文件,然后通过zendparse去进行语法和词法解析,而zendparse是通过lex_scan去扫描出token并进行语法分析。可以通过调试器观察到include的文件名参数经过lex_scan后的数据:

image-20221203161116732

这里就能发现,虽然长度为10,但是文件名只剩下了1.txt

从C语言特性来看问题产生的原因

其实看了这么多流程,归根结底,还是因为C语言的"问题"。因为C语言在处理字符串的时候,是以\0结尾的。

image-20221203161607253

于是我们尝试自己手动给他加上\0看看C语言最终获取到的是什么

image-20221203162257096

现在,我们就知道究竟是什么造成了PHP出现00截断这个漏洞了。

漏洞修复

那PHP官方是怎么修复的呢,我们来看看

修复的代码位于ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HANDLER函数的开始处:

该代码中:

Z_STRVAL_P(inc_filename)即上图中的val,即"1.txt",strlen取得长度为5,而Z_STRLEN_P(inc_filename)即上图中的len即10。

一旦出现%00截断,include的文件名经过url转码由"1.txt%00.jpg"变为"1.txt\000.jpg",进入php语法词法分析器解析后会将这个字符串解析成一个字符串,并使用zend_scan_escape_string进行字符串转码,如图,进入zend_scan_escape_string的内容为:

image-20221203162652595

中间的\000还被解析为4个字符,转码中会将他当作八进制数据转成一个字符\0,因此最终1.txt\000.jpg长度是10。

只要比较发现文件名的strlen长度和语法分析出来的长度不一样,就说明内部存在截断的字符,因此输出了打开文件失败的信息。

用小Demo来演示就是:

image-20221203162852978

JAVA下的00截断

看完了PHP的00截断,我们再来看看JAVA的00截断漏洞又是因为什么产生的呢?

演示代码

通过在漏洞版本和非漏洞版本运行以上代码,可知如果00截断成功,则会在系统的c盘根目录新建一个内容为Test by c0ny1shell.jsp,如果没有截断成功,则抛出Invalid file path异常。

漏洞调试

我选择使用JDK1.7.0(JDK1.7第一个版本),来跟踪漏洞测试代码从运行到触发。

image-20221203163510025

将传进来的name参数作为路径,新建了File对象,再次传入到FileOutputStream对象新的构造函数。根据传入的两个参数的类型,我们可以确定会进入到以下这个构造函数。

image-20221203163527039

FileOutputStream对象的构造方法又调用了open函数,打开了name参数传进来的文件路径,我们继续跟进open函数。

image-20221203163558165

发现open函数是一个native method。它的实现体是由非java语言(c语言)实现的。只能去OpenJDK官网下载jdk源码来查看它的实现。无奈没有找到jdk7u1的源码,只找到了jdk7u75的源码。其实在小版本上源码应该区别不大。

\openjdk\jdk\src\windows\native\java\io\FileOutputStream_md.c中找到了FileOutputStream类的open方法的JNI实现。open方法又调用了fileOpen方法,继续跟进fileOpen方法。

image-20221203163622129

io_util_md.c中找到了fileOpen方法的定义。

image-20221203163643177

通过阅读以上代码,可知如果在Windows NT/Windows 2000平台下会调用pathToNTPath函数将原始文件路径转化为Windows NT系统合法路径。然而通过阅读该方法源码,发现它并没有对\00字符串进行过滤。如果在其他Window操作系统版本下,则直接使用原始文件路径。

按照winFileHandleOpen方法的逻辑,无论如何最终都是调用了CreateFileW这个Windows API函数来创建文件。由于这个过程中均未对\00字符串进行过滤,如果传入的文件路径带有\00字符,则CreateFileW函数在创建文件时,路径会被截断。这没什么好说的。

这里我们没法继续跟进CreateFileW函数,毕竟Windows不开源。为了文章的严谨性,这里我用C语言写一个demo,来证明该函数可以截断。

运行会创建一个shell.jsp

漏洞修复

这里选择使用jdk1.7.0_80(JDK1.7最新版本),来观察漏洞如果被修复的。

我们继续按照原来漏洞触发的调用链重新跟踪一遍,跟踪到第二构造函数时,发现多了一个针对文件路径的检查,若检查结果为非法,则抛出异常Invalid file path.

image-20221203163817769

继续跟进,来到java.io.File类的isInvalid方法,发现该检查函数判断了路径中是否包含00字符串。(注意:java默认编码为Unicode,00字符串的Unicode编码为\u0000)。

image-20221203163844706

这里与PHP修复方案还是不太一样的,JAVA对于这种格式是直接报错,而PHP则会贴心的给你转义以后继续让你使用。

影响版本

我们知道jdk1.7版本是部分版本存在漏洞的。但这里我们需要确定是哪个版本修复了这个漏洞。翻阅了JDK1.7多个版本代码,发现在JDK1.7.0_40(7u40)开始加上了对文件名是否存在\00字符的检查。也就是说 JDK1.7.0_40之前java是存在00截断的,而之后的版本就不存在了!

后面在官网的JDK 7u40的更新日志中也找到了关于00截断问题Bug ID,分别为JDK-8003992JDK-8011539,具体链接放在了文末的参考文章里了。其实这两个是同一个Bug,官网也说明了它们重复了。

image-20221203164001720

文章来源

从源码级别了解PHP %00截断原理

从代码层面理解java的00截断漏洞深入篇