Skip to main content

Command Palette

Search for a command to run...

Linux File System Hunting

Updated
13 min read
Linux File System Hunting

Linux hides almost nothing — if you know where to look. The file system isn't just a place to store data. It is the operating system: kernel parameters, network state, process memory, hardware abstractions, security policies — all of it exposed as files you can read (and sometimes write) with any text editor.

This is a walk through what I found when I stopped running tutorials and started actually investigating the system. No ls cheatsheets. Just genuine discoveries, and why they matter.

The Findings

01. /etc/nsswitch.conf

The Invisible Traffic Controller for Every Name Lookup

Most developers know that /etc/resolv.conf holds your DNS server. Far fewer know that it doesn't always get consulted first — or at all. That decision lives in /etc/nsswitch.conf, the Name Service Switch configuration.

# /etc/nsswitch.conf excerpt from a live system
passwd:    files
group:     files
hosts:     files dns      ← this line decides everything
networks: files

The hosts line reads left to right: check /etc/hosts first (files), then query DNS (dns). This is why adding a line to /etc/hosts can override DNS without touching any DNS server — the system just stops looking after it finds a match in the file.

Key Insight

This is why security tools and malware alike abuse /etc/hosts for redirection. If an attacker writes 192.168.1.50 bank.example.com into /etc/hosts, no DNS firewall saves you — because DNS never gets asked. The nsswitch.conf order makes local files the final word.

On systems with LDAP, NIS, or SSS integration, this file can also route group and password lookups to a directory server — meaning a single machine's authentication behavior can be radically different just by changing one line in this file.


02. /proc/net/route

Your Routing Table Is Just a File — Written in Hex

Tools like ip route and netstat -r feel like they're querying a database. They're not — they're reading a file. The kernel's entire routing table is exposed at /proc/net/route.

Iface       Destination  Gateway   Flags  Mask
d251894c2a  06000415     00000000  0001   7FFFFFFF
d251894c2a  00000000     07000415  0003   00000000

Everything is in little-endian hexadecimal. The destination 00000000 with mask 00000000 is the default route (0.0.0.0/0 — match anything), and its gateway 07000415 decodes byte-by-byte reversed to 21.4.0.7.

Key Insight

The flag value 0003 is a bitmask. Flag 0x1 means the route is usable, flag 0x2 means a gateway is involved. A route with flag 0001 but no gateway is a directly-connected network — the machine can reach those hosts without going through any router. Understanding this without any tools is how you debug networking in a stripped-down container or recovery environment where nothing is installed.

This also reveals something conceptually important: the routing table isn't some opaque kernel structure locked away in memory. Linux deliberately surfaces it as a file so that any program can read it using standard file I/O — no special syscalls required.


03. /proc/1/cmdline & /proc/1/status

PID 1 Is the Ancestor of Everything — And You Can Interrogate It

Every process on Linux has an entry inside /proc/ named by its process ID. PID 1 is special: it's the first process the kernel starts, the parent of all others. Killing it crashes the system. Reading it tells you everything about how the machine was bootstrapped.

# What is PID 1?
cat /proc/1/cmdline | tr '\0' ' '
→ /process_api --addr 0.0.0.0:2024 --memory-limit-bytes 4294967296

# Its security posture
cat /proc/1/status | grep Cap
→ CapEff: 00000000a82c35fb   ← effective Linux capabilities
→ Seccomp: 0               ← no seccomp filter active

Arguments in cmdline are NUL-separated (not space-separated), which is why the tr conversion is needed. On a traditional server, PID 1 would be systemd or an older init. Here, we're in a container and PID 1 is a custom process API — proof that containers can completely replace the init system.

Key Insight

The CapEff field decodes to a bitmask of Linux capabilities — fine-grained privileges that replaced the blunt "root vs not-root" model. A process can have CAP_NET_BIND_SERVICE (bind port 80) without having CAP_SYS_PTRACE (debug other processes). Security-hardened containers strip this bitmask down to the minimum. The fact that you can inspect the current capability set of any process through a file, without root, is a powerful forensic tool.


04. /etc/shadow & /etc/passwd

Why Passwords Are Stored in a File You Can't Read

The split between /etc/passwd and /etc/shadow is one of Linux's most important security evolutions — and the reason for it is entirely about file permissions.

# /etc/passwd — world-readable (permissions: 644)
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

# /etc/shadow — root + shadow group only (permissions: 640)
-rw-r----- 1 root shadow 609 Apr 18 18:13 /etc/shadow

The x in passwd's password field literally means "the real hash is in shadow." Before shadow passwords existed, the hash lived in /etc/passwd itself — world-readable. Any user could read and offline-crack every password on the system.

Key Insight

