Safeguard
Application Security•

CWE-77 Command Injection: How It Happens and How to Fix It

Shell commands are only one target. Any custom command language can be injected into. Here is how CWE-77 works, what it costs, and how to build commands safely.

Safeguard Security Team
5 min read

CWE-77 is command injection in its general form. The product builds all or part of a command from externally influenced input, and does not neutralize, or incorrectly neutralizes, special elements that could change the intended command when it is sent to a downstream component.

Most people hear "command injection" and think of a shell. MITRE makes a point of widening that: many protocols and products have their own custom command language, and developers may not realize those can be attacked too. OS shell strings are the best known case, and they have their own entry, CWE-78. This guide is about the wider idea.

How it happens

The pattern is the same everywhere. Your code assembles a command string. Part of that string comes from outside. The receiving component splits and interprets the string, and a character that means "this command ends here" or "this argument starts here" lets outside input rewrite the command.

Here is a short Python sketch against a made-up line protocol, where each command ends with a carriage return and line feed:

# Vulnerable: key comes from a user
sock.sendall(("GET " + key + "\r\n").encode())

# If key is "a\r\nDELETE everything", the server sees two commands.

The fix for that sketch is to refuse control characters in the value, or to use a client library that frames values properly:

if any(c in key for c in "\r\n"):
    raise ValueError("invalid key")
sock.sendall(("GET " + key + "\r\n").encode())

MITRE's consequence text describes the shell version of this: if a malicious user injects a character such as a semicolon that delimits the end of one command and the beginning of another, they may be able to insert an entirely new and unrelated command.

The observed examples show how different the targets can be:

  • CVE-2022-1509: injection of sed script syntax.
  • CVE-2020-11698: an anti-spam product that allows injection of SNMP commands into a configuration file.
  • CVE-2019-12921: an image program that allows injection of commands in the "Magick Vector Graphics (MVG)" language.
  • CVE-2024-5184: an API service using a large generative AI model that allows direct prompt injection to leak hard-coded system prompts or execute other prompts.

See CVE-2022-1509, CVE-2020-11698, CVE-2019-12921 and CVE-2024-5184.

What an attacker gains

MITRE lists integrity, confidentiality and availability, through execution of unauthorized code or commands. An injected command that you never intended can run with whatever privileges the receiving component has. That is why command injection tends to rate high: the attacker is not limited to the data you exposed, they get to ask the component to do new things.

How to find it

MITRE points to static analysis (SAST) as a way to find some instances by analyzing source or compiled code and modeling data flow from input to the place a command is built. It will not catch everything, so also review by hand.

A useful checklist:

  1. List every place your code builds a command for another program, service or protocol.
  2. Mark which pieces of each command come from outside.
  3. Check whether those pieces are passed as separate, typed arguments or glued into one string.
  4. Look at what the receiving side can do. Can the command language read files, write files or start processes?

How to fix it

The record gives these steps.

  1. Avoid external processes. If at all possible, use library calls rather than external processes to recreate the desired functionality.
  2. Build commands statically. If possible, make sure all external commands called from the program are statically created, with only data slots filled from input.
  3. Validate with an allowlist. Assume all input is malicious. Accept known good input, reject the rest, and consider length, type, range, syntax and business rules. Do not rely only on looking for malicious input, since that will miss something. Denylists can still help detect attacks.
  4. Enforce policy at run time. Use run time policy enforcement in an allowlist fashion to prevent use of any non-sanctioned commands.
  5. Tighten permissions. Assign permissions that prevent the user from accessing or opening privileged files.

Injection can also arrive through the software you depend on, in a library that builds commands for a tool. A free in-browser dependency scan at gold.safeguard.sh/scan needs no login, keeps the file on your machine and covers up to 100 dependencies. Reachability analysis then shows whether your code calls the affected function.

Quick check

Is CWE-77 only about shell commands?

No. The shell form is CWE-78. CWE-77 covers any command language, such as SNMP, sed scripts or image scripting languages.

What is the safest design choice MITRE lists first?

Use library calls instead of launching external processes where you can.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.