Skip to content

Unsanitized Callback Input Leads to Code Execution in @antv/dom-util v2.0.4 #133

@dfzysmy2tf-create

Description

@dfzysmy2tf-create

Vulnerability Report: @antv/dom-util

hi, we are a security team. We found a vulnerability in your project.

Vulnerability Details

EX0003: Code Execution

Verified Output: [CASE_ID=EX0003] [VULN_CODE] Function constructor callback executed

Taint Analysis:

Attacker creates a callback using Function constructor (code execution) and passes it to addEventListener. When the malicious element's addEventListener is called, it immediately invokes the attacker-controlled callback, executing the dynamically constructed code.

Sink Location: package/package/esm/add-event-listener.js line 6

PoC Code:

const _pkg = await import(require.resolve('./package/package'));
const lib = _pkg.default;
const attackerCallback = new Function('console.log("TAINT_MARKER_FUNCTION_CONSTRUCTOR")');
const mockElement = { addEventListener: function(e, cb) { cb(); } };
lib.addEventListener(mockElement, 'click', attackerCallback);

PoC File: poc_EX0003.js


EX0005: Code Execution

Verified Output: [CASE_ID=EX0005] [VULN_CODE] Code execution via eval-like addEventListener

Taint Analysis:

Attacker provides an element where addEventListener is replaced with eval. When the library calls target.addEventListener(eventType, callback, false), it becomes eval('console.log("TAINT_MARKER_EVAL")', callback, false), executing the eventType string as code.

Sink Location: package/package/esm/add-event-listener.js line 4

PoC Code:

const _pkg = await import(require.resolve('./package/package'));
const lib = _pkg.default;
const attackerElement = { addEventListener: eval };
lib.addEventListener(attackerElement, 'console.log("TAINT_MARKER_EVAL")', function() {});

PoC File: poc_EX0005.js


EX0006: Code Execution

Verified Output: [CASE_ID=EX0006] [VULN_CODE] Callback executed via Proxy interception

Taint Analysis:

Attacker uses a Proxy object to intercept property access. When addEventListener checks for target.addEventListener, the Proxy's get trap returns a malicious function that immediately executes the callback, allowing attacker-controlled code execution through the callback parameter.

Sink Location: package/package/esm/add-event-listener.js line 6

PoC Code:

const _pkg = await import(require.resolve('./package/package'));
const lib = _pkg.default;
const proxy = new Proxy({}, { get: function(target, prop) { if(prop === 'addEventListener') return function(e, cb) { cb(); }; } });
lib.addEventListener(proxy, 'click', function() { console.log('TAINT_MARKER_PROXY_CALLBACK'); });

PoC File: poc_EX0006.js


Affected Sinks

File Line Type Code Snippet
package/package/esm/modify-css.js 5 DYNAMIC_PROP_WRITE dom.style[key] = css[key];
package/package/lib/modify-css.js 7 DYNAMIC_PROP_WRITE dom.style[key] = css[key];
package/package/esm/add-event-listener.js 6 CODE_EXEC_SINK remove: function () {
package/package/esm/add-event-listener.js 16 CODE_EXEC_SINK remove: function () {
package/package/lib/index.js 6 CODE_EXEC_SINK Object.defineProperty(exports, "addEventListener", { enumerable: true, get: function () { return add_event_listener_1.default; } });
package/package/lib/index.js 8 CODE_EXEC_SINK Object.defineProperty(exports, "createDom", { enumerable: true, get: function () { return create_dom_1.default; } });
package/package/lib/index.js 10 CODE_EXEC_SINK Object.defineProperty(exports, "getHeight", { enumerable: true, get: function () { return get_height_1.default; } });
package/package/lib/index.js 12 CODE_EXEC_SINK Object.defineProperty(exports, "getOuterHeight", { enumerable: true, get: function () { return get_outer_height_1.default; } });
package/package/lib/index.js 14 CODE_EXEC_SINK Object.defineProperty(exports, "getOuterWidth", { enumerable: true, get: function () { return get_outer_width_1.default; } });
package/package/lib/index.js 16 CODE_EXEC_SINK Object.defineProperty(exports, "getRatio", { enumerable: true, get: function () { return get_ratio_1.default; } });
package/package/lib/index.js 18 CODE_EXEC_SINK Object.defineProperty(exports, "getStyle", { enumerable: true, get: function () { return get_style_1.default; } });
package/package/lib/index.js 20 CODE_EXEC_SINK Object.defineProperty(exports, "getWidth", { enumerable: true, get: function () { return get_width_1.default; } });
package/package/lib/index.js 22 CODE_EXEC_SINK Object.defineProperty(exports, "modifyCSS", { enumerable: true, get: function () { return modify_css_1.default; } });
package/package/lib/add-event-listener.js 8 CODE_EXEC_SINK remove: function () {
package/package/lib/add-event-listener.js 18 CODE_EXEC_SINK remove: function () {

Remediation

  • Validate all configuration options and callbacks before use
  • Avoid passing untrusted functions or objects as configuration
  • Use TypeScript strict typing to prevent unexpected function injection
  • Review all entry points that accept user-controlled configuration objects

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions