Step 108. Environment Variables and PATH Injection

Step 108. Environment Variables and PATH Injection

Level 2 — Security Introduction and Attack Skill Basics | Difficulty ★★★☆☆ | Estimated time: 2.5 hours

Prerequisites: permissions and sudo from Steps 23–24, setuid from Step 106, shell script basics from Step 28.

  • What you need: a Linux terminal (Ubuntu/WSL). All of today’s experiments happen under /tmp and inside your terminal session — close the window and everything returns to normal.
  • ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.
  • Caution: the "fake command" we build today is a harmless echo script, and when the experiment ends we restore PATH and delete the files. Applying this technique to someone else’s session or server is an unambiguous attack.

When you type ls, what exactly does the computer execute? The correct answer is "it depends." The shell searches the folders listed in an environment variable called PATH, from front to back, and runs the first ls it finds. In other words, "what gets executed" is decided by a single variable.

Which raises this question — what if a folder I can write to lands at the very front of PATH? And what if some high-privilege script calls ls without a path? My fake ls executes instead, with those privileges. That’s PATH injection. Today we start from how environment variables work, reproduce this attack ourselves, and close it with defenses.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Explain what environment variables are, and read and write them with export and $VARIABLE
  • Check a command’s search path and real location with echo $PATH, which, and type
  • Reproduce the experiment of creating a fake command and slipping it in via PATH manipulation
  • Explain why calling with absolute paths neutralizes this attack
  • Organize PATH injection’s attack scenario and defenses (absolute paths, PATH initialization, sudo secure_path)

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Linux shell (bash); experiment folder is /tmp/fake
Today’s commands echo $PATH, export PATH=..., which, type, env, ${PATH#prefix} (restoration)
Concepts needed Environment variables, PATH search order (front wins), absolute path vs name-only call, PATH injection, sudo’s secure_path
Today’s artifact PATH injection reproduction records + an attack-scenario/defense memo

2-1. Environment Variables — A Bulletin Board Open to Every Program

An environment variable is a "name=value" list that travels with a process. When you run a program from the shell, the environment variables are passed to the child as-is. See the whole list with env, and read one with echo $NAME.

The famous ones are HOME (my home folder), USER (my name), LANG (language settings), and today’s protagonist PATH. Change one in the shell and it applies only to that shell and programs launched afterward — close the terminal and it’s gone. That’s why today’s experiment is safe.

2-2. PATH — The Search Order of the Command Detective

PATH is a colon-separated list of folders. For example:

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:...

Type ls and the shell searches this list from the left. Is there a /usr/local/sbin/ls? No. … /usr/bin/ls found → run it. There’s one core rule — the folder in front wins. If programs with the same name exist in two folders, the one earlier in PATH runs.

Conversely, a call containing /, like ./run.sh, bypasses PATH entirely — because a path was specified. This contrast is the axis of today’s attack and defense.

2-3. PATH Injection — Poisoning the Search Order

The attack is structured like this.

① The attacker plants a fake ls in a writable folder (e.g., /tmp/fake)
② Some script calls "ls" by name in an environment whose PATH front is poisoned
③ The shell's search checks /tmp/fake first, and the fake runs
④ If that script has elevated privileges via setuid or sudo → the fake runs with elevated privileges too

Note that there are two conditions — PATH must be poisoned, and the caller must use only the name, without a path. Break either one and the attack fails. That’s why the defenses target exactly those two.

2-4. sudo Doesn’t Trust PATH

This attack is historically famous, so defenses are already built in everywhere. The representative one is sudo’s secure_path setting — when you run a command with sudo, it discards the user’s PATH and replaces it with the safe list written in /etc/sudoers. So on modern systems, the doors where PATH injection works are narrow — sudo misconfigurations (keeping PATH via env_keep), or setuid scripts and cron jobs that don’t initialize PATH themselves, are the main prey.


3. Follow Along

3-1. Reading PATH — Confirming the Search Order (measured)

echo $PATH
which ls
type ls

Output (measured 2026-09-09, WSL — the tail is environment-specific paths, omitted/adjusted):

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:...
/usr/bin/ls
ls is /usr/bin/ls

How to read it: the left side of the colon-separated list is the folder searched first. which shows "what actually runs when you type this name," and type shows how the shell interprets the name (external command, shell builtin, or alias). Right now it’s /usr/bin/ls — normal. These two commands are today’s "before/after comparison" tools.

3-2. Making a Fake Command (measured)

mkdir -p /tmp/fake
printf '#!/bin/bash\necho "Gotcha! I am the fake ls"\n' > /tmp/fake/ls
chmod +x /tmp/fake/ls
cat /tmp/fake/ls

Output (measured 2026-09-09):

#!/bin/bash
echo "Gotcha! I am the fake ls"

How to read it: the filename is ls, but the contents are a harmless echo. Without chmod +x it isn’t treated as an executable and the attack fails — Step 23’s x permission reappears here.

3-3. Poisoning PATH — The Moment the Fake Wins (measured)

export PATH=/tmp/fake:$PATH
which ls
ls

Output (measured 2026-09-09):

/tmp/fake/ls
Gotcha! I am the fake ls

How to read it: which‘s answer changed, and ls actually ran the fake. All we did was prepend /tmp/fake: to $PATH — seizing the front of the search order. You’ve just witnessed the same two letters ls becoming a completely different program with your own eyes.

3-4. Absolute Paths Don’t Shake (measured)

In the same poisoned state, let’s call with a path attached.

/bin/ls /tmp/fake

Output (measured 2026-09-09):

ls

How to read it: the real ls ran and correctly printed the contents of /tmp/fake (the single fake ls). A call containing / bypasses PATH, so it’s unaffected by the poisoning. This is defense #1.

3-5. Reproducing the Vulnerable Script Scenario (measured)

Now let’s create the role of "a script running with elevated privileges" and complete the attack. Assume an admin’s inspection script calls ls without a path.

printf '#!/bin/bash\nls /tmp/fake\n' > /tmp/fake/backup_check.sh
chmod +x /tmp/fake/backup_check.sh
echo "--- normal PATH ---"
/tmp/fake/backup_check.sh
echo "--- poisoned PATH ---"
export PATH=/tmp/fake:$PATH
/tmp/fake/backup_check.sh

Output (measured 2026-09-09):

--- normal PATH ---
backup_check.sh
ls
--- poisoned PATH ---
Gotcha! I am the fake ls

How to read it: the script didn’t change by a single letter, yet the outcome split. One variable changed the program’s behavior. If this script were setuid or executed via a sudo rule, the Gotcha! spot could have contained any command the attacker wanted — that’s the power of PATH injection.

3-6. Restoration — The End of the Experiment (measured)

export PATH=${PATH#/tmp/fake:}
which ls

Output (measured 2026-09-09):

/usr/bin/ls

How to read it: ${PATH#/tmp/fake:} is shell syntax meaning "strip /tmp/fake: from the front of PATH." A simpler restoration is closing and reopening the terminal — export is valid only within the session. Finally, delete the fake too: rm -rf /tmp/fake (check the path before running — rm -rf cannot be undone).

3-7. Confirming sudo’s Defense (measurement and reading the config)

grep -r secure_path /etc/sudoers /etc/sudoers.d/

Output (measured 2026-09-09):

/etc/sudoers:Defaults	secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"

How to read it: sudo discards the user’s poisoned PATH and replaces it with this list. For reference, in our measurement, when root ran sudo sh -c 'echo $PATH', PATH was preserved — root is exempt from this reset. For regular users’ sudo, it applies. So PATH injection’s real-world targets narrow down not to sudo but to setuid scripts and cron jobs that don’t sanitize PATH themselves.

3-8. Summarizing the Attack Scenario and Defenses

Let’s fold today’s experiment into an offense/defense document. Write this in your wiki.

### PATH injection
- Attack scenario: craft a fake command in a writable folder → poison the front of PATH →
  a high-privilege script (setuid, cron) that calls commands by name runs the fake
- Conditions: ① poisoned PATH ② path-less call — both required
- Defenses:
  1. Always use absolute paths in scripts (/bin/ls)
  2. Initialize PATH to a safe value at the top of scripts (export PATH=/usr/bin:/bin)
  3. Keep sudo's secure_path; never add PATH to env_keep
  4. Never put world-writable folders like /tmp in PATH (especially not first)

4. Missions & Exercises

Mission — Reproduce and Document the Whole PATH Injection Process

  1. Reproduce 3-2 through 3-5 — fake command creation, PATH poisoning, which before/after comparison, vulnerable script execution
  2. Repeat the same experiment once more with the fake command as cat instead of ls
  3. Restore with 3-6 and delete /tmp/fake
  4. Write an attack/defense summary document in your wiki in the 3-8 format — but write defense #2 (PATH initialization) only after actually applying it to the vulnerable script and confirming by experiment that it’s "safe even in a poisoned environment"

Exercises

Exercise 1. Explain the difference between ls and ./ls from the PATH search perspective, and pick which of the two is vulnerable to PATH injection.

Exercise 2. In export PATH=/tmp/fake:$PATH, what happens if you set only export PATH=/tmp/fake without appending $PATH at the end?

Exercise 3. State PATH injection’s two conditions, and connect one defense that breaks each condition.

Exercise 4. Despite sudo’s secure_path, name two targets where PATH injection is still a threat.


5. Model Answers & Completion Criteria

Mission Model Answer

The key comparison of the reproduction (based on measurements from 2026-09-09):

before poisoning, which ls → /usr/bin/ls
after poisoning, which ls → /tmp/fake/ls
vulnerable script (poisoned environment) → fake runs
vulnerable script + absolute path fix (/bin/ls) → runs correctly even in the poisoned environment

How to verify: ① does which change before/after, then return after restoration? ② did the fake cat experiment succeed too — a procedure confirming the technique isn’t specific to ls? ③ the defense experiment: after fixing the script to /bin/ls or adding export PATH=/usr/bin:/bin to its first line, does the fake stay away even when run from a poisoned terminal? ④ is /tmp/fake deleted?

Exercise Answers

Answer 1. ls searches the PATH list from the front and runs the first one found, so it’s vulnerable to poisoning. ./ls is a path specification meaning "the ls in the current folder," bypassing PATH and thus unrelated to injection. The vulnerable side is ls (name-only call).

Answer 2. PATH becomes only /tmp/fake, so /usr/bin and the rest can’t be found — nearly every external command like ls and grep becomes command not found. Since the terminal is paralyzed, whenever you change PATH you must always append the existing value ($PATH). (If paralyzed, close and reopen the terminal — it’s session-scoped, so it recovers.)

Answer 3. Condition ① poisoned PATH → defense: initialize PATH at the top of scripts, sudo secure_path. Condition ② path-less call → defense: always call with absolute paths (/bin/ls) in scripts. Breaking either one makes the attack fail.

Answer 4. ① setuid scripts/programs that don’t initialize PATH themselves, ② cron jobs that inherit the user’s environment (especially user crontabs). Neither passes through sudo, so they’re outside secure_path’s protection.

Completion Criteria Checklist

  • [ ] I can write and read environment variables with export and echo $NAME
  • [ ] I can explain that PATH is a search list where "left wins"
  • [ ] I know the difference between which and type
  • [ ] I reproduced the experiment of creating a fake command and slipping it in via PATH manipulation
  • [ ] I can restore with ${PATH#prefix} or by restarting the session
  • [ ] I confirmed by experiment that absolute-path calls neutralize PATH injection
  • [ ] Mission: I completed the attack/defense summary document in my wiki

6. Common Pitfalls & Fixes

Wall 1. I made a fake, but the real one runs

Symptom: you changed PATH, yet the normal ls appears.
Cause: ① you didn’t chmod +x the fake (not executable, so skipped), ② you appended /tmp/fake to the end of PATH instead of the front, ③ the terminal where you exported and the terminal where you run are different.
Fix: first check whether which ls points to /tmp/fake/ls. If it points there but the real one still runs, it’s an execute-permission issue — the shell silently skips non-executable candidates.

Wall 2. I overwrote PATH entirely and every command died

Symptom (Screen example):

bash: ls: command not found

Cause: you didn’t append the existing value, as in export PATH=/tmp/fake.
Fix: don’t panic — export is valid only within the session. Close the terminal and open a new one; done. To fix it in the same window, you can use shell syntax with absolute paths: /usr/bin/env is still alive.

Wall 3. I exported, but it doesn’t work in another window

Symptom: which ls in the neighboring terminal is normal.
Cause: environment variables are passed only to the process (that shell) and its children. Another terminal is another process.
Fix: it’s not a bug — it’s the nature of environment variables. Memorize "scope of an environment variable = that process and its descendants" — this property is also the device that made today’s experiment safe.

Wall 4. The vulnerable script works fine even in the poisoned environment

Symptom: the fake doesn’t appear; the real result comes out.
Cause: the call inside the script is an absolute path like /bin/ls, or the script initializes PATH itself.
Fix: that script is already defended — for the 3-5 reproduction, first confirm the call is "path-less." In the field too, PATH injection’s first procedure is reading how the target script makes its calls.

Wall 5. You forget to clean up after the experiment

Symptom: the next day, /tmp/fake and the poisoned-PATH habit are still around.
Cause: skipping 3-6.
Fix: the last commands of an experiment are always restoration and deletion. In particular, never do experiments that write PATH poisoning into a startup file like .bashrc — that’s installing a trap for yourself that springs the fake on every login.


7. Summary

Today’s Concepts

Concept One-line explanation
Environment variable A name=value list attached to a process and passed to its children
PATH The list of folders hunted through for command names — left wins
Name call vs path call ls searches PATH; /bin/ls and ./ls are direct specification
PATH injection An attack that poisons the front of PATH to slip in a fake command
Conditions ① poisoned PATH + ② path-less call — both required
secure_path The safe list sudo uses after discarding the user’s PATH
Session-scoped export applies only to that terminal and its children — close the window and it’s back to normal

Today’s Commands

Command What it does
echo $PATH View the search order
export PATH=/tmp/fake:$PATH Wedge a folder at the front of PATH (for experiments)
export PATH=${PATH#/tmp/fake:} Strip the wedged folder back out (restoration)
which command What actually runs when you type this name
type command How the shell interprets that name
env The full environment variable list
grep secure_path /etc/sudoers Check sudo’s PATH defense setting

An Instinct More Important Than Commands

Today’s lesson is one line: "environment is behavior." The same script becomes a different program through a single environment variable — this is why attackers look at a system not only at files but at settings and environment together. The defender’s instinct is simpler. The finger that attaches a path before commands when writing scripts — /bin/ls. And when reading someone else’s script, an eye that automatically checks "is this call a name or a path?" When Step 106’s setuid meets today’s PATH, you get the classic privilege-escalation combination. Next time you see a setuid file or a cron script, your first question should be this — "are the commands inside written with paths?"


Once every box is checked, Step 108 is complete. Click the checkbox in the sidebar to save your progress.