Очистить строки, чтобы сделать их безопасными для URL и имени файла? - PullRequest
133 голосов
/ 19 апреля 2010

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

До сих пор я придумал следующую функцию, которая, я надеюсь, решает эту проблему и позволяет также использовать сторонние данные UTF-8.

/**
 * Convert a string to the file/URL safe "slug" form
 *
 * @param string $string the string to clean
 * @param bool $is_filename TRUE will allow additional filename characters
 * @return string
 */
function sanitize($string = '', $is_filename = FALSE)
{
 // Replace all weird characters with dashes
 $string = preg_replace('/[^\w\-'. ($is_filename ? '~_\.' : ''). ']+/u', '-', $string);

 // Only allow one dash separator at a time (and make string lowercase)
 return mb_strtolower(preg_replace('/--+/u', '-', $string), 'UTF-8');
}

Есть ли у кого-нибудь хитрые образцы данных, которые я могу использовать для этого, или знаете, как лучше защитить наши приложения от дурных имен?

$ is-filename допускает некоторые дополнительные символы, такие как временные файлы vim

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

Ответы [ 23 ]

87 голосов
/ 19 апреля 2010

Я нашел эту большую функцию в коде Chyrp :

/**
 * Function: sanitize
 * Returns a sanitized string, typically for URLs.
 *
 * Parameters:
 *     $string - The string to sanitize.
 *     $force_lowercase - Force the string to lowercase?
 *     $anal - If set to *true*, will remove all non-alphanumeric characters.
 */
function sanitize($string, $force_lowercase = true, $anal = false) {
    $strip = array("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]",
                   "}", "\\", "|", ";", ":", "\"", "'", "‘", "’", "“", "”", "–", "—",
                   "—", "–", ",", "<", ".", ">", "/", "?");
    $clean = trim(str_replace($strip, "", strip_tags($string)));
    $clean = preg_replace('/\s+/', "-", $clean);
    $clean = ($anal) ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean ;
    return ($force_lowercase) ?
        (function_exists('mb_strtolower')) ?
            mb_strtolower($clean, 'UTF-8') :
            strtolower($clean) :
        $clean;
}

и этот в wordpress код

/**
 * Sanitizes a filename replacing whitespace with dashes
 *
 * Removes special characters that are illegal in filenames on certain
 * operating systems and special characters requiring special escaping
 * to manipulate at the command line. Replaces spaces and consecutive
 * dashes with a single dash. Trim period, dash and underscore from beginning
 * and end of filename.
 *
 * @since 2.1.0
 *
 * @param string $filename The filename to be sanitized
 * @return string The sanitized filename
 */
function sanitize_file_name( $filename ) {
    $filename_raw = $filename;
    $special_chars = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}");
    $special_chars = apply_filters('sanitize_file_name_chars', $special_chars, $filename_raw);
    $filename = str_replace($special_chars, '', $filename);
    $filename = preg_replace('/[\s-]+/', '-', $filename);
    $filename = trim($filename, '.-_');
    return apply_filters('sanitize_file_name', $filename, $filename_raw);
}

Обновление сентябрь 2012

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

57 голосов
/ 23 апреля 2010

Некоторые замечания по вашему решению:

  1. 'u' в конце вашего паттерна означает, что паттерн , а не соответствующий тексту будет интерпретирован как UTF-8 (я полагаю, вы предположили, что последний?).
  2. \ w соответствует символу подчеркивания. Вы специально включаете его для файлов, что приводит к предположению, что вы не хотите, чтобы они были в URL, но в коде, который у вас есть, URL будет разрешено включать подчеркивание.
  3. Включение «чужого UTF-8» зависит от локали. Не ясно, является ли это языковым стандартом сервера или клиента. Из документов PHP:

Символ «слово» - это любая буква или цифра или символ подчеркивания, то есть любой символ, который может быть частью Perl «слово». Определение букв и цифр контролируется таблицами символов PCRE и может отличаться, если происходит сопоставление для конкретной локали. Например, в «fr» (французском) языке некоторые коды символов, превышающие 128, используются для букв с надстрочными знаками, и они соответствуют \ w.

