Недокументированная санация при загрузке файлов PHP

При обработке загрузки файлов, согласно официальной документации PHP, имя файла следует очищать от обхода каталога и, возможно, других видов атак:

// basename() may prevent filesystem traversal attacks;
// further validation/sanitation of the filename may be appropriate
$name = basename($_FILES["pictures"]["name"][$key]);

Несмотря на это, я обнаружил, что по умолчанию имя файла уже очищено, когда оно поступает в скрипт PHP.

У меня есть доказательства того, что Apache получает вредоносное имя файла: filename="../file.png", в то время как PHP-скрипт вместо этого считывает очищенное имя в переменную $_FILES.

Низкоуровневый дамп ввода Apache:

mod_dumpio: dumpio_in (data-HEAP):
--------------------------eb8b65b665870e02
Content-Disposition: form-data;
name="attachment";
filename="../file.png" ← [Malicious file name]
Content-Type: image/png

PHP скрипт

echo $_FILES['attachment']['name']; ← [File name already sanitised: 'file.png']

Я обнаружил такое поведение как в модуле Apache, так и в php-fpm при работе PHP с 5.5 до 7.2, и я должен сделать вывод, что интерпретатор PHP выполняет эту очистку перед передачей переменной в сценарий.

Итак, спасибо PHP за то, что сделали санитарию для меня без моего ведома и согласия. Однако (и это мой вопрос), поскольку эта функция, насколько я знаю, не документирована, я хотел бы знать критерии / regexp / алгоритма санации, чтобы обеспечить ее соответствие моим потребностям.

1 ответ

Решение

Вы хотите посмотреть на rfc1867.cКажется, это та часть, на которую вы ссылаетесь:

SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler)

Из комментария видно, что basename() используется для избавления от ложных обратных косых черт, которые на самом деле могут быть правильными (я думаю, возможно)Hello\ World.txt"?). Но это основано на поведении IE, и в комментарии говорится, что он может быть удален в будущем.

Таким образом, вы не можете полагаться на эту "дезинфекцию", чтобы оставаться там.

...

    /* The \ check should technically be needed for win32 systems only where
     * it is a valid path separator. However, IE in all it's wisdom always sends
     * the full path of the file on the user's filesystem, which means that unless
     * the user does basename() they get a bogus file name. Until IE's user base drops
     * to nill or problem is fixed this code must remain enabled for all systems. */

    s = _basename(internal_encoding, filename TSRMLS_CC);
    if (!s) {
        s = filename;
    }
Другие вопросы по тегам