简析b站关于站内链接的正则匹配
前言
本文由 @Rorical 的文章《优雅的在哔哩哔哩评论区发送链接》启发,目的在于学习正则匹配的基本技巧。
本文将用介绍简单的正则匹配,并通过站内链接的正则匹配作为例子进行讲解。
阅读本文时可以参照: https://www.runoob.com/regexp/regexp-syntax.html 来帮助理解
什么是正则匹配
正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符")。
正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。
正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
简单来说,正则匹配就是一串可以匹配具有特定格式的字符。
例如
runoo+b,可以匹配 runoob、runooob、runoooooob 等,+ 号代表前面的字符必须至少出现一次(1次或多次)。
runoob,可以匹配 runob、runoob、runoooooob 等, 号代表前面的字符可以不出现,也可以出现一次或者多次(0次、或1次、或多次)。
colou?r 可以匹配 color 或者 colour,? 问号代表前面的字符最多只可以出现一次(0次、或1次)。
Credit: https://www.runoob.com/regexp/regexp-syntax.html
Part1-专栏投稿中站内链接的正则匹配
通过简单的搜索,可以发现专栏投稿中共有如下4条正则匹配规则(cdn的正则略过)
1.^(http:)?(https:)?(\/\/)?((([a-zA-Z0-9_-])+(\.)?){1,2}\.)?(bilibili.com)+(:\d+)?(\/((\.)?(\?)?=?&?%?[#!a-zA-Z0-9_-](\?)?)*)*$ 2.^(http:)?(https:)?(\/\/)?(([a-zA-Z0-9_-])+(\.)?){0,2}(\.biligame.com)+(:\d+)?(\/((\.)?(\?)?=?&?%?[#!a-zA-Z0-9_-](\?)?)*)*$/i 3.^(http:)?(https:)?(\/\/)?(acg.tv)+(:\d+)?(\/((\.)?(\?)?=?&?%?[#!a-zA-Z0-9_-](\?)?)*)*$ 4.^(bilibili:\/\/)(\S)+$
在这部分里,本文将由简单到复杂,以倒序的方式逐条进行说明。
^(bilibili:\/\/)(\S)+$
首先看几个基础组成部分
^ - 字符串必须由该正则匹配开始 $ - 字符串必须由该正则匹配结束 () - 代表一个子表达式 \ - 代表转义符号,跟在\后面的字符不代表任何含义 例如 \^ 就代表 ^, 使^失去了作用. \S - 非打印字符,匹配任何非空白字符 + - 匹配前面的子表达式一次或多次 (至少匹配前面的一次)
那么该表达式代表的意思就是
必需由 bilibili:// 开始,后面跟上至少一次或多次的任意字符。
例如 bilibili://oasidhjfjkasfnkhj
无法匹配的,比如不是由bilibili://开头的, abilibili://adfasdfasf
^(http:)?(https:)?(\/\/)?(acg.tv)+(:\d+)?(\/((\.)?(\?)?=?&?%?[#!a-zA-Z0-9_-](\?)?)*)*$
同样先把基础部分标一下
? - 代表匹配0次或者一次 (最多匹配一次) \d - 代表匹配数字 [] - 代表可以匹配的字符,例如[0123456789]代表可以匹配任意一个数字,[0123456789]等同于[0-9]等同于\d
然后是是解析
(http:)?(https:)?(\/\/)?
代表至多匹配 http: , https: , // 各1次
(acg.tv)+
代表匹配 acg.tv 至少一次
(:\d+)?
代表 :数字 例如 :80 :99(其实就是端口号),出现0次或者1次,
(\/((\.)?(\?)?=?&?%?[#!a-zA-Z0-9_-](\?)?)*)*$
代表 匹配以/开头的任意字符或者干脆没有(讲起来太麻烦了)
该正则的本意是匹配例如 acg.tv/BV1oA411v7fp 的网址,但实际上却可以匹配很多根本不是网址的字符串。
比如http: 那里只写了至多能匹配一次,没写互斥。acg.tv只写了至少匹配一次.所以可能会出现如下情况
可能的解决方式: ^((http(s)?:\/\/)|(\/\/))?(acg.tv)(:\d+)?(\/((\.)?(\?)?=?&?%?[#!a-zA-Z0-9_-](\?)?)*)*$
^(http:)?(https:)?(\/\/)?(([a-zA-Z0-9_-])+(\.)?){0,2}(\.biligame.com)+(:\d+)?(\/((\.)?(\?)?=?&?%?[#!a-zA-Z0-9_-](\?)?)*)*$
重复的不再说明了
{n,m} - m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。
(([a-zA-Z0-9_-])+(\.)?){0,2}
代表 (匹配任意字符最少一次+0个或者1个点),并且该子表达式最多匹配两次最少不匹配。
比如 a.a. , ab.a , a. 等可以都匹配。但是 a.a.a.就不行了。说白了就是想要匹配子域名,且最多匹配到3级子域名。
该正则的本意是匹配biligame.com旗下的网站已经其2/3级子域名。
例如 https://www.biligame.com/detail/?id=101772 。
但是实际怎么就不用我多说了吧。先不提前文提到的http:
其他的比如
a.a..biligame.com 都可以出现超链接。
还有更奇妙的是
biligame.com 居然是非法链接。
可能的解决方式: ^((http(s)?:\/\/)|(\/\/))?([a-zA-Z0-9_-]+\.){0,2}(biligame.com)(:\d+)?(\/((\.)?(\?)?=?&?%?[#!a-zA-Z0-9_-](\?)?)*)*$
^(http:)?(https:)?(\/\/)?((([a-zA-Z0-9_-])+(\.)?){1,2}\.)?(bilibili.com)+(:\d+)?(\/((\.)?(\?)?=?&?%?[#!a-zA-Z0-9_-](\?)?)*)*$
这个没啥新的好说了,基本和上一个一致
可能的解法: ^((http(s)?:\/\/)|(\/\/))?([a-zA-Z0-9_-]+\.){0,2}(bilibili.com)(:\d+)?(\/((\.)?(\?)?=?&?%?[#!a-zA-Z0-9_-](\?)?)*)*$
Part2-评论区中站内链接的正则匹配
详情参照@Rorical 的文章《优雅的在哔哩哔哩评论区发送链接》
评论区有个功能,就是自动把b站的官方链接直接变成超链接(蓝链),这样就可以直接点链接跳转了。
判断是否为官方链接的代码依旧写死在commet.js里。
(http(s)?:\/\/)?([a-z0-9A-Z]+.)?(bilibili.(com|tv|cn)|biligame.(com|cn)|(bilibiliyoo|im9).com|biliapi.net|b23.tv|sugs.suning.com|kaola.com)(\$|\/|)([\/.$*?~=#!%@&-A-Za-z0-9_]*)(?![^<>]*>|[^"]*?<\/a)
. - 代表任意字符
先说几个没问题的
(http(s)?:\/\/)?
匹配http://或者https:// 最多一次
([/.$*?~=#!%@&-A-Za-z0-9_]*)(?![^<>]*>|[^"]*?<\/a)
代表匹配任意字符
然后就是有问题的
(\$|\/|)
代表匹配 $ 或者 / 或者 空值, 但是谁会去匹配一个 $ 啊,外星人嘛?
该表达式的本意是要么有/匹配后面路径,要么没有/,不匹配后面路径。
但是不知道哪个弱智程序员居然把$转义了,导致$根本不能起到本应该有的作用。
另外 这边还有另外一个错误,在后面会用到,就是 | 了一个空值,就是说啥都不匹配也行。
这段就直接相当于 (\$|\/)?
这又加个bug。
接下来就是大问题了
包括([a-z0-9A-Z]+.)?
和 (bilibili.(com|tv|cn)|biligame.(com|cn)|(bilibiliyoo|im9).com|biliapi.net|b23.tv|sugs.suning.com|kaola.com)
在内的所有.都没有转义
就拿第一个来说
([a-z0-9A-Z]+.)?
匹配一串字符+一个 . ,最多一次。
同时,因为 . 代表任意字符, 所以该子表达式就几乎等同于 ([a-z0-9A-Z]{1,})?
也就是说,要么匹配两个以上的任意字符,要么不匹配。
但是该子表达式的本意匹配一个二级域名或者不匹配二级域名, .的不转义导致该表达式失去效果。
这里,可以直接用一级域名做个跳转就能实现蓝链接。
再看下一段,同样是.没有转义。举个例子 bilibili.com, 那么符合规则的就不止一个bilibili.com了 bilibili0com bilibiliacom bilibilibcom bilibili0com 都是可以的
这个时候,利用这边的漏洞以及上面一个可以不匹配的漏洞,可以轻松构造出一个能够被匹配的二级域名。
这样子的话,用二级域名直接做个跳转就能实现蓝链接跳转了。
当然照惯例,给出修复方法: (http(s)?:\/\/)?([a-z0-9A-Z]+\.)?(bilibili\.(com|tv|cn)|biligame\.(com|cn)|(bilibiliyoo|im9)\.com|biliapi\.net|b23\.tv|sugs\.suning\.com|kaola\.com)($|\/)([/.$*?~=#!%@&-A-Za-z0-9_]*)(?![^<>]*>|[^"]*?<\/a)
Part3-进阶-后端正则匹配的猜解
这段内容中,本文将简单介绍如何猜解b23.tv短链接后端中的正则匹配。
POST /x/share/click?build=9333&buvid=db234615f49c5ca155cc50d6c04bb700&oid=https://www.bilibili.com&platform=ios&share_channel=COPY&share_content=123&share_id=public.webview.0.0.pv&share_mode=1&share_origin=&share_title=123&sid= HTTP/1.1 User-Agent: PostmanRuntime/7.26.2 Accept: */* Cache-Control: no-cache Postman-Token: 66c47e9c-74d6-4a48-933c-12fcdd8d4f22 Host: api.bilibili.com Accept-Encoding: gzip, deflate, br Connection: keep-alive Cookie: Content-Length: 0
尝试 https://www.bilibili.com -> 成功
尝试 https://www.biligame.com -> 成功
尝试 https://www.im9.com -> 成功
尝试 https://www.biliapi.net -> 成功
尝试 https://www.bilibili.co -> 成功
尝试 https://aabilibili0com -> 成功, 说明没有转义.
可以首先构建出一段子表达式(bilibili.com|biligame.com|im9.com|biliapi.net|bilibili.co)
尝试 https://www.bilibili.com -> 成功
尝试 https://a.a.a.a.a.bilibili.com -> 成功
尝试 www.bilibili.com -> 失败
尝试 https://bilibili.com -> 失败
尝试 http:https://www.bilibili.com -> 失败
由此可以推断出,后端的正则匹配必须有http或者https以及一个二级域名
结合上一段,构建出子表达式
^(http(s)?:\/\/)([a-z0-9A-Z]+.)+(bilibili.com|biligame.com|im9.com|biliapi.net|bilibili.co)
接下来尝试2级域名
尝试 https://aabilibili0com.a.a -> 失败
尝试 https://aabilibili0com/.a.a -> 成功
尝试 https://aabilibili0com -> 成功
尝试 https://aabilibili0com/ -> 成功
由此构建子表达式
($|\/)([\/.$*?~=#!%@&-A-Za-z0-9_]*)(?![^<>]*>|[^"]*?<\/a)
结合起来 推测后端正则表达式为
^(http(s)?:\/\/)([a-z0-9A-Z]+.)+(bilibili.com|biligame.com|im9.com|biliapi.net|bilibili.co)($|\/)([\/.$*?~=#!%@&-A-Za-z0-9_]*)(?![^<>]*>|[^"]*?<\/a)
那么,要绕过后端只有一种办法,就是买个域名。例如
https://www.aabilibili.com https://www.asd123fbilibili.com https://www.aaim9.com https://www.1254im9.com https://www.vcxzdfaim9.com https://www.123fgadsbilibili.com https://www.bsfdvbsbilibili.com
解决办法类似评论区
^(http(s)?:\/\/)([a-z0-9A-Z]+\.)+(bilibili\.com|biligame\.com|im9\.com|biliapi\.net|bilibili\.co)($|\/)([\/.$*?~=#!%@&-A-Za-z0-9_]*)(?![^<>]*>|[^"]*?<\/a)