Недокументированная санация при загрузке файлов 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;
}