nodejs thenable promise await
题目
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
| const {promises: fs} = require('fs');
const crypto = require('crypto');
const fastify = require('fastify');
const app = fastify();
app.register(require('fastify-cookie'));
app.register(require('fastify-session'), {
secret: Math.random().toString(2),
cookie: {secure: false},
});
const sessions = new Map();
const setRoutes = async (session, salt) => {
const index = await fs.readFile('index.html');
session.routes = {
flag: () => '*** CENSORED ***',
index: () => index.toString(),
scrypt: (input) => crypto.scryptSync(input, salt, 64).toString('hex'),
base64: (input) => Buffer.from(input).toString('base64'),
set_salt: async (salt) => {
session.routes = await setRoutes(session, salt);
session.salt = salt;
return 'ok';
},
[salt]: () => salt,
};
return session.routes;
};
app.get('/', async (request, reply) => {
if (!sessions.has(request.session.sessionId)) {
sessions.set(request.session.sessionId, {});
}
const session = sessions.get(request.session.sessionId);
if (!session.salt) {
session.salt = '';
}
if (!session.routes) {
await setRoutes(session, '');
}
console.dir(request.query);
const {action, data} = request.query || {};
let route;
switch (action) {
case 'Scrypt': route = 'scrypt'; break;
case 'Base64': route = 'base64'; break;
case 'SetSalt': route = 'set_salt'; break;
case 'GetSalt': route = session.salt; break;
default: route = 'index'; break;
}
reply.type('text/html')
return session.routes[route](data);
});
app.listen(59101, '0.0.0.0');
|
解
传入的action
通过switch
得到对应的route
,来调用session.routes
中的函数
1
2
3
4
5
6
7
8
9
10
11
12
| session.routes = {
flag: () => '*** CENSORED ***',
index: () => index.toString(),
scrypt: (input) => crypto.scryptSync(input, salt, 64).toString('hex'),
base64: (input) => Buffer.from(input).toString('base64'),
set_salt: async (salt) => {
session.routes = await setRoutes(session, salt)
session.salt = salt;
return 'ok';
},
[salt]: () => salt,
};
|
如果route = flag
就能调用flag: () => '*** CENSORED ***'
打印出flag的值了,又因为只有route = session.salt
为变值,所以查看如何能够更改session.salt
虽然set_salt
中能够更改session.salt
的值,但是在更改的上一步,又调用了setRoutes
更改session.routes
的值,如果想要更改session.salt = flag
,那么session.route
中原有的flag
函数会被覆盖成() => salt
,无法打印出flag
的值
那么有没有办法让session.salt = flag
且session.routes
中的flag
函数不被更改呢
thenable
1
| const setRoutes = async (session, salt) => {
|
因为setRoutes
是一个async,所以其返回值是一个Promise
async
又因为setRoutes
的返回值为session.routes
,把其包装成Promise
相当于Promise.resolve(session.routes)
Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是thenable(即带有“then”方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
await 的定义:await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行 async function。
https://juejin.cn/post/6899426910821548045#heading-21
await
会等待Promise
的状态从pending
改变后再继续执行,如果Promise
的状态一直为pending
那就会阻塞住
In JavaScript, a thenable is an object that has a then()function. All promises are thenables, but not all thenables are promises.
https://masteringjs.io/tutorials/fundamentals/thenable
所有的Promise
都带有then
,且then
可以被重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // A thenable is an object with a `then()` function. The
// below thenable behaves like a promise that fulfills with
// the value `42` after 10ms.
const thenable = {
then: function(onFulfilled) {
setTimeout(() => onFulfilled(42), 10);
}
};
Promise.resolve().
then(() => thenable).
then(v => {
v; // 42
});
|
可以看到then
中调用了状态改变函数onFulfilled
,如果没有这个,Promise
将一直为pending
1st-request
首先GET /?action=SetSalt&data=flag
设置session.salt = flag
2nd-request
其次GET /?action=SetSalt&data=then
,这样就会重置session.routes
,并且session.routes = {..., then: ()=> salt}
,在执行到await
时,因为一直处于pending
,所以阻塞,不能执行下一步更改session.salt
,这时候session.salt = flag
boom
GET /?action=GetSalt
,就会去调用打印flag的函数
参考文章
https://ctftime.org/writeup/30716
https://qiita.com/kusano_k/items/1dfc4bca725cead0f182#beginners-web-2021-beginner-hard
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
https://masteringjs.io/tutorials/fundamentals/thenable
https://juejin.cn/post/6899426910821548045
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function