Skip to content

Stop PowerShell command injection in the extractor sidecar - #35

Open
sxyrxyy wants to merge 1 commit into
ionsec:mainfrom
sxyrxyy:fix/extractor-sidecar-ps-injection
Open

Stop PowerShell command injection in the extractor sidecar#35
sxyrxyy wants to merge 1 commit into
ionsec:mainfrom
sxyrxyy:fix/extractor-sidecar-ps-injection

Conversation

@sxyrxyy

@sxyrxyy sxyrxyy commented Jul 10, 2026

Copy link
Copy Markdown

What

The extractor sidecar runs a built command via spawn('pwsh', ['-Command', cmd]), and that cmd was assembled by dropping values straight into single-quoted PowerShell literals:

  • parameters.fqdn / parameters.organization
  • parameters.tenantId
  • parameters.startDate / parameters.endDate
  • orgOutputPath
  • credentials.applicationId, the cert path, and the cert password

A single ' in any of them ends the literal and the rest of the value runs as PowerShell. The parameters.* values come from the extraction request body (only a few sub-fields are validated; the rest pass through and then override the org defaults in jobService), so an analyst who can create an extraction can inject commands -> RCE on the sidecar container, and a clean SSRF to http://169.254.169.254/... for cloud credentials.

Fix

Add a small psQuote() helper that wraps a value as a safe single-quoted PowerShell string (the only special character inside one is ', escaped by doubling it) and route every interpolated value through it. One file, no behavior change for legitimate values.

Repro (before)

POST /api/extractions
Authorization: Bearer <analyst JWT>
Content-Type: application/json

{
  "type": "unified_audit_log",
  "startDate": "2026-01-01T00:00:00Z",
  "endDate": "2026-01-02T00:00:00Z",
  "parameters": { "startDate": "2026-01-01'; Invoke-RestMethod http://169.254.169.254/ -UseBasicParsing; '" }
}

The injected Invoke-RestMethod runs on the sidecar. After this PR the ' is doubled and the value stays a literal.

Notes

  • Minimal, safe-by-construction escaping. The nicer long-term fix is to pass these as -ArgumentList / a base64 -EncodedCommand and stop building a command string at all - happy to do that as a follow-up.
  • The main extractor (services/extractor/src/index.js) has the same buildPowerShellCommand pattern, but no user-selectable extraction type routes there today, so it isn't the live sink. Worth hardening the same way; left out here to keep the PR focused.
  • Didn't run the test suite locally.

The sidecar built a pwsh -Command string by interpolating parameters.fqdn, tenantId, startDate, endDate (and the cert path/password, app id) into single-quoted literals. A single quote in any of them broke out and ran arbitrary PowerShell - RCE on the sidecar for anyone who can create an extraction, plus a path to the cloud metadata endpoint.

Route every interpolated value through a helper that wraps it as a safe single-quoted string (PowerShell escapes ' by doubling it). Longer term these should be -ArgumentList / an encoded command.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant