Skip to content

fix(ssh): prevent OS command injection via ssh key and username (CTO-5387) - #113

Open
yash-atwal wants to merge 1 commit into
mainfrom
security/CTO-5387-ssh-command-injection
Open

yash-atwal wants to merge 1 commit into
mainfrom
security/CTO-5387-ssh-command-injection

Conversation

@yash-atwal

@yash-atwal yash-atwal commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes a second-order OS command injection (CWE-78, CVSS 9.0) in the SSH access module — CTO-5387 / GHSA-grpj-9ghj-xhv8.

ssh/helpers.py built remote shell commands by string-formatting requester-controlled values — the submitted SSH public key and the derived username — directly into fabric connection.sudo() calls, e.g.:

'echo "{}" | sudo tee -a /home/{}/.ssh/authorized_keys > /dev/null'.format(ssh_key, username)

The only check was an empty-string test on the key. A requester (a normal, low-privilege authenticated action) could embed $(...), backticks or ; in the ssh_pub_key / username fields; on approval the payload executed as root/sudo on every host the automation identity manages.

Fix — defence in depth

1. Sink hardening (the complete fix)shlex.quote() every interpolated value (ssh key, username, derived /home/<user>/… paths, and the sed expression) in add_user, add_key_existing_user, and replace_user_key. Quoted payloads can no longer break out of their shell token.

2. Source validation (defence in depth)

  • SSHAccess.verify_identity() now rejects anything that isn't a single, well-formed OpenSSH public key line (strict <type> <base64> [comment] grammar; no newlines).
  • sshHelper() / replace_user_key() reject usernames that aren't valid Linux login names — before any connection is opened.
  • sed regex metacharacters are escaped for correctness (shell safety comes from shlex.quote on the whole expression).

Tests

Adds ssh/test_ssh_injection.py (31 cases): validators, verify_identity, dispatcher reject-paths, and sink-level quoting — asserting the $(...) payload cannot fire even if validation is bypassed.

Verified locally two ways: (a) full test suite green; (b) a standalone harness that executes the generated commands through a real shell confirms no marker file is created by any attack path, while a benign key still works.

Risk / compatibility

  • No API/signature changes. Behaviour is identical for well-formed keys and usernames; only malformed/malicious input is now rejected.

…5387)

The SSH access module built remote shell commands by string-formatting
requester-controlled values — the submitted SSH public key and the derived
username — directly into fabric connection.sudo() calls (e.g.
`echo "{}" | sudo tee -a /home/{}/.ssh/authorized_keys`). With only an
empty-string check on the key, a requester could embed shell metacharacters
($(...), backticks, ;) that execute as root on the target host once the
request is approved. Second-order OS command injection, CWE-78, CVSS 9.0
(GHSA-grpj-9ghj-xhv8).

Fix (defence in depth):
- Sink: shlex.quote every interpolated value (ssh key, username, derived
  paths, sed expression) in add_user, add_key_existing_user and
  replace_user_key. This is the complete fix — quoted payloads can no longer
  break out of their shell token.
- Source: validate the SSH public key against a strict OpenSSH grammar in
  SSHAccess.verify_identity, and validate the username against a Linux
  login-name allow-list in sshHelper/replace_user_key, so malformed input is
  rejected before any connection is opened.
- sed metacharacters are now escaped for correctness (shell safety comes from
  shlex.quote on the whole expression).

Adds ssh/test_ssh_injection.py covering the validators, verify_identity, the
dispatcher reject paths, and sink-level quoting (payload cannot fire even if
validation is bypassed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited), Workspace UI (inherited)

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 537b5a6f-ba44-4c3c-b8cd-5d093df1903e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

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