原文地址: 又双叒叕学习了一遍正则表达式
前两天在 Twitter 上看到了题图,感觉又是个大坑,在此介绍正则本身和在 JavaScript 中使用正则的坑。如有错误,烦请指正。
首先说说 JavaScript 中正则的坑。
字面量 VS RegExp()
在 JavaScript 中创建正则表达式有两种方式:
// 正则字面量 var pattern1 = /\d+/; // 构造 RegExp 实例,以字符串形式传入正则 var pattern2 = new RegExp('\\d+');
两种方式创建出的正则没有任何差别。从创建方式上看,正则字面量可读性更优,因为正则中经常使用 \
反斜杠在字符串中是一个转义字符,想以字符串中表示反斜杠的话,需要使用 \\
两个反斜杠。
但是,需要注意,每个正则表达式都有一个独立的对象表示,每次创建正则表达式,都会为其创建一个新的正则表达式对象,这和其它类型(字符串、数组)不同。
我们可以通过让正则表达式只编译一次并将其保存在一个变量中以供后续使用来实现优化。
因此,第一段代码将创建三个正则表达式对象,并进行了三次编译,虽然表达式是相同的。而第二段代码则性能更高。
console.log(/abc/.test('a')); console.log(/abc/.test('ab')); console.log(/abc/.test('abc')); var pattern = /abc/; console.log(pattern.test('a')); console.log(pattern.test('ab')); console.log(pattern.test('abc'));
这其中有性能隐患。先记住这一点,我们继续往下看。
冷知识 lastIndex
这里我们来解释下题图中的情况是怎么回事。
这其实是全局匹配的坑,也就是正则后的 /g
符号。
var pattern = /abc/g; console.log(pattern.global) // true
用 /g
标识的正则作为全局匹配,也就拥有了 global 属性并导致了题图中呈现的异常行为。
全局正则表达式的另一个属性 lastIndex 用于存放上一次匹配文本之后的第一个字符的位置。
RegExp.prototype.exec()
和 RegExp.prototype.test()
方法都以 lastIndex
属性中所存储的位置作为下次正则匹配检索的起点。连续调用这两个方法就可以遍历字符串中的所有匹配文本。
lastIndex
属性可读写,当 RegExp.prototype.exec()
或 RegExp.prototype.test()
再也找不到可以匹配的文本时,会自动把 lastIndex 属性重置为 0。因此使用这两个方法来检索文本,是可以无限执行下去的。我们也就明白了题图中为何每次执行 RegExp.prototype.test()
返回的结果都不一样。
不仅如此,看看下面这段代码,能看出来有什么问题吗?
var count = 0; while (/a/g.test('ababc')) count++;
不要轻易拷贝到控制台中尝试,会把浏览器卡死的。
由于每个循环中 /a/g.test('ababc')
都创建了新的正则表达式对象,每次匹配都是重新开始,这一操作会无限执行下去,形成死循环。
正确的写法是:
var count = 0; var regex = /a/g; while (regex.test('ababc')) count++;
这样,每次循环中操作的都是同一个正则表达式对象,随着每次匹配后 lastIndex
的增加,等到将整个字符串匹配完成后,就跳出循环了。
给以上知识点画个重点:
1.将正则表达式保存到变量中,只在逻辑中使用这个变量,不仅性能更高,还安全。 2.谨慎使用全局匹配,RegExp.prototype.exec()
或 RegExp.prototype.test()
这两个方法的执行结果可能每次都不同。 3.做到了以上两点后,还要谨慎在循环中使用正则匹配。
回溯陷阱 Catastrophic Backtracking
回溯陷阱是正则表达式本身的一个坑了,会导致非常严重的性能问题,事故现场可以参看《一个正则表达式引发的血案,让线上 CPU100% 异常!》。
简单介绍一下回溯陷阱的问题源头,正则引擎分为 NFA(确定型有穷自动机)
和 DFA(不确定型有穷自动机)
,DFA
是从匹配文本入手,同一个字符不会匹配两次(可以理解为手里捏着文本,挨个字符拿去匹配正则),时间复杂度是线性的,它的功能有限,不支持回溯。大多数编程语言选用的都是 NFA
,相当于手里拿着正则表达式,去匹配文本。
/(a(bdc|cbd|bcd)/
中已经有三种匹配路径,在 NFA
中,以文本 'abcd' 为例,将花费 7 步才能匹配成功:
(图中还包括了字符边界的匹配步骤,因此多了三步)
1.正则中的第一个字符 a 匹配到 'abcd' 中的第一个字母 'a',匹配成功。 2.此时遇到了匹配路径的分叉口,bdc 或 cbd 或 bcd,先使用 bdc 来匹配。 3.bdc 中的第一个字符 b 匹配到了 'abcd' 中的第二个字母 'b',匹配成功。 4.bdc 中的第二个字符 d 与 'abcd' 中的第三个字母 'c' 不匹配,这条路径匹配失败,此时将发生回溯(backtrack),把 'b' 还回去。选择第二条路径 cbd 进行匹配。 5.cbd 的第一个字符 'c' 就与 'b' 匹配失败。开始第三条路径 bcd 的匹配。 6.bcd 的第一个字符 'b' 与文本 'b' 匹配成功。 7.bcd 的第一个字符 'c' 与文本 'c' 匹配成功。 8.bcd 的第一个字符 'd' 与文本 'd' 匹配成功。
至此匹配完成。
可想而知,如果正则中再多一些匹配路径或者匹配本文再长一点,匹配步骤将多到难以控制。
比如用 /(a*)*bc/
来匹配 'aaaaaaaaaaaabc' 都会导致性能问题,匹配文本中每增加一个 'a',都会导致执行时间翻倍。
禁止这种回溯陷阱的方法有两种:
1.占有优先量词(Possessive Quantifiers) 2.原子分组(Atomic Grouping)
可惜 JavaScript 不支持这两种语法,有兴趣可以 Google 自行了解下。
在 JavaScript 中我们没有方法可以直接禁止回溯陷阱,我们只能:
1.避免量词嵌套 (a*)* => a*
2.减少匹配路径
除此之外,我们也可以把正则匹配放到 Service Worker 中进行,从而避免影响页面性能。
网络安全入门学习路线
其实入门网络安全要学的东西不算多,也就是网络基础+操作系统+中间件+数据库,四个流程下来就差不多了。
1.网络安全法和了解电脑基础
其中包括操作系统Windows基础和Linux基础,标记语言HTML基础和代码JS基础,以及网络基础、数据库基础和虚拟机使用等...
别被这些看上去很多的东西给吓到了,其实都是很简单的基础知识,同学们看完基本上都能掌握。计算机专业的同学都应该接触了解过,这部分可以直接略过。没学过的同学也不要慌,可以去B站搜索相关视频,你搜关键词网络安全工程师会出现很多相关的视频教程,我粗略的看了一下,排名第一的视频就讲的很详细。 当然你也可以看下面这个视频教程仅展示部分截图:
学到http和https抓包后能读懂它在说什么就行。
2.网络基础和编程语言
3.入手Web安全
web是对外开放的,自然成了的重点关照对象,有事没事就来入侵一波,你说不管能行吗! 想学好Web安全,咱首先得先弄清web是怎么搭建的,知道它的构造才能精准打击。所以web前端和web后端的知识多少要了解点,然后再学点python,起码得看懂部分代码吧。
最后网站开发知识多少也要了解点,不过别紧张,只是学习基础知识。
等你用几周的时间学完这些,基本上算是具备了入门合格渗透工程师的资格,记得上述的重点要重点关注哦! 再就是,要正式进入web安全领域,得学会web渗透,OWASP TOP 10等常见Web漏洞原理与利用方式需要掌握,像SQL注入/XSS跨站脚本攻击/Webshell木马编写/命令执行等。
这个过程并不枯燥,一边打怪刷级一边成长岂不美哉,每个攻击手段都能让你玩得不亦乐乎,而且总有更猥琐的方法等着你去实践。
学完web渗透还不算完,还得掌握相关系统层面漏洞,像ms17-010永恒之蓝等各种微软ms漏洞,所以要学习后渗透。可能到这里大家已经不知所云了,不过不要紧,等你学会了web渗透再来看会发现很简单。
其实学会了这几步,你就正式从新手小白晋升为入门学员了,真的不算难,你上你也行。
4.安全体系
不过我们这个水平也就算个渗透测试工程师,也就只能做个基础的安全服务,而这个领域还有很多业务,像攻防演练、等保测评、风险评估等,我们的能力根本不够看。
所以想要成为一名合格的网络工程师,想要拿到安全公司的offer,还得再掌握更多的网络安全知识,能力再更上一层楼才行。即便以后进入企业,也需要学习很多新知识,不充实自己的技能就会被淘汰。
从时代发展的角度看,网络安全的知识是学不完的,而且以后要学的会更多,同学们要摆正心态,既然选择入门网络安全,就不能仅仅只是入门程度而已,能力越强机会才越多。
尾言
因为入门学习阶段知识点比较杂,所以我讲得比较笼统,最后联合CSDN整理了一套【282G】网络安全从入门到精通资料包,需要的小伙伴可以点击链接领取哦! 网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!