Linux
Step 109. Symbolic Links and Hard Links — Another Name for a File
Level 2 — Security Introduction and Attack Skill Basics | Difficulty ★★★☆☆ | Estimated time: 2–3 hours
Prerequisites: you’ve finished Steps 23–24 (permissions and ownership), Step 106 (setuid), and Step 108 (PATH injection). A Linux terminal (including WSL) is ready.
- What you need: one Linux terminal. All of today’s experiments happen only inside a practice folder in
/tmp; system files are read-only. - Caution: ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. Today’s experiments themselves are 100% safe, but the symbolic link abuse technique taught at the end is an ingredient of real privilege-escalation attacks — remember the boundary. All measurements in this chapter were performed in
/tmp/linklabon WSL Linux (Ubuntu 24.04).
Run ls -l and you get a file list. But that "filename" isn’t actually the file’s substance. The substance exists separately, and the name is just a tag pointing to it. And multiple tags can be attached to one substance — this is a link. Today we build both kinds of this tag system, hard links and symbolic links, with our own hands, and see why this harmless-looking feature is a favorite material of privilege-escalation attacks.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Explain the structure in which a file’s substance (inode) and its name are separated
- Create hard links and symbolic links, and confirm their difference with
ls -li - Demonstrate by experiment what happens to both links when the original is deleted
- Trace the substance a symbolic link points to with
readlink -f - Explain in a scenario how symbolic links are abused in attacks
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | Linux terminal (WSL or lab VM), practice folder /tmp/linklab |
| Today’s commands | ln original hardlink (hard link), ln -s target link (symbolic link), ls -li (check inodes), readlink -f (trace links), stat (file substance info), find -xtype l (find broken links) |
| Concepts needed | inode, directory entry (name→inode mapping), link count, symlink attack |
| Today’s artifact | A practice folder proving the difference between the two links by experiment, plus summary notes |
2-1. The Substance of a File — the inode
On Linux, a file is split into two layers. The numbered chunk holding the data’s substance (contents, permissions, owner, etc.) is the inode (index node), and the "filename" we see is just one line of a mapping table written inside a directory — "this name points to inode number X."
That’s why renaming with mv a.txt b.txt leaves the contents intact, and why "attaching multiple names to one substance" — today’s topic — is possible. The name is a tag; the inode is the house.
2-2. Hard Links — Another Front Door to the Same House
A hard link creates one more name pointing to the same inode. The original and the hard link are equals — you can’t tell which is the "real" one; both are doors to the same substance. Delete one side and the substance survives as long as another name remains (in reality, the data is released when the link count reaches 0).
There are two constraints. You can’t make one on a directory, and you can’t cross filesystems (other disks/partitions) — because an inode number is valid only within its own filesystem.
2-3. Symbolic Links — A File Named "Shortcut"
A symbolic link (symlink) is "a special file containing text that points to another path." It’s similar to a Windows shortcut (.lnk), but on Linux most programs automatically follow symbolic links and treat them as the original — far more powerful, and just as much more dangerous.
Symbolic links have none of the hard link’s constraints. They can point to directories, to other filesystems, even to paths that don’t exist (which makes them "broken links").
2-4. Why Are We Learning This in a Security Chapter?
Think about the property that programs "automatically follow links." Suppose a script running with admin privileges records results to /tmp/report.txt. What if an attacker, knowing this, has already made /tmp/report.txt a symbolic link pointing to /etc/shadow? The script unknowingly overwrites /etc/shadow. This is the basic form of a symlink attack.
Programs working in world-writable public directories like /tmp, predictable temporary filenames — when these two meet, accidents happen. In today’s final experiment, we reproduce this structure inside a safe practice folder.
3. Follow Along
3-1. Preparation — Practice Folder and Original File
Input
mkdir -p /tmp/linklab && cd /tmp/linklab
echo "original content line 1" > original.txt
How to read it: /tmp is a public space anyone can write to, making it ideal for experiments, and it empties naturally on reboot. All of today’s experiments happen only inside this folder.
3-2. Making a Hard Link — Same inode
Input
ln original.txt hard.txt
ln -s /tmp/linklab/original.txt sym.txt
ls -li
Output (measured 2026-09-09):
total 8
63899 -rw-r--r-- 2 root root 19 Sep 9 15:07 hard.txt
63899 -rw-r--r-- 2 root root 19 Sep 9 15:07 original.txt
63900 lrwxrwxrwx 1 root root 25 Sep 9 15:07 sym.txt -> /tmp/linklab/original.txt
How to read it: the -i option shows the inode number at the very front. hard.txt and original.txt have the exact same number, 63899 — two names attached to one substance. The 2 after the permission field is the link count (the number of names pointing to this substance). sym.txt, on the other hand, has a different inode (63900), the first character of the permission field is l (a link file), and next to the name it shows -> what it points to.
Why: this one picture comparing the three files at a glance is today’s key photograph. The inode numbers prove that a hard link is "another indistinguishable original," while a symbolic link is "a separate file that is a shortcut."
3-3. Modify One Side — A Hard Link Is the Same Body
Input
echo "line 2 added via the hard link" >> hard.txt
cat original.txt
Output (measured 2026-09-09):
original content line 1
line 2 added via the hard link
How to read it: we wrote only to hard.txt, yet the line was added to original.txt too. Two names, one body — the natural result. Attach this instinct to your body: "not a copy but an alias."
3-4. Following a Symbolic Link — It Can Point to Anything
This time, let’s make a link pointing to a system file. We only read it.
Input
ln -s /etc/passwd passwd_link.txt
head -3 passwd_link.txt
ls -l passwd_link.txt
readlink -f sym.txt
Output (measured 2026-09-09):
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
lrwxrwxrwx 1 root root 11 Sep 9 15:07 passwd_link.txt -> /etc/passwd
/tmp/linklab/original.txt
How to read it: reading passwd_link.txt produced the contents of /etc/passwd — because programs automatically followed the link. readlink -f tells you the absolute path of the final substance even when links are chained. It’s the standard tool for checking "what is this file’s true identity?" in logs and scripts.
3-5. The Delete-the-Original Experiment — The Decisive Difference Between the Two Links
Input
rm original.txt
ls -li
cat hard.txt
cat sym.txt
Output (measured 2026-09-09):
=== after deleting the original ===
total 4
63899 -rw-r--r-- 1 root root 50 Sep 9 15:07 hard.txt
63886 lrwxrwxrwx 1 root root 11 Sep 9 15:07 passwd_link.txt -> /etc/passwd
63900 lrwxrwxrwx 1 root root 25 Sep 9 15:07 sym.txt -> /tmp/linklab/original.txt
original content line 1
line 2 added via the hard link
cat: sym.txt: No such file or directory
How to read it: we deleted the original, yet hard.txt shows the contents just fine — the link count merely dropped from 2 to 1; the substance lives on. sym.txt, by contrast, still exists as a file but the target it follows is gone — it became a broken link. The error message is amusing — sym.txt clearly exists, yet No such file or directory. This error doesn’t mean "the link file is missing"; it means "the target the link points to is missing." It’s confusing at first. That’s normal.
3-6. Hard Link Limits — Two Errors
Input
ln /tmp dir_hardlink
ln hard.txt /mnt/c/hard_cross.txt
Output (measured 2026-09-09):
ln: /tmp: hard link not allowed for directory
ln: failed to create hard link '/mnt/c/hard_cross.txt' => '/tmp/linklab/hard.txt': Invalid cross-device link
How to read it: you can’t make a hard link on a directory (hard link not allowed for directory), and you can’t cross to another filesystem (Invalid cross-device link — /mnt/c is the Windows drive in WSL, a separate filesystem). Both are possible with a symbolic link (ln -s). This difference in constraints is also the answer to "why the dangerous side is always the symbolic link."
3-7. Finding Broken Links
Input
find /tmp/linklab -xtype l
Output (measured 2026-09-09):
/tmp/linklab/sym.txt
How to read it: -xtype l selects "things that turn out to be links when followed," i.e., only broken symbolic links whose targets are missing. It’s useful when writing system inspection or cleanup scripts, and from the attacker’s side it’s also a reconnaissance technique for finding "links someone left behind."
3-8. Symlink Attack Demonstration — Inside the Practice Folder
Suppose there’s a script the administrator runs. This script records results to /tmp/linklab/report.txt every time. What happens if an attacker knows this and prepares in advance? Let’s reproduce it using only files I own.
Input
echo "important file only the admin may write" > important.txt
chmod 644 important.txt
ln -s /tmp/linklab/important.txt report.txt
echo "content the attacker sent through the symlink" > report.txt
cat important.txt
ls -l report.txt
Output (measured 2026-09-09):
--- important.txt result ---
content the attacker sent through the symlink
lrwxrwxrwx 1 root root 26 Sep 9 15:08 report.txt -> /tmp/linklab/important.txt
How to read it: the script (here, run by me as a stand-in) merely wrote to report.txt, yet what actually changed was important.txt. The write rode the link and flowed over to overwrite an unrelated file. Here both victim and attacker were my own files, so it was safe — but in this structure, if the script runs as root and the link’s target is /etc/shadow or root’s SSH public-key file — that’s what a real privilege-escalation incident looks like.
Why: the starting point of defense is here too. A privileged program must not write to predictable names in public directories, and must check before writing whether the target is a symbolic link (safety devices like O_NOFOLLOW) — these are the standard defenses.
4. Missions & Exercises
Mission — Link Experiment Report
In the /tmp/linklab folder, do the following and organize the results into notes (Notepad, Markdown, anything):
- Create a file
a.txt, then make a hard linkb.txtand a symbolic linkc.txt. - Paste the
ls -lioutput and mark which of the three files share an inode and which doesn’t. - Delete
a.txt, thencatb.txtandc.txteach, and record exactly what result (contents printed / error message) appears. - Record the output of
readlink -f c.txtand write in one line what this command does. - Finally, summarize "why symbolic links are dangerous" in two sentences, citing the 3-8 demonstration as evidence.
How to verify yourself: if your notes record the No such file or directory error explained as "not because the link file is missing, but because the target is missing," you’ve grasped the core.
Exercises
Exercise 1. In ls -li output, what are the two pieces of evidence showing that a hard link and the original are "the same substance"?
Exercise 2. Running cat on a symbolic link printed No such file or directory. Yet ls -l clearly shows the file exists. Explain this seemingly contradictory situation.
Exercise 3. Hard links can’t be made on directories and can’t cross filesystems. Symbolic links can do both. From the attacker’s standpoint, which side is more "useful," and why — explain from the perspective of the constraints.
Exercise 4. A script running as root every hour appends logs to /tmp/backup.log. What preparation would a regular user with write access to /tmp need to turn this into privilege escalation, and how can a defender block it?
Answers & completion criteria · expand/collapse
5. Model Answers & Completion Criteria
Mission Model Answer
Example procedure (based on the environment measured 2026-09-09):
cd /tmp/linklab
echo "mission original" > a.txt
ln a.txt b.txt
ln -s /tmp/linklab/a.txt c.txt
ls -li
64123 -rw-r--r-- 2 root root 7 Sep 9 15:20 a.txt
64123 -rw-r--r-- 2 root root 7 Sep 9 15:20 b.txt
64124 lrwxrwxrwx 1 root root 17 Sep 9 15:20 c.txt -> /tmp/linklab/a.txt
(Screen example — your inode numbers and timestamps will differ. The same structure is what matters.)
Mark that a.txt and b.txt share the same inode number and link count (2), while only c.txt has a different inode and a -> marker. Then:
rm a.txt
cat b.txt # → prints "mission original" (hard link survives)
cat c.txt # → cat: c.txt: No such file or directory (broken link)
readlink -f c.txt # → /tmp/linklab/a.txt (shows the path even of a missing target)
Example summary sentence: "A symbolic link is a shortcut that programs follow automatically. When a privileged program opens a file at a path I can write to, if that file is a link, the write/read flows to the link’s target — the proof is that in 3-8, what I wrote to report.txt overwrote important.txt."
Exercise Answers
Answer 1. ① the inode numbers in the first column are identical (both 63899 in our measurement), and ② the link count after the permission field is 2. For reference, even the file size and modification time are completely identical — because it’s the same substance’s information shown twice.
Answer 2. What exists is the "link file" itself; what’s missing is "the target the link points to." cat follows the link and tries to open the target, and fails because the target is gone. The error message points at the link’s name, which confuses people, but it’s actually reporting the target’s absence. You can select only such broken links with find -xtype l.
Answer 3. The symbolic link. A hard link connects only to an "existing" inode on the same filesystem, but a symbolic link points to anything with just a path string — other filesystems, directories, files that don’t exist yet, and even system files like /etc/shadow. The decisive point is that an attacker can lay the trap knowing only the path, without knowing the target file’s inode.
Answer 4. Attack preparation: before the script runs, delete /tmp/backup.log and create a symbolic link with that name pointing to the target file (e.g., /etc/shadow or root’s /root/.ssh/authorized_keys). When the script writes its log, the content lands in the target file. Defense: root programs don’t write to predictable filenames in public directories; if they must, they check for links (O_NOFOLLOW); and they use isolation devices like /tmp‘s sticky bit (Step 106) and systemd’s PrivateTmp.
Completion Criteria Checklist
- [ ] I can explain that the inode is "the file’s substance" and the name is a tag
- [ ] I made a hard link with
lnand a symbolic link withln -s - [ ] I can read inode numbers, link counts, and
->markers inls -li - [ ] I confirmed by experiment that deleting the original leaves the hard link alive but breaks the symbolic link
- [ ] I know the true meaning of the
cat: symlink: No such file or directoryerror - [ ] I can trace a link’s final target with
readlink -f - [ ] I can explain the structure of a symlink attack (public directory + predictable name + privileged program)
- [ ] Mission: I wrote the link experiment report
6. Common Pitfalls & Fixes
Wall 1. The argument order of ln is confusing
Symptom: you write it backwards like ln -s sym.txt /etc/passwd and create the wrong link.
Cause: ln uses the same order as cp — ln [-s] original(target) newname(link).
Fix: memorize it "like copying." If it’s cp source copy, then it’s ln -s target link. After creating, always check with ls -l that the -> direction is as intended.
Wall 2. You’re scared deleting a link will delete the original
Symptom: typing rm sym.txt feels frightening.
Cause: uncertainty about whether deleting a link deletes the original too.
Fix: rest assured. rm deletes only the name (tag). Delete a symbolic link and the target is unharmed; delete one hard link and, as long as another name remains, the substance survives (the 3-5 measurement is proof). The substance disappears only when the last name is deleted.
Wall 3. Making a hard link gives an error
Symptom (measured 2026-09-09):
ln: failed to create hard link '/mnt/c/hard_cross.txt' => '/tmp/linklab/hard.txt': Invalid cross-device link
Cause: hard links are possible only within the same filesystem. WSL’s /mnt/c (the Windows drive) is a separate filesystem.
Fix: if you must cross filesystems, use a symbolic link (ln -s). The same goes for links to directories (hard link not allowed for directory).
Wall 4. A symbolic link made with a relative path breaks
Symptom: you made it with cd /tmp/linklab && ln -s a.txt c.txt, and viewed from another directory it’s a broken link.
Cause: the path inside a symbolic link is interpreted relative to where the link file sits. Move the link or view it from elsewhere, and the relative path points somewhere unintended.
Fix: like today’s exercises, the habit of creating links with absolute paths — ln -s /tmp/linklab/a.txt c.txt — is safe. Check whether any are broken with find path -xtype l.
Wall 5. The doubt "can symlink attacks really work that easily?"
Symptom: 3-8 is so simple that you’re skeptical it actually works.
Cause: good skepticism. Modern Linux has baseline defenses like /tmp‘s sticky bit and the kernel’s protected_symlinks setting.
Fix: the honest answer — even with baseline defenses, this technique still genuinely works on misconfigured setups, legacy systems, and self-written scripts. You’ll keep meeting it in wargames and real privilege escalation (in the "gaps" list we’ll organize in Step 110). Knowing the principle is the prerequisite for spotting it.
7. Summary
Today’s Concepts
| Concept | One-line explanation |
|---|---|
| inode | The numbered chunk holding the file’s substance (contents, permissions, owner) |
| Hard link | Another name pointing to the same inode — equal to the original |
| Symbolic link | A special file pointing to another path — a shortcut |
| Link count | The number of names pointing to one inode — released when it hits 0 |
| Broken link | A symbolic link whose target is gone (found with find -xtype l) |
| Symlink attack | A technique that diverts a privileged program’s writes/reads to an unintended file via a link |
Today’s Commands
| Command | What it does |
|---|---|
ln original newname |
Create a hard link (same filesystem, files only) |
ln -s target linkname |
Create a symbolic link (points to anything; absolute paths recommended) |
ls -li |
Check inode numbers, link counts, -> targets |
readlink -f link |
Trace the absolute path of a link’s final substance |
stat file |
Detailed lookup of substance info like inode and link count |
find path -xtype l |
Find broken symbolic links |
An Instinct More Important Than Commands
There’s one picture today’s experiment should leave behind — the separation of name and substance. The filesystem is a tag-management system, and programs open not the tag but the substance behind it. Attackers work in this gap. Being able to ask the question "is the file this program opens really that file?" — that is today’s harvest.
The permission model looks sturdy on the outside, but it’s bypassed by a single "tag manipulation" like this. The setuid, PATH, and now link knowledge you’ve accumulated are all different faces of the same question — "does the configuration behave only as intended?"
Once every box is checked, Step 109 is complete.
ONE STEP FURTHER
Finished this lesson?
Check the completion criteria, then mark your progress.