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 = flagsession.routes中的flag函数不被更改呢

thenable

1
const setRoutes = async (session, salt) => {

因为setRoutes是一个async,所以其返回值是一个Promise async

1
return session.routes;

又因为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