没有不值得去解决的问题,也没有不值得去学习的技术!

在 PHP 中实现文件路径名(编辑模板代码时,可新增文件)的安全验证

查看运行结果,验证成功的 3 个文件名中,需要再排除掉:testjpg.、.testjpg,而第 3 条验证规则恰好可以排队掉:testjpg.、.testjpg。目录开头与文件扩展名结尾,可以使用 dirname() 与 Laravel 中的验证方法:ends_with:foo,bar,… 验证的字段必须以给定的值之一结尾。

1、查看请求参数:asset[key]: assets/2022/02/25.css,准备在模板文件中新增文件:assets/2022/02/25.css。响应失败:Theme files may not be stored in subfolders。如图1

查看请求参数:asset[key]: assets/2022/02/25.css,准备在模板文件中新增文件:assets/2022/02/25.css。响应失败:Theme files may not be stored in subfolders。
图1

2、查看请求参数:asset[key]: assets/25.css,准备在模板文件中新增文件:assets/25.css。响应成功。如图2

查看请求参数:asset[key]: assets/25.css,准备在模板文件中新增文件:assets/25.css。响应成功。
图2

3、查看请求参数:asset[key]: assets/02\25.css,准备在模板文件中新增文件:assets/02\25.css。响应失败:”assets/02\\25.css” contains illegal characters。如图3

查看请求参数:asset[key]: assets/02\25.css,准备在模板文件中新增文件:assets/02\25.css。响应失败:"assets/02\\25.css" contains illegal characters。
图3

4、查看请求参数:asset[key]: templates/customers/order.order1.liquid,准备在模板文件中新增文件:templates/customers/order.order1.liquid。响应成功。目录:templates/customers 早已经生成好。如图4

查看请求参数:asset[key]: templates/customers/order.order1.liquid,准备在模板文件中新增文件:templates/customers/order.order1.liquid。响应成功。目录:templates/customers 早已经生成好。
图4

5、最终整理出的验证规则如下:
(1)路径参数必须以已经生成好的目录开头。目录包含:[‘layouts/’, ‘auth/’, ‘pages/’, ‘components/’, ‘assets/’, ‘js/’, ‘sass/’, ‘assets/iconfont/’, ‘js/common/’, ‘js/view/’, ‘js/view/account/’, ‘js/view/cart/’, ‘js/view/collections/’, ‘js/view/product/’, ‘js/view/productlist/’, ‘js/view/search/’]
(2)当 asset[key] 去掉目录开头部分后,便仅需要验证纯粹的文件名,不允许再包含 / 与 \
(3)验证 asset[key] 的文件扩展名,其文件后缀名必须属于一个预先定义的数组中,扩展名包含:[‘.php’, ‘.json’, ‘.css’, ‘.scss’, ‘.js’, ‘.vue’]

6、难点主要在于第 2 条验证规则的实现,参考:https://stackoverflow.com/questions/31089394/check-if-string-is-valid-filename 。POSIX“完全可移植的文件名”列出了这些:A-Z a-z 0-9 . _ – 。新建文件:FilePathValidator.php,不允许空格。

PHP
<?php
function validate($filename) {
    if (preg_match('/^[\w\-.]+$/', $filename)) { 
        return $filename . ' 验证成功';
    } else {
        return $filename . ' 验证失败';
    }
}
 
$filenames = ['assets/2022/02/25.css', 'assets/02\25.css', '/25.css', '25.css/', '2_- 5.css', '2_-5.css', '中国.css', 'testjpg.', '.testjpg', 'test.jpg'];
foreach ($filenames as $filename) {
    echo validate($filename) . PHP_EOL;
}
?>

7、查看运行结果,验证成功的 3 个文件名中,需要再排除掉:testjpg.、.testjpg,而第 3 条验证规则恰好可以排队掉:testjpg.、.testjpg。目录开头与文件扩展名结尾,可以使用 dirname() 与 Laravel 中的验证方法:ends_with:foo,bar,… 验证的字段必须以给定的值之一结尾。如图5

查看运行结果,验证成功的 3 个文件名中,需要再排除掉:testjpg.、.testjpg,而第 3 条验证规则恰好可以排队掉:testjpg.、.testjpg。目录开头与文件扩展名结尾,可以使用 dirname() 与 Laravel 中的验证方法:ends_with:foo,bar,… 验证的字段必须以给定的值之一结尾。
图5
Plaintext
assets/2022/02/25.css 验证失败
assets/02\25.css 验证失败
/25.css 验证失败
25.css/ 验证失败
2_- 5.css 验证失败
2_-5.css 验证成功
中国.css 验证失败
testjpg. 验证成功
.testjpg 验证成功
test.jpg 验证成功

PHP
    /**
     * Return the validation rules.
     *
     * @return array<string, array<mixed>>
     */
    public function rules(): array
    {
        $extensions = implode(",", ThemeAsset::BLADE_EXTENSIONS);
        return [
            'key' => [
                // 当获取文件路径中的目录部分后,其必须属于目录列表之一
                function ($attribute, $value, $fail) {
                    if (!in_array(dirname($value) . '/', ThemeAsset::BLADE_DIRS)) {
                        $fail('The directory portion of ' . $attribute . ' can only contain the following: ' . implode(", ", ThemeAsset::BLADE_DIRS));
                    }
                },
                // 当获取文件路径中的文件名部分后,便仅需要验证纯粹的文件名,只允许包含:A-Z、a-z、0-9、.、_、-
                function ($attribute, $value, $fail) {
                    if (!preg_match('/^[\w\-.]+$/', basename($value))) {
                        $fail('The file name portion of ' . $attribute . ' can only contain the following: A-Z, a-z, 0-9, ., _, -');
                    }
                },
                // 验证的字段必须以给定的值之一结尾
                'ends_with:' . $extensions,
            ],
        ];
    }

8、这会过滤掉不属于 ISO-1252 的有效文件名,例如日文、中文、西欧、西里尔文、中东……字符。 此外,它不会过滤掉具有有效字符但在操作系统中无效的文件名,例如 COM1、LPT1、AUX …。此二项缺陷暂时皆可以接受。后续可能会解决不会过滤掉具有有效字符但在操作系统中无效的文件名。如图6

这会过滤掉不属于 ISO-1252 的有效文件名,例如日文、中文、西欧、西里尔文、中东......字符。 此外,它不会过滤掉具有有效字符但在操作系统中无效的文件名,例如 COM1、LPT1、AUX ...。此二项缺陷暂时皆可以接受。后续可能会解决不会过滤掉具有有效字符但在操作系统中无效的文件名。
图6

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系