NewApp Monitoring is liveSign up free
AgentbeamBeta
Course outline

Learn / Level 4 / lesson 5

Shellshock

How a feature of the Bash shell turned data in an environment variable into commands, why web servers and SSH exposed it, and what the botched first fix teaches about patching.

7 min read · Yash Thakker · updated 2026-10-24

shellshock · CVE-2014-6271 · bash · command injection

A variable that should hold plain text carrying hidden commands into a shell that runs them

What you will learn

  • Explain what an environment variable is and how child programs inherit it
  • Describe how Bash's exported-function feature mixed data with code
  • Explain why web servers and SSH setups let remote input reach Bash
  • Check a shell safely and describe why one patch was not enough

Before this: Heartbleed

Heartbleed was a bug in how data was measured. Shellshock was a bug in how data was interpreted: a place where text that should only be stored was treated as instructions. That mix-up, data becoming code, is behind many of the bugs we will meet later, including prompt injection in AI agents. So it is worth understanding it in a small, concrete setting.

What a shell and an environment variable are

A is the program that reads commands and runs them (the terminal lesson used one). Bash is the most common shell on Linux, and it is also used silently by countless scripts and services.

An is a named piece of text attached to a process: HOME=/Users/sam, LANG=en_GB. When one program starts another, the child receives a copy of the parent's variables. That inheritance is useful, and it means variables are a channel along which text flows from one program to the next without anyone looking at it.

Try this in a terminal:

export GREETING="hello"
bash -c 'echo $GREETING'

The new Bash process prints hello: it inherited the variable.

A convenience feature

Bash has a feature for sharing functions (named bundles of commands) with child Bash processes. You export a function, and Bash passes it by stuffing its definition into an environment variable shaped like this:

NAME=() { echo hi; }

The child Bash sees a variable whose value starts with () {, recognises a function definition, and defines the function at startup. The design is old and useful. The danger is that this happens automatically, before any of your script runs, for every variable in the environment.

The bug

Bash parsed that value to find the function. Up to version 4.3, it kept going after the function ended. If there was extra text after the closing brace, Bash treated it as the next command and ran it, at the moment of import. The CVE record states this plainly: Bash "processes trailing strings after function definitions in the values of environment variables."

Step by step: a request value becomes an environment variable, a child Bash starts and imports it as a function, and the trailing text is run as a command

The point is the absence of a boundary: a variable meant to hold a function definition also carried a command, and the parser could not tell the difference. This is weakness CWE-78, OS command injection, in a new place. The place matters because of who could set that variable.

Normal import compared with the bug: a variable holding only a function definition versus one with extra commands after it, which Bash also ran

Why remote strangers could reach it

The record lists the situations: the ForceCommand feature in the OpenSSH server, the mod_cgi and mod_cgid modules in the Apache HTTP Server, scripts run by some DHCP clients, and any case "in which setting the environment occurs across a privilege boundary from Bash execution."

Take the web case. A CGI program is a script a web server runs for each request. By convention the server passes pieces of the request into environment variables: the browser's User-Agent header becomes HTTP_USER_AGENT, and so on. If that script, or a program it launches, is Bash, then a stranger's header value is parsed by Bash as a possible function definition. Nobody wrote any code to run the stranger's text. The shell did it on its own while starting up.

That is why the record scores it as network-reachable with no privileges and no user interaction, 9.8 out of 10 (critical).

The real case

Three things to note from the record:

  • Severity. CVSS 9.8, critical. Every impact measure is high and nothing was required of the victim.
  • The incomplete fix. The record adds that "the original fix for this issue was incorrect; CVE-2014-7169 has been assigned to cover the vulnerability that is still present after the incorrect fix." Patch the symptom you can see and a neighbouring path may remain. Real fixes change the parsing so that importing a function can never run anything else, and defenders had to update again.
  • Timeline. Published 2014-09-24; added to the CISA Known Exploited Vulnerabilities catalogue on 2022-01-28.

What defenders do

  • Update Bash to a build that includes the later fixes, and apply vendor updates for appliances that bundle it.
  • Do not run web requests through Bash if you can avoid it. Modern frameworks do not use CGI shells; reducing the number of places where remote text meets a shell shrinks the attack surface.
  • Use a plainer shell for system scripts. Many Linux systems use a minimal shell for scripts that does not have Bash's function-import feature at all.
  • Limit what the service can do so that a flaw gives little. A web process running as a locked-down user can do much less than one running as an administrator.

How AI changes this

Attackers. Shellshock is a case where the vulnerable component was ubiquitous and rarely thought about. Automated scanners can test every reachable endpoint for such flaws within hours of publication; AI makes it easier to adapt probes to unusual deployments. The Beam post on PHP2Shell and AI agents reports Cloudflare seeing attack traffic before the patch, and attackers changing payloads to evade rules. The race from disclosure to scanning is the part that has shortened.

Defenders. AI can help list which of your systems run the affected component, read vendor advisories into an action list, and explain unfamiliar script behaviour. What it cannot replace is verification: confirm each host is actually fixed.

Agents. Shellshock is the older sibling of an agent problem. A coding agent builds shell commands from text it read, which may include a web page, an issue comment or a file name. If that text slips into the command unquoted, data becomes instructions again, which is the shape of . Agents also launch child processes that inherit the agent's environment variables, including any secrets stored there. Beam's guide to stopping dangerous commands in Claude Code shows checking the command before it runs.

Try it

First look at how your shell shares variables and functions on your own machine:

greet() { echo "hi from a function"; }
export -f greet
bash -c 'greet'

(export -f is a Bash feature; if your shell is zsh, run bash first.) The child Bash uses a function it never defined, imported from the environment.

Now check whether your Bash is a vulnerable version. This command only sets one variable and prints a word, and it touches only your own shell:

env x='() { :;}; echo VULNERABLE' bash -c 'echo test'
bash --version

A fixed Bash prints only test. If it prints VULNERABLE, that Bash still has the bug; update it. On current macOS and Linux you should see only test.

Check yourself

In one sentence, what was the mistake in Bash that became Shellshock?

When importing an exported function from an environment variable, Bash also executed any text that came after the function's body, so data that should have been stored was run as commands.

Why did a web server running a CGI script give strangers a way in, even though no one wrote code to run their input?

The server copies parts of the request, such as headers, into environment variables for the script. If Bash starts, it parses those variables at startup, so the stranger's text reached the buggy parser without any application code involved.

The first patch for CVE-2014-6271 was incomplete. What should a defender take from that?

Verify that a fix actually closes the hole rather than assuming the first update is final, and track follow-up advisories. A patch can block the example payload while leaving a related path open.

Next

Shellshock mostly ran with the permissions of a service. Next we study how attackers, and bugs, climb from a limited account to administrator.