What would you like to learn?

Try PowerShell, networks, XSS, or Step 138

Browse the full curriculum →

Linux

Step 109. Symbolic Links and Hard Links — Another Name for a File

Step 109Estimated practice · 2–3 hours

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/linklab on 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):

  1. Create a file a.txt, then make a hard link b.txt and a symbolic link c.txt.
  2. Paste the ls -li output and mark which of the three files share an inode and which doesn’t.
  3. Delete a.txt, then cat b.txt and c.txt each, and record exactly what result (contents printed / error message) appears.
  4. Record the output of readlink -f c.txt and write in one line what this command does.
  5. 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 ln and a symbolic link with ln -s
  • [ ] I can read inode numbers, link counts, and -> markers in ls -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 directory error
  • [ ] 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 cpln [-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 pathsln -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.

Something wrong with this page or a link? Let us know.
Next