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/hostsfor redirection. If an attacker writes192.168.1.50bank.example.cominto/etc/hosts, no DNS firewall saves you — because DNS never gets asked. Thensswitch.conforder 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
0003is a bitmask. Flag 0x1 means the route is usable, flag 0x2 means a gateway is involved. A route with flag0001but 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 likewww-dataanddaemonis 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 aswww-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/cgroupis 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/stdinis a symlink to/proc/self/fd/0,/dev/nullis a character device that discards everything written to it, and/dev/zeroproduces 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 to0.
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 touchingsshd,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/sudois 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
mapsfile by checking for regions that are bothwandx.
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.
