PHP: как дезинфицировать загруженные имена файлов?

У меня есть PHP-приложение.

Я разрешаю пользователям загружать файлы в мое веб-приложение.

Вопрос. Как лучше всего очистить имена файлов загруженных документов $_FILES["filename"]["tmp_name"] в PHP?

ОБНОВЛЕНИЕ:

Могу ли я взять MD5 имени загруженного файла и использовать его в качестве нового назначенного имени файла? Если да, то как мне это сделать в PHP?


person frooyo    schedule 20.09.2010    source источник
comment
Можете ли вы дать четкое определение «дезинфицировать»? Как для MySQL? URL-адрес?   -  person fredley    schedule 21.09.2010
comment
Я загружаю файлы на свой веб-сервер. Файлы могут быть изображениями, документами и т. д. Я не хочу конфликтов имен файлов. И я не хочу, чтобы люди пытались загружать имена файлов, которые могут быть запрещены в моей файловой системе.   -  person frooyo    schedule 21.09.2010
comment
Вы также должны знать, какие файлы вы разрешаете им загружать. Вы не хотите, чтобы кто-то мог загружать такие вещи, как файлы html/javascript.   -  person Moses    schedule 21.09.2010
comment
@Moses: он может позволить людям загружать файлы HTML или EXE. Однако он должен обращаться с ними более осторожно, например. не обслуживайте HTML как текст/html, а как обычный/текст.   -  person Crozin    schedule 21.09.2010
comment
Исходя из того, что сказал Моисей, вам обязательно следует следить за содержимым файлов. Например, если кто-то загружает файл PHP, он может содержать вредоносный код, позволяющий использовать дополнительные эксплойты, распространять вирус и т. д. Способ предотвратить это — проверить расширения: php, asp и т. д. не должны быть разрешены.   -  person Kranu    schedule 21.09.2010


Ответы (6)


Бьюсь об заклад, что вы также храните некоторую информацию о файле в базе данных. Если это правильно, вы можете использовать первичный ключ (ID) в качестве имени файла на своем сервере и сохранить исходное имя файла в базе данных. Это дает вам большую гибкость, поскольку вы можете манипулировать метаданными, не переименовывая фактический файл.

person Adam Byrtek    schedule 20.09.2010

Я бы просто запустил простое регулярное выражение, которое заменяет любые не буквенно-цифровые символы символом подчеркивания (или просто удаляет эти символы). Конечно, убедитесь, что вы сохранили расширение.

Если вы хотите пойти немного дальше, вы можете использовать расширение magic mime, чтобы убедиться, что файл имеет тот же формат, что и расширение.

EDIT: Чтобы избежать конфликтов имен файлов в каталоге, вы можете добавить md5 IP-адреса пользователей + текущее время к имени файла.

person Sam Day    schedule 20.09.2010
comment
Что произойдет, если они загрузят 2 документа одновременно? - person frooyo; 21.09.2010
comment
Добавьте немного дополнительной энтропии, используйте счетчик для каждого файла, который вы обрабатываете: - person Sam Day; 21.09.2010
comment
Если $i увеличивается для каждого файла, который вы обрабатываете: $filename = $sanitizedFileName . md5($_SERVER["REMOTE_ADDR"] . time() . $i) . $extension; - person Sam Day; 21.09.2010

Чтобы избежать конфликта имен файлов, просто проверьте, не существует ли данное или сгенерированное имя файла:

do {
   // Generate filename, eg.:
   $filename = md5(uniqid()) . $fileExtension;
} while (file_exists($filename));

Это дает вам 100% уверенность в том, что имя файла уникально. Использование md5 (или любого другого хэш-алгоритма) гарантирует, что имя файла безопасно и легко обрабатывается.

person Crozin    schedule 20.09.2010
comment
Это не имеет никакого смысла. Вы берете MD5 функции uniqid. Почему? - person frooyo; 21.09.2010
comment
Это только пример. Вы можете использовать исходное имя файла и т. д. Дело в том, что вы должны проверять в цикле, используется ли уже сгенерированное имя файла. Если это так, перегенерируйте имя файла. - person Crozin; 21.09.2010
comment
Брать md5() из uniqid() не имеет смысла. Использование md5() исходного имени файла (т. е. постоянного значения) в цикле имеет еще меньше смысла. :-) - person Andras Nemeth; 28.04.2013
comment
Конечно, удаление md5 из uniqid имеет смысл! uniqid — это очень длинная строка, содержащая тире, md5 преобразует ее в последовательность буквенно-цифровых символов, что гораздо приятнее видеть. Даже удаление md5 из time() . $filename имеет большой смысл — вы создаете буквенно-цифровые имена файлов, которые гарантированно будут уникальными! - person hijarian; 10.01.2014
comment
@Crogin Извините за некропостинг, но почему не md5(time()), а md5(uniqid())? Я понимаю, что это может быть дело вкуса, но все же? - person hijarian; 10.01.2014
comment
time() имеет разрешение 1 секунду, а uniqid() (который использует microtime()) имеет разрешение 1 миллисекунду. Это означает, что если бы вы использовали time() в случае коллизии, ваш цикл выполнял бы ту же задачу за 1 секунду - это было бы бессмысленно. - person Crozin; 10.01.2014
comment
Просто читая эту тему, я стал умнее. Я не обращаю внимания на такие мелкие детали, как замечание разницы во времени между uniqid() и time(). В следующий раз вместо того, чтобы просто выбрать ответ с наивысшим рейтингом, я прочитаю все ответы и последующие комментарии. - person wyz1; 19.04.2019
comment
@ wyz1 Возможно, вы неправильно поняли комментарий Крозина (или я неправильно понял ваш: p). Разница между time() и uniqid() не в производительности, а в точности. С time() у вас есть дубликаты, если два файла загружаются в одну и ту же секунду. С uniqid() у вас есть дубликаты, только если два файла загружаются в одну и ту же 1/1000000-ю секунды. Кроме того, uniqid() имеет необязательный параметр, чтобы добавить больше энтропии к результату и сделать его еще более безопасным. - person Thibault Witzig; 23.04.2019

