Linux
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
/tmpand 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
echoscript, 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
exportand$VARIABLE - Check a command’s search path and real location with
echo $PATH,which, andtype - 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/bashnecho "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/bashnls /tmp/faken' > /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
- Reproduce 3-2 through 3-5 — fake command creation, PATH poisoning,
whichbefore/after comparison, vulnerable script execution - Repeat the same experiment once more with the fake command as
catinstead ofls - Restore with 3-6 and delete
/tmp/fake - 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.
Answers & completion criteria · expand/collapse
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
exportandecho $NAME - [ ] I can explain that PATH is a search list where "left wins"
- [ ] I know the difference between
whichandtype - [ ] 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.
ONE STEP FURTHER
Finished this lesson?
Check the completion criteria, then mark your progress.