Skip to content

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

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions