WeChall - Training - RegexMini

Challenge

一个很短的 PHP/PCRE 正则训练。页面要求提交一个 username:它必须通过正则检查,但长度又要超过正则允许的范围。

核心逻辑可以简化成:

1
2
3
4
5
6
7
if (!preg_match('/^[a-zA-Z]{1,16}$/', $username)) {
die('invalid');
}

if (strlen($username) > 16) {
// solved
}

看起来是矛盾的:正则只允许 1 到 16 个英文字母,后面却要求 strlen($username) > 16

Solution

关键在 PCRE 里 $ 的语义。

在默认模式下,$ 不只匹配字符串真正结尾,也可以匹配「字符串末尾换行符之前的位置」。也就是说:

1
aaaaaaaaaaaaaaaa\n

这串内容有 16 个 a,后面跟一个换行符。

正则 /^[a-zA-Z]{1,16}$/ 匹配时:

  • ^[a-zA-Z]{1,16} 吃掉 16 个 a
  • $ 可以停在最后的 \n 前面。
  • 因此 preg_match() 判定通过。

但 PHP 的 strlen() 会真实计算字节数,换行也算 1 字节:

1
16 个 a + 1 个 \n = 17

于是同一个输入同时满足:

1
2
preg_match('/^[a-zA-Z]{1,16}$/', $username) === 1
strlen($username) > 16

用 curl 提交时要保留末尾的 literal newline,推荐交给 --data-urlencode

1
2
3
4
5
$ curl -sL \
-b 'WC=...' \
-H 'User-Agent: Mozilla/5.0' \
--data-urlencode $'username=aaaaaaaaaaaaaaaa\n' \
'https://www.wechall.net/challenge/training/regexmini/index.php'