Tools · Linux command line for security · 15 / 28
Which process is listening on a port? Using ss and lsof on Linux
Map every listening socket and established connection to a process and user with ss -tulpn and lsof -i, and spot services bound where they should not be.
ss replaces netstat
ss -tulpn shows TCP and UDP listening sockets, numeric addresses, and the owning process. Run it as root to see processes for every user. The Local Address column tells you what interface a service is bound to: 127.0.0.1 or ::1 means only local clients, 0.0.0.0 or [::] means every interface.
ss -tnp state established shows active connections and which process owns each one.
sudo ss -tulpn sudo ss -tnp state established sudo ss -tnp dst :443
lsof for detail
lsof -i :5432 lists processes with sockets on port 5432. lsof -p PID lists every file, library, and socket a process has open, which is useful after you find a suspicious PID. lsof -i -n -P avoids slow name lookups.
A process with a socket to an unfamiliar external address on a high port deserves the questions from the previous tutorial: who, what started it, and where is the binary.
sudo lsof -i :5432 sudo lsof -i -n -P | grep ESTABLISHED sudo lsof -p 1234 | head -40
Lab: find a misbound database
Install PostgreSQL or Redis on a lab VM and change its listen setting to all interfaces. Use ss to see it on 0.0.0.0, scan it from your scanner VM to confirm reachability, then change it back to 127.0.0.1 and verify both locally and with Nmap.
This is one of the most common real findings: a database that only the local application needs, listening on every interface because a tutorial said to.
sudo sed -i "s/^#\?listen_addresses.*/listen_addresses = '*'/" /etc/postgresql/*/main/postgresql.conf sudo systemctl restart postgresql sudo ss -tlnp | grep 5432 # fix sudo sed -i "s/^listen_addresses.*/listen_addresses = 'localhost'/" /etc/postgresql/*/main/postgresql.conf sudo systemctl restart postgresql && sudo ss -tlnp | grep 5432
Baseline listeners
Save sudo ss -tulpn output for each server role. A new listener that does not match a deployment is worth investigating, because backdoors often open a port.
Remember that a firewall can block a 0.0.0.0 listener, but binding to localhost removes the exposure even if the firewall is later changed.
Diagram
A database bound to all interfaces versus localhost
Same service, different exposure.
Who can connect. Anyone who can reach any interface.
Depends on. Firewall rules staying correct.
Typical cause. Copied configuration.
Who can connect. Only local processes.
Depends on. Nothing else.
Fit. App and database on the same host.
Check yourself
1. What does a Local Address of 0.0.0.0:6379 mean?
2. Which command lists every file and socket a given process has open?
3. Why is binding to localhost better than relying only on the firewall?