原理
pathinfo ( string $path , int $options )
pathinfo函数用于获取文件或目录的路径信息。
它接受一个文件或目录的路径作为输入,并返回一个关联数组,其中包含有关路径的信息。
选项 | 描述 | 识别信息 |
PATHINFO_DIRNAME | 只返回目录名。不包括末尾的斜杠。 | 目录路径 |
PATHINFO_BASENAME | 只返回基本文件名,不包括目录名或扩展名。 | 基本文件名 |
PATHINFO_EXTENSION | 只返回文件的扩展名,不包括目录名、基本文件名或(反)斜杠。从文件名的末尾开始查找,找到第一个点号 (.) 后的内容就被认为是拓展名 | 文件扩展名 |
PATHINFO_FILENAME | 返回文件名,包括基本文件名和扩展名,但不包括目录名或斜杠。 | 文件名(基本文件名和扩展名) |
PATHINFO_ALL | 返回包含目录名、基本文件名、扩展名和文件名的关联数组。这是默认值。 | 包含目录名、基本文件名、扩展名和文件名的关联数组 |
PATHINFO_EXTENSION常量表示识别任何有效的拓展名,如果拓展名有斜杠 / 或者 \ 就忽略,返回文件名最后一个点号后面的字符串作为拓展名。如果遇到最后一个点号后面没有拓展名或者拓展名无效就返回为空。
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION); |
利用
了解pathinfo函数的特点下面就可以开始绕过了。
首先这是一个黑名单后缀,可以通过使用未知的拓展名绕过。很显然要使用特殊的符号和可执行的拓展名拼接,但是这个符号不会影响拼接的拓展名服务器识别是脚本文件。
如果使用 1.php. 的文件名上传上去,最后一个点后面没有了,pathinfo函数就会返回一个空字符给变量 $file_ext,再拿这个空字符去匹配黑名单显然这个文件就不会被阻止。
如果是windows部署,如果我们上传成功并且将 1.php. 保存到了服务器上,那么windows一定会给它强制改为 1.php 的格式。
基于windows特性,同样的使用 1.php. . 1.php空格 1.php.空格 1.php::$DATA等格式都可以,可以绕过黑名单,也能让文件最终保存为 1.php 。
如果是linux部署的话,那么基于windows特性的绕过方法就不能用了,但是还是会有一些方法两个系统环境都可以使用的。例如空字符截断抓包改成 1.php%00.jpg (%00要解码,PHP版本低于5.3)。
如果PHP版本过高那空字符截断就无效了,只能用一个之前从未使用的方法,在两个系统环境使用有一点点区别。在windows下部署可以抓包保存文件名使用1.php/. 1.php\. 1.php/\.等,在linux下部署就只能用1.php/. 这个了。原因很简单,文件命名的时候 / \ 在windows是禁止的而 / 在linux也是禁止的,所以不会出现在文件名中最终保存还是1.php文件名。但是 \ 在linux是一个转义符号允许出现在文件名中,出现在后缀(1.php\.)那就没什么意义了。
关于为什么 / 后面要一个点,是因为pathinfo函数返回后缀名(最后一个点号后面的字符串)的时候会去除 / 和 \ 。如果使用1.php/的话,那么去除 / 后返回的真实的php后缀被读取到就会黑名单匹配上。加上一个点pathinfo函数读取的就是最后的点后面的字符串,点后面是空字符串不是有效拓展名它就返回为空,空就不会匹配黑名单以达到绕过黑名单目的。
再拓展一下 \. 在linux的作用。如果linux下有一个文件1.php\.的文件,使用php 1.php\.命令去执行,那么会认为文件名是 1.php. 就会找不到这个文件。正确做法是php '1.php\.'告诉系统 \ 是文件名一部分而不是转义符号。