Создание пули

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

Итак, на вашем месте я бы после преобразования в нижний регистр преобразовал бы любые «специальные» символы в их эквивалент (например, é -> e) и заменил бы не [az] символы на «-», ограничиваясь запусками одного '-' как вы сделали. Здесь есть реализация преобразования специальных символов: https://web.archive.org/web/20130208144021/http://neo22s.com/slug

Санитарная обработка в целом

У OWASP есть реализация PHP их Enterprise Security API на PHP, которая, среди прочего, включает методы для безопасного кодирования и декодирования ввода и вывода в вашем приложении.

Интерфейс энкодера обеспечивает:

canonicalize (string $input, [bool $strict = true])
decodeFromBase64 (string $input)
decodeFromURL (string $input)
encodeForBase64 (string $input, [bool $wrap = false])
encodeForCSS (string $input)
encodeForHTML (string $input)
encodeForHTMLAttribute (string $input)
encodeForJavaScript (string $input)
encodeForOS (Codec $codec, string $input)
encodeForSQL (Codec $codec, string $input)
encodeForURL (string $input)
encodeForVBScript (string $input)
encodeForXML (string $input)
encodeForXMLAttribute (string $input)
encodeForXPath (string $input)

https://github.com/OWASP/PHP-ESAPI https://www.owasp.org/index.php/Category:OWASP_Enterprise_Security_API

30 голосов
/ 28 апреля 2010

Это должно сделать ваши имена файлов безопасными ...

$string = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $string);

и более глубокое решение этого вопроса:

// Remove special accented characters - ie. sí.
$clean_name = strtr($string, array('Š' => 'S','Ž' => 'Z','š' => 's','ž' => 'z','Ÿ' => 'Y','À' => 'A','Á' => 'A','Â' => 'A','Ã' => 'A','Ä' => 'A','Å' => 'A','Ç' => 'C','È' => 'E','É' => 'E','Ê' => 'E','Ë' => 'E','Ì' => 'I','Í' => 'I','Î' => 'I','Ï' => 'I','Ñ' => 'N','Ò' => 'O','Ó' => 'O','Ô' => 'O','Õ' => 'O','Ö' => 'O','Ø' => 'O','Ù' => 'U','Ú' => 'U','Û' => 'U','Ü' => 'U','Ý' => 'Y','à' => 'a','á' => 'a','â' => 'a','ã' => 'a','ä' => 'a','å' => 'a','ç' => 'c','è' => 'e','é' => 'e','ê' => 'e','ë' => 'e','ì' => 'i','í' => 'i','î' => 'i','ï' => 'i','ñ' => 'n','ò' => 'o','ó' => 'o','ô' => 'o','õ' => 'o','ö' => 'o','ø' => 'o','ù' => 'u','ú' => 'u','û' => 'u','ü' => 'u','ý' => 'y','ÿ' => 'y'));
$clean_name = strtr($clean_name, array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u'));

$clean_name = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $clean_name);

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

$clean_name = strtolower($clean_name);

для последней строки.

21 голосов
/ 19 апреля 2010

Попробуйте это:

function normal_chars($string)
{
    $string = htmlentities($string, ENT_QUOTES, 'UTF-8');
    $string = preg_replace('~&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i', '$1', $string);
    $string = html_entity_decode($string, ENT_QUOTES, 'UTF-8');
    $string = preg_replace(array('~[^0-9a-z]~i', '~[ -]+~'), ' ', $string);

    return trim($string, ' -');
}

Examples:

echo normal_chars('Álix----_Ãxel!?!?'); // Alix Axel
echo normal_chars('áéíóúÁÉÍÓÚ'); // aeiouAEIOU
echo normal_chars('üÿÄËÏÖÜŸåÅ'); // uyAEIOUYaA

На основании выбранного ответа в этой теме: URL Friendly Username в PHP?

13 голосов
/ 11 октября 2012

Это не совсем ответ, поскольку он не предлагает никаких решений (пока!), Но он слишком велик, чтобы поместиться в комментарии ...


