Description
Using user input to select a function on window, globalThis, or global can invoke unintended functions. eval executes its string argument; Function creates a function that may be called later. If an attacker also controls the executed string, arbitrary code can run with the privileges of that environment.
Potential impact
- Arbitrary JavaScript execution through code-executing functions
- Exposure of accessible cookies, tokens, or environment variables
- Changes to application state or misuse of privileged operations
- Resource exhaustion through loops or expensive computations
Remediation
- Do not let user input dynamically choose methods on global or function objects.
- Register only allowed functions in a dedicated object or
Map, and validate keys with checks such asObject.hasOwnbefore calling them. - If global access is necessary, use a narrow fixed allow-list such as
encodeURIComponentanddecodeURIComponent. - Reject names outside the allow-list and use a safe default behavior.
- Exclude code-generating or string-executing functions such as
eval,Function,setTimeout,setInterval, andexecScript. Prefer JSON parsing, explicit branches, or a dedicated API map. - For additional protection, avoid
'unsafe-eval'in browser CSP. Where compatible, restrict string code generation in Node.js with--disallow-code-generation-from-stringsand runtime or build policies.
Examples
Before
javascript
// Browser example
// Use the URL fragment as a global function name.
// https://site.example/#eval calls window["eval"]("alert('pwned')").
window.addEventListener('load', () => {
const name = location.hash.slice(1); // User-controlled
window[name]("alert('pwned')"); // Allows eval/Function calls.
});
// Node.js (Express) example
const express = require('express');
const app = express();
app.get('/do', (req, res) => {
const action = req.query.action; // User-controlled
// /do?action=eval executes the example string and changes global state.
globalThis[action]("globalThis.exampleResult = 1"); // Vulnerable
res.send('done');
});
After
javascript
// Restricted dispatch: dedicated API object, allowlist, and key checks.
// Object.create(null) removes inherited prototype members.
const API = Object.create(null);
API.ping = (d) => 'pong';
API.upper = (d) => String(d || '').toUpperCase();
function callApi(name, data){
// Check own-key presence and function type.
if (!Object.hasOwn(API, name) || typeof API[name] !== 'function') {
throw new Error('invalid action');
}
return API[name](data);
}
// Browser example
window.addEventListener('load', () => {
const name = new URL(location.href).searchParams.get('action');
try {
const result = callApi(name, 'hello');
console.log(result);
} catch (e) {
console.warn('blocked');
}
});
// Node.js (Express) example
const express = require('express');
const app = express();
app.get('/do', (req, res) => {
try {
const result = callApi(req.query.action, req.query.data);
res.json({ ok: true, result });
} catch {
res.status(400).json({ ok: false, error: 'invalid action' });
}
});
// Narrow global-function allowlist, excluding code execution.
const SAFE_GLOBALS = Object.freeze(["encodeURIComponent", "decodeURIComponent"]);
app.get('/encode', (req, res) => {
const action = req.query.action;
if (!SAFE_GLOBALS.includes(action)) {
return res.status(400).send('invalid action');
}
res.send(globalThis[action](String(req.query.value || '')));
});
Explanation:
- Before: User input selects a global function. Selecting
evalexecutes the example's fixed string.Functiononly creates a function; its body runs when that function is called. Attacker control of the executed string could enable arbitrary code execution. - After: Register only allowed functions, validate own keys and function types, and reject unknown names. A null-prototype container avoids inherited members. Any necessary global-function allow-list must exclude code-generating functions.