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

Securely Validate File Paths in PHP When Editing Template Code

Checking the execution results, among the 3 file names that passed validation, we need to exclude: testjpg. and .testjpg, while the third validation rule happens to filter out: testjpg. and .testjpg. The leading directory and the trailing file extension can be validated using dirname() and the validation method in Laravel: ends_with:foo,bar,… The validated field must end with one of the given values.

1. Checked the request parameter: asset[key]: assets/2022/02/25.css, preparing to create a new file in the template files: assets/2022/02/25.css. Response failed: Theme files may not be stored in subfolders. As shown in Figure 1

Checked the request parameter: asset[key]: assets/2022/02/25.css, preparing to create a new file in the template files: assets/2022/02/25.css. Response failed: Theme files may not be stored in subfolders.
Figure 1

2. Checked the request parameter: asset[key]: assets/25.css, preparing to create a new file in the template files: assets/25.css. Response successful. As shown in Figure 2

Checked the request parameter: asset[key]: assets/25.css, preparing to create a new file in the template files: assets/25.css. Response successful.
Figure 2

3. Checked the request parameter: asset[key]: assets/02\25.css, preparing to create a new file in the template files: assets/02\25.css. Response failed: “assets/02\\25.css” contains illegal characters. As shown in Figure 3

Checked the request parameter: asset[key]: assets/02\25.css, preparing to create a new file in the template files: assets/02\25.css. Response failed: "assets/02\\25.css" contains illegal characters.
Figure 3

4. Checked the request parameter: asset[key]: templates/customers/order.order1.liquid, preparing to create a new file in the template files: templates/customers/order.order1.liquid. Response successful. The directory: templates/customers has already been generated. As shown in Figure 4

Checked the request parameter: asset[key]: templates/customers/order.order1.liquid, preparing to create a new file in the template files: templates/customers/order.order1.liquid. Response successful. The directory: templates/customers has already been generated.
Figure 4

5. The final validation rules are as follows:
(1) The path parameter must start with an already generated directory. Directories include: [‘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) After stripping the leading directory part from asset[key], only the pure file name needs to be validated, and it must not contain / or \
(3) Validate the file extension of asset[key]. The file suffix must belong to a predefined array. Extensions include: [‘.php’, ‘.json’, ‘.css’, ‘.scss’, ‘.js’, ‘.vue’]

6. The main difficulty lies in implementing the second validation rule. Reference: https://stackoverflow.com/questions/31089394/check-if-string-is-valid-filename . POSIX “fully portable filenames” lists these: A-Z a-z 0-9 . _ – . Created file: FilePathValidator.php, spaces are not allowed.

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. Checking the execution results, among the 3 file names that passed validation, we need to exclude: testjpg. and .testjpg, while the third validation rule happens to filter out: testjpg. and .testjpg. The leading directory and the trailing file extension can be validated using dirname() and the validation method in Laravel: ends_with:foo,bar,… The validated field must end with one of the given values. As shown in Figure 5

Checking the execution results, among the 3 file names that passed validation, we need to exclude: testjpg. and .testjpg, while the third validation rule happens to filter out: testjpg. and .testjpg. The leading directory and the trailing file extension can be validated using dirname() and the validation method in Laravel: ends_with:foo,bar,… The validated field must end with one of the given values.
Figure 5
Plaintext
assets/2022/02/25.css validation failed
assets/02\25.css validation failed
/25.css validation failed
25.css/ validation failed
2_- 5.css validation failed
2_-5.css validation successful
中国.css validation failed
testjpg. validation successful
.testjpg validation successful
test.jpg validation successful

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. This will filter out valid file names that do not belong to ISO-1252, such as Japanese, Chinese, Western European, Cyrillic, Middle Eastern… characters. In addition, it will not filter out file names with valid characters that are invalid in the operating system, such as COM1, LPT1, AUX… Both of these flaws are currently acceptable. The failure to filter out file names with valid characters that are invalid in the operating system may be resolved later. As shown in Figure 6

This will filter out valid file names that do not belong to ISO-1252, such as Japanese, Chinese, Western European, Cyrillic, Middle Eastern... characters. In addition, it will not filter out file names with valid characters that are invalid in the operating system, such as COM1, LPT1, AUX... Both of these flaws are currently acceptable. The failure to filter out file names with valid characters that are invalid in the operating system may be resolved later.
Figure 6

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

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

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

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

关于我  ·  GitHub  ·  邮件联系