Я провел некоторое тестирование (относительно имен файлов) в Windows 7 и Ubuntu 12.04, и выяснил, что:

1. PHP не может обрабатывать не-ASCII имена файлов

Хотя и Windows, и Ubuntu могут обрабатывать имена файлов Unicode (даже RTL, как кажется), PHP 5.3 требует хаков, чтобы иметь дело даже с простым старым ISO-8859-1, поэтому лучше сохранить его в ASCII только для безопасности.

2. Длина имени файла имеет значение (особенно в Windows)

В Ubuntu максимальная длина имени файла (включая расширение) составляет 255 (без пути):

/var/www/uploads/123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345/

Однако в Windows 7 (NTFS) максимальная длина имени файла зависит от его абсолютного пути:

(0 + 0 + 244 + 11 chars) C:\1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234\1234567.txt
(0 + 3 + 240 + 11 chars) C:\123\123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\1234567.txt
(3 + 3 + 236 + 11 chars) C:\123\456\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456\1234567.txt

Википедия говорит, что:

NTFS позволяет каждому компоненту пути (каталог или имя файла) быть 255 длинные символы.

Насколько мне известно (и тестирование), это неправильно.

В общей сложности (с учетом косой черты) все эти примеры имеют 259 символов, если вы уберете C:\, который дает 256 символов (не 255 ?!). Каталоги были созданы с помощью Проводника, и вы заметите, что он ограничивает себя в использовании всего доступного пространства для имени каталога. Причина этого заключается в том, чтобы разрешить создание файлов с использованием 8.3 соглашения об именовании файлов . То же самое происходит с другими разделами.

Файлы не должны резервировать 8,3 длины, конечно:

(255 chars) E:\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901.txt

Вы не можете создавать больше подкаталогов, если абсолютный путь к родительскому каталогу содержит более 242 символов, потому что 256 = 242 + 1 + \ + 8 + . + 3. Используя Проводник Windows, вы не можете создать другой каталог, если родительский каталог содержит более 233 символов (в зависимости от локали системы), потому что 256 = 233 + 10 + \ + 8 + . + 3; 10 здесь - длина строки New folder.

Файловая система Windows создает неприятную проблему, если вы хотите обеспечить взаимодействие между файловыми системами.

3. Остерегайтесь зарезервированных символов и ключевых слов

Помимо удаления не-ASCII, непечатных и управляющих символов , вам также необходимо повторно (разместить / переместить):

"*/:<>?\|

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

  • "*? -> _
  • /\| -> -
  • : -> [ ]-[ ]
  • < -> (
  • > -> )

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

4. Чувствительность к регистру

Это само собой разумеется, но если вы хотите обеспечить уникальность файлов в разных операционных системах, вам следует преобразовать имена файлов в нормализованный регистр, так что my_file.txt и My_File.txt в Linux не будут одинаковыми my_file.txt файл в Windows.

5. Убедитесь, что он уникален

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

Общие уникальные идентификаторы включают метку времени UNIX, дайджест содержимого файла или случайную строку.

6. Скрытые файлы

То, что его можно назвать, не означает, что оно должно ...

Точки, как правило, заносятся в белый список в именах файлов, но в Linux скрытый файл представлен лидирующей точкой.

7. Другие соображения

Если вам нужно удалить несколько символов имени файла, расширение обычно более важно, чем базовое имя файла. Допуская значительное максимальное количество символов для расширения файла (8-16), необходимо удалить символы из базового имени. Также важно отметить, что в маловероятном случае наличия более одного длинного расширения, такого как _.graphmlz.tag.gz - _.graphmlz.tag only _, следует рассматривать в качестве базового имени файла.

8. Ресурсы

Калибр довольно прилично обрабатывает искажение имени файла:

Страница Википедии по искажению имени файла и связанная глава из Использование Samba .


Если, например, вы попытаетесь создать файл, который нарушает любое из правил 1/2/3, вы получите очень полезную ошибку:

