清理字符串以使其URL和文件名安全吗?

Xeo*_*oss 134 php url filenames sanitization

我试图想出一个功能,它可以很好地清理某些字符串,以便它们可以安全地在URL中使用(如post slug),也可以安全地用作文件名.例如,当有人上传文件时,我想确保从名称中删除所有危险字符.

到目前为止,我已经提出了以下函数,我希望能解决这个问题并允许外部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');
}
Run Code Online (Sandbox Code Playgroud)

有没有人有任何棘手的样本数据我可以针对此运行 - 或者知道更好的方法来保护我们的应用程序免受坏名声?

$ is-filename允许一些额外的字符,如temp vim文件

更新:删除了明星字符,因为我无法想到有效用途

Xeo*_*oss 87

我在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;
}
Run Code Online (Sandbox Code Playgroud)

这个在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);
}
Run Code Online (Sandbox Code Playgroud)

2012年9月更新

Alix Axel在这方面做了一些令人难以置信的工作.他的功能框架包括几个伟大的文本过滤器和转换.

  • WordPress代码不可移植,因为它使用`apply_filters` (23认同)
  • ($肛门)?preg_replace ....我不知道PHP,但听起来不错. (14认同)
  • 使用force-option,$ anal -variable听起来非常可怕. (8认同)
  • 来自wordpress代码的那个不起作用,也在wordpress本身.. (3认同)

Ala*_*lly 57

您对解决方案的一些观察:

  1. 你模式结尾处的'u'意味着模式,而不是它匹配的文本将被解释为UTF-8(我假设你假设后者?).
  2. \ w匹配下划线字符.您明确地将其包含在文件中,这些文件会导致您不希望它们出现在URL中,但在代码中,您将允许URL包含下划线.
  3. 包含"外国UTF-8"似乎与语言环境有关.目前尚不清楚这是服务器还是客户端的区域设置.从PHP文档:

"单词"字符是任何字母或数字或下划线字符,即任何可以成为Perl"单词"一部分的字符.字母和数字的定义由PCRE的字符表控制,如果发生特定于语言环境的匹配,则可能会有所不同.例如,在"fr"(法语)语言环境中,一些大于128的字符代码用于重音字母,并且这些字符代码由\ w匹配.

创建slu

您可能不应该在帖子中包含重音等字符,因为从技术上讲,它们应该是百分比编码(根据URL编码规则),因此您将看到难看的URL.

所以,如果我是你,在小写之后,我将任何'特殊'字符转换为它们的等价物(例如é - > e)并用' - '替换非[az]字符,限制单个' - '的运行就像你做的那样.这里有一个转换特殊字符的实现:https://web.archive.org/web/20130208144021/http : //neo22s.com/slug

一般消毒

OWASP有一个企业安全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)
Run Code Online (Sandbox Code Playgroud)

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

  • 我创建了一个名为`สังเวชพระปกเกศกองบู๊กู้ขึ้นใหม่.txt`的文件,然后创建了一个带有链接的UTF-8 HTML文件.令人惊讶的是它工作 - 即使在窗户上!但是,然后我有了PHP`file_put_contents('สังเวชพระปกเกศกองบู๊กู้ขึ้นใหม่.txt')`并且它无法从该字符串创建一个集市文件名.然后我尝试使用`fopen()`创建它并获得相同的混乱文件名.显然PHP(至少在Windows上)无法创建UTF-8文件名.http://bugs.php.net/bug.php?id=46990&thanks=6 (3认同)

SoL*_*oST 30

这应该使您的文件名安全...

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

更深层次的解决方案是:

// 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);
Run Code Online (Sandbox Code Playgroud)

这假定您需要文件名中的点.如果你想将它转移到小写,只需使用

$clean_name = strtolower($clean_name);
Run Code Online (Sandbox Code Playgroud)

为最后一行.


Joh*_*nde 21

试试这个:

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
Run Code Online (Sandbox Code Playgroud)

根据此主题中选定的答案:PHP中的URL友好用户名?

  • 哈!编码黑客的实体很甜蜜!虽然乍一看并不清楚这种方法是如何做到的.但是有一个问题."Frédéric&Éric"不会变成"Frederic amp Eric"吗? (4认同)

Ali*_*xel 13

这不是一个答案,因为它没有提供任何解决方案(还有!),但它太大了,不适合评论......


我在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/
Run Code Online (Sandbox Code Playgroud)

但是,在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
Run Code Online (Sandbox Code Playgroud)

维基百科说:

NTFS允许每个路径组件(目录或文件名)长度为255个字符.

据我所知(和测试),这是错误的.

总共(计算斜线)所有这些示例都有259个字符,如果你去掉C:\它会产生256个字符(不是255?!).使用资源管理器创建的目录,您会注意到它限制自己使用目录名称的所有可用空间.这样做的原因是允许使用8.3文件命名约定创建文件.其他分区也会发生同样的事情.

文件当然不需要保留8.3长度要求:

(255 chars) E:\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901.txt
Run Code Online (Sandbox Code Playgroud)

如果父目录的绝对路径超过242个字符,则无法再创建子目录,因为256 = 242 + 1 + \ + 8 + . + 3.如果父目录的字符数超过233个(取决于系统区域设置),则使用Windows资源管理器无法创建另一个目录,因为256 = 233 + 10 + \ + 8 + . + 3; 在10这里是字符串的长度New folder.

如果要确保文件系统之间的互操作性,Windows文件系统会带来一个令人讨厌的问题.

3.注意保留字符和关键字

除了删除非ASCII,不可打印和控制字符外,还需要重新(放置/移动):

"*/:<>?\|
Run Code Online (Sandbox Code Playgroud)

删除这些字符可能不是最好的主意,因为文件名可能会失去一些意义.我认为,至少,这些字符的多次出现应该被单个下划线(_)取代,或者更具代表性(这只是一个想法):

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

还有一些特殊的关键词应该避免(比如NUL),虽然我不知道如何克服这个问题.也许带有随机名称后备的黑名单是解决它的好方法.

4.案例敏感性

这不用说,但如果你想这样确保文件在不同的操作系统上的独特性,你应该转换文件名归一化的情况下,这种方式my_file.txtMy_File.txt在Linux上不会都成为相同my_file.txt的Windows文件.

5.确保它是独一无二的

如果文件名已存在,则应将唯一标识符附加到其基本文件名.

公共唯一标识符包括UNIX时间戳,文件内容的摘要或随机字符串.

6.隐藏文件

仅因为它可以被命名并不意味着它应该......

点通常在文件名中以白名单列出,但在Linux中,隐藏文件由前导点表示.

7.其他考虑因素

如果必须删除文件名的一些字符,则扩展名通常比文件的基本名称更重要.允许文件扩展名(8-16)具有相当大的最大字符数,应从基本名称中删除字符.同样重要的是要注意,在不太可能发生多个长扩展的情况下 - 例如_.graphmlz.tag.gz- 在这种情况下_.graphmlz.tag_应被视为文件基名.

8.资源

Calibre处理文件名错误相当:

维基百科页面上的文件名修改使用Samba的链接章节.


例如,如果您尝试创建违反任何规则1/2/3的文件,您将收到一个非常有用的错误:

Warning: touch(): Unable to create file ... because No error in ... on line ...
Run Code Online (Sandbox Code Playgroud)


ale*_*lex 11

我一直认为Kohana做得很好.

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);
}
Run Code Online (Sandbox Code Playgroud)

方便的UTF8::transliterate_to_ascii()将变成ñ=> n之类的东西.

当然,你可以UTF8::*用mb_*函数替换其他东西.


jah*_*jah 5

在文件上传方面,最安全的做法是阻止用户控制文件名.正如已经暗示的那样,将规范化的文件名存储在数据库中,并随机选择一个唯一的名称作为实际文件名.

使用OWASP ESAPI,可以生成这些名称:

$userFilename   = ESAPI::getEncoder()->canonicalize($input_string);
$safeFilename   = ESAPI::getRandomizer()->getRandomFilename();
Run Code Online (Sandbox Code Playgroud)

您可以在$ safeFilename中附加时间戳,以帮助确保随机生成的文件名是唯一的,甚至不检查现有文件.

在URL编码方面,再次使用ESAPI:

$safeForURL     = ESAPI::getEncoder()->encodeForURL($input_string);
Run Code Online (Sandbox Code Playgroud)

此方法在编码字符串之前执行规范化,并将处理所有字符编码.


Joh*_*lia 5

我改编自另一个来源并增加了一些额外的,可能有点矫枉过正

/**
 * 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('À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'ÿ', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', 'Œ', 'œ', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', 'Š', 'š', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', 'Ÿ', '?', '?', '?', '?', 'Ž', 'ž', '?', 'ƒ', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?'); 
    $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;
}
Run Code Online (Sandbox Code Playgroud)


小智 5

这是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));
}
Run Code Online (Sandbox Code Playgroud)


Mot*_*tin 5

我建议* 用于PHP的URLify(在Github上为480颗星以上) -“来自Django项目的URLify.js的PHP端口。对非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"

?>
Run Code Online (Sandbox Code Playgroud)

要为文件名生成段:

<?php

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

?>
Run Code Online (Sandbox Code Playgroud)

*没有其他建议符合我的标准:

  • 应该可以通过作曲家安装
  • 不应依赖iconv,因为它在不同系统上的行为不同
  • 应该可扩展以允许覆盖和自定义字符替换
  • 受欢迎(例如Github上的许多明星)
  • 有测试

另外,URLify还会删除某些单词并去除所有未音译的字符。

这是一个测试案例,其中大量的外来字符已使用URLify正确音译:https ://gist.github.com/motin/a65e6c1cc303e46900d10894bf2da87f