【问题标题】:Secure unicode regex in PHP for text field submissionsPHP 中用于文本字段提交的安全 unicode 正则表达式
【发布时间】:2018-11-16 10:46:39
【问题描述】:

我目前正在使用接受文本字段提交的 PHP 表单处理代码。我的代码是这样的:

function checkInput($f) {
    $f = strtr($f, array('Š' => 'S','Ž' => 'Z','š' => 's','ž' => 'z','Ÿ' => 'Y','À' => 'A','Á' => 'A','Â' => 'A','Ã' => 'A','Ä' => 'A','Å' => 'A','Ç' => 'C','È' => 'E'));  
    $f = strtr($f, array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u'));  
    $f = preg_replace(array('#(\ ){2,}#', '#(\.){4,}#', '/[^\w\-\_\.\:\, ]+/'), array(' ', '...', '_'), $f);  
    return $f; 
}  

此代码检查带重音的字符,并用不带重音的“常规”字符替换那些字符。 preg_replace 行检查:
1.如果有2个或更多连续空格,如果是:替换为1个空格;
2.如果有4个或更多连续点,如果是:替换为3个点;
3. 如果有不匹配的字符,如果有:用下划线(_)替换;

我想支持其他语言的 unicode 字符,例如 Cyrillic。只需在preg_replace 行中添加u 就足够了吗?示例:

$f = preg_replace(array('#(\ ){2,}#', '#(\.){4,}#', '/[^\w\-\_\.\:\, ]+/u'), array(' ', '...', '_'), $f);   

我不确定这在安全性方面是否可行。请指教。

编辑:
这个正则表达式似乎正在工作,它将允许的字符限制为正则表达式中的指定字符,但它不允许非拉丁字符..

/^[a-z0-9\.\,\:\!\?\-\_\ ]+/iu  

我想允许字符:a 到 z(不区分大小写)、0 到 9、. , : ! ? - _ 空格和非拉丁字符。

EDIT2:
好的,现在这似乎在代码中正常工作:

$rgx = '/[^a-z0-9-\_\.\:\,\!\?\w ]+/iu';  
$f = preg_replace($rgx, "", $f);  
$f = preg_replace(array('#(\ ){2,}#', '#(\.){4,}#'), array(' ', '...'), $f);  
return $f;

它允许字符 a - z、数字、- _ 。 :,! ?和非拉丁字符。并替换任何受限字符,如引号 " ' 和分号 ; 以防止 SQL 注入。

【问题讨论】:

  • u 将使\w 识别Unicode,/[^\w]/u 将匹配任何不是Unicode 字母、数字或_(以及其他一些字符)的字符。你可能想用'/[^a-zA-Z0-9-_.:,\s]+/'替换'/[^\w\-\_\.\:\, ]+/u'
  • 谢谢,我已经测试过了:它允许西里尔字符,但不允许拉丁字符。我在这里测试过:rubular.com/r/yp6dhfehrl 这是一个 ruby​​ 网站,但我认为 PHP 应该可以正常工作..
  • 在你的代码中,你用正则表达式替换,而在rubular你是匹配。 PCRE 用于 PHP 正则表达式,而不是 Onigmo(用于 Rubular)。使用regex101.com 测试PHP 正则表达式,它是最用户友好的 - 恕我直言 - PCRE、JS、Python re 和 Go 正则表达式的正则表达式测试网站。
  • 谢谢,我已经在 regex101 上测试了这个正则表达式,这似乎有效:^[a-zA-Z0-9-_\.\:\,\!\?\w]+ /u
  • 我想要允许字符:a-zA-Z0-9.,:!?_ -(空格)拉丁字符和非拉丁字符。字符串中不允许(不匹配)的任何字符必须替换为“”(从字符串中删除)。这必须在整个字符串中完成。此外,我想用 1 个空格等替换 2 个或更多连续的空格,例如上面的 preg_match 示例。示例:“te-st in!p.ut”不会更改。但是:“test i@n';put”必须改为:“test input”。并且:“测试[大量空间]输入”改为:“测试输入”。

标签: php regex


【解决方案1】:

请允许我清理您的 Edit #2 模式并建议一个单调用实现。

代码:(Demo)

function sanitizer($string) {
    return preg_replace(['~[^\p{L}\p{N}_.:,!? -]+~u', '~ \K +|\.{3}\K\.+~'], '', $string);
}

$strings = [
    "1: Доброе утро - Dobraye ootro &       Good morning",
    "2: Добрый день => Dobriy den'....... (Good afternoon)"
];

foreach ($strings as $string) {
    echo sanitizer($string);
    echo "\n---\n";
}

输出:

1: Доброе утро- Dobraye ootro Good morning
---
2: Добрый день Dobriy den... Good afternoon
---

我本可以为preg_replace() 编写一个管道模式,但我想对字符串进行两次传递。 1. 删除任何无效字符,然后 2. 删除可能由第一遍形成或未形成的过长字符序列。

值得注意的模式变化:

  1. [a-zA-Z0-9_] 更简单地写成\w 但是因为你使用u 标志和准备PHP7.3's strict adherence to PCRE2,最好写出两个:\p{L}\p{N}_

  2. 避免在没有特殊含义的字符之前写不必要的斜线——它只会使你的模式更长更难解释。通常具有特殊含义的字符(如*?+ 等)在字符类[ ... ] 中失去了特殊含义。

  3. 将连字符移动到否定字符类的前面或后面,以避免写入字符范围的可能性。 (因为您的 - 出现在一系列字符 0-9 之后,这不是问题,但作为最佳实践,最好记住这一点。

  4. \K 表示“忘记之前匹配的子字符串”,换句话说,“从这里开始匹配”。这使您可以避免捕获组,并通过将匹配替换为空字符串来截断不需要的字符。

附言您仍然应该像原来的帖子那样运行strtr() 电话。

【讨论】:

    猜你喜欢
    • 2013-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多