Чао, эта функция также удаляет все точки, а затем я создаю чистую строку с расширением.

function sanitaze_upload_file($data)
{
    $imgName   = $data;
    $indexOFF  = strrpos($imgName, '.');
    $nameFile  = substr($imgName, 0,$indexOFF);
    $extension = substr($imgName, $indexOFF);
    $clean     = preg_replace("([^\w\s\d\-_~,;\[\]\(\)])", "", 
    $nameFile);
    $NAMEFILE  = str_replace(' ', '', $clean).$extension;
    return $NAMEFILE;
}
person the_martux    schedule 17.11.2017
comment
Сколько тестов эта функция прошла? Могу ли я просто скопировать и вставить это в свой код? - person wyz1; 19.04.2019

Вместо очистки имен файлов, указанных пользователем, используйте любой другой уникальный идентификатор для этой фотографии и сохраните его как имя файла. Я предпочитаю использовать идентификаторы пользователей, которые являются числовыми и всегда уникальными.

move_uploaded_file($_FILES["tmp_name"],"/home/yourname/".$user_id));

Затем вы можете получить изображение из любого места (скажем, S3 или даже вашего собственного сервера), просто зная идентификатор пользователя. Вам даже не нужен атрибут в вашей базе данных для хранения URL-адресов изображений.

person Gaurav Gupta    schedule 21.09.2010
comment
Что происходит, когда пользователю необходимо загрузить несколько файлов? - person wyz1; 19.04.2019

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

hash('md5', $_FILES["filename"]["tmp_name"]);
person jduren    schedule 20.09.2010
comment
Вы имеете в виду: хэш('md5', $_FILES[filename][tmp_name] ) ? - person frooyo; 21.09.2010
comment
Да, первый аргумент, который вы передаете, — это тип хеша, который вы хотите создать, а второй аргумент — это строка, из которой вы хотите создать хэш. Хэш PHP — php.net/manual/en/function.hash.php - person jduren; 21.09.2010
comment
Я также предлагаю добавить практику Sam Days, где вы можете добавить текущее время к имени файла перед хешированием, что создаст еще более уникальное имя файла. - person jduren; 21.09.2010
comment
Если вы идете по пути «просто иметь (псевдо)случайное имя файла», использование функции tempnam() автоматически решит условия гонки. - person Wrikken; 21.09.2010
comment
Вам определенно нужно что-то добавить перед хешированием. Хеширование одного только имени файла не предотвратит конфликты имен, потому что одно и то же имя файла всегда будет создавать один и тот же хэш. - person Ben Dunlap; 21.09.2010
comment
@jduren, пожалуйста, обновите свой ответ следующим образом: hash('md5', $_FILES[filename][tmp_name] ). Если вы обновите ответ, я отмечу, что он принят - person frooyo; 21.09.2010
comment
@Wrikken tempnam() фактически создает новый файл, не так ли? Использование хешированной уникальной строки, тогда просто переименование файла было бы более идеальным. - person jduren; 21.09.2010
comment
Нет, это имя файла tmp, которое PHP использует, когда пользователь загружает файл на сервер. - person frooyo; 21.09.2010
comment
@ user401839 Нет, Вриккен упомянул функцию tempnam() в своем комментарии (4-й комментарий к моему ответу), и мне интересно, с точки зрения производительности, использует tempnam(), который создает новый файл в целом лучше, чем просто переименование файла с помощью хэш исходного имени файла с добавлением времени или других уникальных данных. - person jduren; 21.09.2010
comment
@jduren: разница в производительности незначительна, избегая условий гонки и конфликтов имен файлов, и прежде чем вы получите надежную реализацию этих tempnam(), они уже должны быть выполнены. - person Wrikken; 21.09.2010
comment
@Wikkan есть шанс, что вы можете предоставить пример использования, в котором вы могли бы использовать tempnam(), например, для решения этого вопроса. Я немного читал на странице PHP Man для функции tempnam() и просто не вижу, как tempnam полезен в этом случае. Насколько я могу судить, он создает временный файл с возможностью установки префикса. Но он создает его с расширением .tmp, а затем возвращает имя файла? Создание всего нового файла только для получения уникальной строки кажется немного излишним, но я могу неправильно его интерпретировать. Пример? - person jduren; 21.09.2010
comment
(1) Вы фактически будете использовать файл как новый файл, цель для загрузки (то, что файл не является временным, поскольку tempnam заставит вас думать, что это не имеет значения, и не позволяйте этому сбить вас с толку) (2 ) Ни одна отдельная загрузка не перезаписывает существующую, даже если some_hash(имя файла) равно (3) Никакая 2 одновременная загрузка никогда не будет запрашивать одно и то же имя файла, даже если они одновременны и приводят к одному и тому же хешу. И да, вероятность того, что (2) или (3) произойдет, невелика, однако, учитывая сочетание достаточного количества посетителей и времени, это когда-нибудь произойдет, и тогда вы рады, что запрограммировали, чтобы это не было проблемой. - person Wrikken; 21.09.2010