Warning: touch(): Unable to create file ... because No error in ... on line ...
11 голосов
/ 23 апреля 2010

Я всегда думал, Кохана довольно хорошо справилась с этим .

public static function title($title, $separator = '-', $ascii_only = FALSE)
{
if ($ascii_only === TRUE)
{
// Transliterate non-ASCII characters
$title = UTF8::transliterate_to_ascii($title);

// Remove all characters that are not the separator, a-z, 0-9, or whitespace
$title = preg_replace('![^'.preg_quote($separator).'a-z0-9\s]+!', '', strtolower($title));
}
else
{
// Remove all characters that are not the separator, letters, numbers, or whitespace
$title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', UTF8::strtolower($title));
}

// Replace all separator characters and whitespace by a single separator
$title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);

// Trim separators from the beginning and end
return trim($title, $separator);
}

Удобный UTF8::transliterate_to_ascii() превратит что-то вроде ñ => n.

Конечно, вы можете заменить другие UTF8::* функции на mb_ *.

5 голосов
/ 11 августа 2011

Я адаптировался из другого источника и добавил пару дополнительных, может быть, немного излишним

/**
 * Convert a string into a url safe address.
 *
 * @param string $unformatted
 * @return string
 */
public function formatURL($unformatted) {

    $url = strtolower(trim($unformatted));

    //replace accent characters, forien languages
    $search = array('À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'ÿ', 'Ā', 'ā', 'Ă', 'ă', 'Ą', 'ą', 'Ć', 'ć', 'Ĉ', 'ĉ', 'Ċ', 'ċ', 'Č', 'č', 'Ď', 'ď', 'Đ', 'đ', 'Ē', 'ē', 'Ĕ', 'ĕ', 'Ė', 'ė', 'Ę', 'ę', 'Ě', 'ě', 'Ĝ', 'ĝ', 'Ğ', 'ğ', 'Ġ', 'ġ', 'Ģ', 'ģ', 'Ĥ', 'ĥ', 'Ħ', 'ħ', 'Ĩ', 'ĩ', 'Ī', 'ī', 'Ĭ', 'ĭ', 'Į', 'į', 'İ', 'ı', 'IJ', 'ij', 'Ĵ', 'ĵ', 'Ķ', 'ķ', 'Ĺ', 'ĺ', 'Ļ', 'ļ', 'Ľ', 'ľ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'Ń', 'ń', 'Ņ', 'ņ', 'Ň', 'ň', 'ʼn', 'Ō', 'ō', 'Ŏ', 'ŏ', 'Ő', 'ő', 'Œ', 'œ', 'Ŕ', 'ŕ', 'Ŗ', 'ŗ', 'Ř', 'ř', 'Ś', 'ś', 'Ŝ', 'ŝ', 'Ş', 'ş', 'Š', 'š', 'Ţ', 'ţ', 'Ť', 'ť', 'Ŧ', 'ŧ', 'Ũ', 'ũ', 'Ū', 'ū', 'Ŭ', 'ŭ', 'Ů', 'ů', 'Ű', 'ű', 'Ų', 'ų', 'Ŵ', 'ŵ', 'Ŷ', 'ŷ', 'Ÿ', 'Ź', 'ź', 'Ż', 'ż', 'Ž', 'ž', 'ſ', 'ƒ', 'Ơ', 'ơ', 'Ư', 'ư', 'Ǎ', 'ǎ', 'Ǐ', 'ǐ', 'Ǒ', 'ǒ', 'Ǔ', 'ǔ', 'Ǖ', 'ǖ', 'Ǘ', 'ǘ', 'Ǚ', 'ǚ', 'Ǜ', 'ǜ', 'Ǻ', 'ǻ', 'Ǽ', 'ǽ', 'Ǿ', 'ǿ'); 
    $replace = array('A', 'A', 'A', 'A', 'A', 'A', 'AE', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'Y', 's', 'a', 'a', 'a', 'a', 'a', 'a', 'ae', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 'n', 'o', 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 'u', 'y', 'y', 'A', 'a', 'A', 'a', 'A', 'a', 'C', 'c', 'C', 'c', 'C', 'c', 'C', 'c', 'D', 'd', 'D', 'd', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'G', 'g', 'G', 'g', 'G', 'g', 'G', 'g', 'H', 'h', 'H', 'h', 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i', 'IJ', 'ij', 'J', 'j', 'K', 'k', 'L', 'l', 'L', 'l', 'L', 'l', 'L', 'l', 'l', 'l', 'N', 'n', 'N', 'n', 'N', 'n', 'n', 'O', 'o', 'O', 'o', 'O', 'o', 'OE', 'oe', 'R', 'r', 'R', 'r', 'R', 'r', 'S', 's', 'S', 's', 'S', 's', 'S', 's', 'T', 't', 'T', 't', 'T', 't', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'W', 'w', 'Y', 'y', 'Y', 'Z', 'z', 'Z', 'z', 'Z', 'z', 's', 'f', 'O', 'o', 'U', 'u', 'A', 'a', 'I', 'i', 'O', 'o', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'A', 'a', 'AE', 'ae', 'O', 'o'); 
    $url = str_replace($search, $replace, $url);

    //replace common characters
    $search = array('&', '£', '$'); 
    $replace = array('and', 'pounds', 'dollars'); 
    $url= str_replace($search, $replace, $url);

    // remove - for spaces and union characters
    $find = array(' ', '&', '\r\n', '\n', '+', ',', '//');
    $url = str_replace($find, '-', $url);

    //delete and replace rest of special chars
    $find = array('/[^a-z0-9\-<>]/', '/[\-]+/', '/<[^>]*>/');
    $replace = array('', '-', '');
    $uri = preg_replace($find, $replace, $url);

    return $uri;
}
5 голосов
/ 26 апреля 2010

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

Используя OWASP ESAPI, эти имена можно сгенерировать следующим образом:

$userFilename   = ESAPI::getEncoder()->canonicalize($input_string);
$safeFilename   = ESAPI::getRandomizer()->getRandomFilename();

Вы можете добавить метку времени к $ safeFilename, чтобы убедиться, что случайно сгенерированное имя файла уникально, даже не проверяя существующий файл.

С точки зрения кодирования для URL и повторного использования ESAPI:

$safeForURL     = ESAPI::getEncoder()->encodeForURL($input_string);

Этот метод выполняет канонизацию перед кодированием строки и будет обрабатывать все кодировки символов.

5 голосов
/ 18 июля 2014

и это версия Joomla 3.3.2 от JFile::makeSafe($file)

public static function makeSafe($file)
{
    // Remove any trailing dots, as those aren't ever valid file names.
    $file = rtrim($file, '.');

    $regex = array('#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#', '#^\.#');

    return trim(preg_replace($regex, '', $file));
}
5 голосов
/ 18 июля 2016

Я рекомендую * URLify для PHP (480+ звезд на Github) - «PHP-порт URLify.js из проекта Django. Транслитерирует не-ascii символы для использования в URL».

Основное использование:

Чтобы создать слагов для URL:

<?php

echo URLify::filter (' J\'étudie le français ');
// "jetudie-le-francais"

echo URLify::filter ('Lo siento, no hablo español.');
// "lo-siento-no-hablo-espanol"

?>

Чтобы создать слаг для имен файлов:

<?php

echo URLify::filter ('фото.jpg', 60, "", true);
// "foto.jpg"

?>

* Ни одно из других предложений не соответствовало моим критериям:

  • Должен быть установлен через композитор
  • Не должно зависеть от iconv, так как он ведёт себя по-разному в разных системах
  • Должен быть расширяемым, чтобы разрешать переопределения и замену пользовательских символов
  • Популярные (например, много звезд на Github)
  • Имеет тесты

В качестве бонуса, URLify также удаляет определенные слова и удаляет все символы, не транслитерированные.

Вот тестовый пример с тоннами иностранных символов, транслитерируемых правильно с помощью URLify: https://gist.github.com/motin/a65e6c1cc303e46900d10894bf2da87f

...