Unsanitized Callback Input Leads to Code Execution in @antv/dom-util v2.0.4
- Dominant language
- TypeScript
- Stars
- 72
- Forks
- 28
- PR merge metrics
- No merged PRs in 30d
Description
# 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:**
```javascript
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](./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:**
```javascript
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](./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:**
```javascript
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](./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
---
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with package/package/esm/add-event-listener.js and package/package/lib/add-event-listener.js at the reported sink locations, then review the linked PoC cases EX0003, EX0005, and EX0006. Compare the reported behavior with the package entry points and affected files, and define done as a reproducible vulnerability assessment with a specific validated remediation and regression coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100