The shell field (/usr/sbin/nologin) on service accounts like www-data and daemon is a security mechanism — even if someone obtains that account's credentials, they cannot get an interactive shell. The OS literally refuses to start one. This is why compromised web servers don't immediately hand attackers a bash prompt despite running as www-data.

The /etc/group file follows the same pattern — it defines which users belong to which groups, and those group memberships determine what directories, devices, and system calls a user can access without needing full root privileges.


05. /proc/self/cgroup

cgroups: The File That Proves You're in a Container

Control groups (cgroups) are the kernel mechanism that enforces resource limits on processes — they're what makes Docker containers actually isolated. And like everything in Linux, they're exposed as a file system.

cat /proc/self/cgroup
7:pids:     /container_015kGjezqhihRU3xsUmgiAsc/...
6:memory:  /container_015kGjezqhihRU3xsUmgiAsc/process_api/f65f9f8c491c
5:job:      /container_015kGjezqhihRU3xsUmgiAsc
1:cpu:      /container_015kGjezqhihRU3xsUmgiAsc

Each line is a cgroup subsystem and the hierarchical path within it. The memory cgroup enforces RAM limits, pids caps the number of processes, cpu controls scheduling weight. This system was discovered running inside a named container — visible right in the cgroup path.

Key Insight

Reading /proc/self/cgroup is one of the oldest tricks for detecting whether you're inside a Docker/container environment. A native Linux process has a path like / or a short string. A containerized process has a long UUID-style path. Security researchers and escape-detection tools use exactly this file to determine their execution context without any container-specific tools.

The limits set by these cgroups are enforced by the kernel — not userspace. A process cannot exceed its memory limit by any trick; the kernel's OOM killer will terminate it first. The limits live under /sys/fs/cgroup/memory/ as writable files, meaning the container runtime sets resource limits by simply writing a number to a file.


06. /proc/self/fd & /dev/stdin

Everything Is a File — Including Your Open Connections

"Everything is a file" is the most-quoted Linux philosophy and the most under-appreciated. /proc/self/fd makes this tangible: it lists every file descriptor the current process has open, as symbolic links.

ls -la /proc/self/fd
0 → pipe:[36]    ← stdin  (a pipe, not a terminal)
1 → pipe:[37]    ← stdout (also a pipe)
2 → pipe:[38]    ← stderr
3 → /proc/66/fd  ← reading another process's fd directory

File descriptor 0, 1, and 2 are always stdin, stdout, and stderr. Here they all resolve to pipe:[N] — meaning this process's standard streams are connected to pipes, not to a terminal. That's how non-interactive execution works.

Key Insight

The /dev/ directory also exposes this abstraction at the system level. /dev/stdin is a symlink to /proc/self/fd/0, /dev/null is a character device that discards everything written to it, and /dev/zero produces infinite zero bytes on read. None of these are real hardware — they're kernel-provided virtual devices implemented as files. A process can fill RAM with zeros in a loop by reading /dev/zero, and wipe sensitive data by writing to /dev/null. The file abstraction isn't metaphor — it's the actual API.


07. /proc/sys/net/ipv4/ip_forward

One Byte That Decides Whether This Machine Becomes a Router

Network configuration in Linux isn't just about interface addresses. The kernel has hundreds of tunable parameters exposed under /proc/sys/, and they govern behavior that would require separate hardware or OS features on other systems.

cat /proc/sys/net/ipv4/ip_forward
→ 0    ← forwarding disabled (this host is an endpoint, not a router)

# To turn this machine into a router at runtime:
echo 1 > /proc/sys/net/ipv4/ip_forward

# Other security-relevant parameters found here:
/proc/sys/net/ipv4/tcp_syncookies      ← SYN flood protection
/proc/sys/net/ipv4/conf/all/accept_redirects  ← ICMP redirect acceptance
/proc/sys/kernel/pid_max              → 65536 (max concurrent processes)

Writing a 1 to ip_forward turns the machine into an IP router — packets arriving on one interface get forwarded out another. This is exactly how Docker enables container networking, how VPNs work, and how cloud VMs act as NAT gateways. It happens with a single file write, no reboot.

Key Insight

Accepting ICMP redirects (accept_redirects) is a classic security misconfiguration. ICMP redirects tell a host "use this different gateway instead." An attacker on the local network can send forged redirects to silently reroute a victim's traffic through themselves — a man-in-the-middle attack via a kernel parameter. This is why hardened server configs always set this to 0.


08. /etc/pam.d/

The Authentication Stack Nobody Talks About

When you ssh into a server or run sudo, what actually checks your password? It's not the application itself — it's PAM, the Pluggable Authentication Modules framework, configured entirely through files in /etc/pam.d/.

# /etc/pam.d/common-auth — applies to ALL services
auth  [success=1 default=ignore]  pam_unix.so  nullok
     ↑ if unix auth succeeds, skip 1 line (skip pam_deny)
auth  requisite                   pam_deny.so
     ↑ if we get here, authentication FAILS immediately

Each line has four fields: the module type (auth, account, session, password), the control flag (how failure is handled), the module name, and options. The success=1 syntax means "on success, skip the next 1 module" — an elegant way to implement short-circuit logic without if/else.

Key Insight

PAM's power is in its pluggability. You can add multi-factor authentication system-wide by inserting a single line into common-auth — without touching sshd, sudo, or login at all. Google Authenticator on Linux works exactly this way: a PAM module. Conversely, a misconfigured PAM file can lock every user out of the system or silently bypass authentication entirely. A single typo in /etc/pam.d/sudo is one of the fastest ways to break a Linux server irreparably.


09. /proc/self/maps

Every Running Process's Memory Layout, Exposed as Text

Want to know exactly what memory a process is using, where shared libraries are loaded, and what permissions each region has? It's all in /proc/[pid]/maps.

# From /proc/1/maps — the process_api binary's memory layout
55cab0551000-55cab0568000  r--p  00000000  /process_api  ← read-only: ELF header
55cab0568000-55cab07de000  r-xp  00017000  /process_api  ← execute: code section
55cab07de000-55cab086f000  r--p  0028d000  /process_api  ← read-only: rodata
55cab0870000-55cab0881000  rw-p  0031e000  /process_api  ← read-write: data/BSS
55cab0883000-55cab0884000  ---p  00000000  (anonymous)    ← guard page
55cab0884000-55cab0885000  rw-p  (heap)

The permission column (r-xp, rw-p, etc.) shows what the process can do with each region. Code is marked execute (x), data is read-write but not executable. The anonymous guard page with ---p (no permissions at all) is a deliberate trap: accessing it causes a segfault, catching stack overflows before they corrupt adjacent memory.

Key Insight

This is the physical manifestation of W^X (Write XOR Execute) — a security principle that says no memory should be both writable and executable at the same time. If an attacker injects shellcode into a writable region, the CPU refuses to execute it. Modern Linux enforces this at the hardware level, and you can verify compliance for any running process directly from its maps file by checking for regions that are both w and x.


10. /proc/self/environ & /etc/environment

Environment Variables Are Inherited Memory — And They Leak Secrets

Environment variables feel ephemeral — set in a shell, used by a program, forgotten. But Linux preserves every process's environment at the time of launch inside /proc/[pid]/environ, readable for the lifetime of the process.

cat /proc/self/environ | tr '\0' '\n'
HOME=/root
PATH=/home/claude/.npm-global/bin:/usr/local/sbin:...
NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
IS_SANDBOX=yes
DEBIAN_FRONTEND=noninteractive

System-wide defaults come from /etc/environment. Per-user defaults come from ~/.profile or ~/.bashrc. Service-level defaults come from systemd unit files. Each layer overrides the previous one.

Key Insight

This is one of the most common sources of credential leaks in production systems. Applications often read secrets from environment variables (API keys, database passwords) — and those values sit in /proc/[pid]/environ, readable by root and by the process owner. On a multi-tenant system, a misconfigured process running as root with loose file permissions can expose every secret of every running service simultaneously. The "12-factor app" pattern of storing secrets in environment variables was always a trade-off, not a best practice.

Also notable: DEBIAN_FRONTEND=noninteractive in the environment tells the apt package manager to never prompt for user input — a common pattern in container images and CI systems where no human is present to answer questions.


"Linux doesn't hide its internals — it exposes them as files.
The system investigator's job is learning to read them."


Quick Reference

# File / Path What It Reveals Why It Matters
01 /etc/nsswitch.conf Name resolution order Controls if DNS is even consulted
02 /proc/net/route Full kernel routing table Debuggable without any tools installed
03 /proc/1/cmdline PID 1 identity & capabilities Reveals init system & security posture
04 /etc/shadow Hashed credentials Permissions model prevents offline cracking
05 /proc/self/cgroup Container membership Proves containerization, shows resource limits
06 /proc/self/fd Open file descriptors Shows real I/O topology of any process
07 /proc/sys/net/ipv4/ Kernel network tunables Runtime routing & security behavior
08 /etc/pam.d/ Authentication stack Governs every login systemwide
09 /proc/[pid]/maps Process memory layout Verifies W^X security enforcement
10 /proc/self/environ Live environment variables Common vector for secret leakage

Closing Thoughts

The Linux file system is not a storage mechanism. It's a universal interface — the same abstraction (open, read, write, close) that works on a text file also works on a kernel parameter, a hardware device, a process's memory, and an active network connection.

This design philosophy means a curious system investigator with nothing but cat and a willingness to explore /proc can learn more about a running system than most dashboards will ever show. The information was always there — it just required knowing where to look.

Every finding in this post was discovered from a live Linux environment. No man pages were harmed in the making of this blog.