how to audit for open services with iproute2
The computer world has a tendency of reinventing the wheel once in a
while. I am not a fan of that process, but sometimes I just have to
bite the bullet and adapt to change. This post explains how I adapted
to one particular change: the netstat
to sockstat
transition.
I used to do this to show which processes where listening on which port on a server:
netstat -anpe
It was a handy mnemonic as, in France, ANPE was the agency
responsible for the unemployed (basically). That would list all
sockets (-a
), not resolve hostnames (-n
, because it's slow), show
processes attached to the socket (-p
) with extra info like the user
(-e
). This still works, but sometimes fail to find the actual
process hooked to the port. Plus, it lists a whole bunch of UNIX
sockets and non-listening sockets, which are generally irrelevant
for such an audit.
What I really wanted to use was really something like:
netstat -pleunt | sort
... which has the "pleut" mnemonic ("rains", but plural, which makes
no sense and would be badly spelled anyway). That also only lists
listening (-l
) and network sockets, specifically UDP (-u
) and TCP
(-t
).
But enough with the legacy, let's try the brave new world of sockstat
which has the unfortunate acronym ss
.
The equivalent sockstat command to the above is:
ss -pleuntO
It's similar to the above, except we need the -O
flag otherwise ss
does that confusing thing where it splits the output on multiple
lines. But I actually use:
ss -plunt0
... i.e. without the -e
as the information it gives (cgroup, fd
number, etc) is not much more useful than what's already provided with
-p
(service and UID).
All of the above also show sockets that are not actually a concern because they only listen on localhost. Those one should be filtered out. So now we embark into that wild filtering ride.
This is going to list all open sockets and show the port number and service:
ss -pluntO --no-header | sed 's/^\([a-z]*\) *[A-Z]* *[0-9]* [0-9]* *[0-9]* */\1/' | sed 's/^[^:]*:\(:\]:\)\?//;s/\([0-9]*\) *[^ ]*/\1\t/;s/,fd=[0-9]*//' | sort -gu
For example on my desktop, it looks like:
anarcat@angela:~$ sudo ss -pluntO --no-header | sed 's/^\([a-z]*\) *[A-Z]* *[0-9]* [0-9]* *[0-9]* */\1/' | sed 's/^[^:]*:\(:\]:\)\?//;s/\([0-9]*\) *[^ ]*/\1\t/;s/,fd=[0-9]*//' | sort -gu
[::]:* users:(("unbound",pid=1864))
22 users:(("sshd",pid=1830))
25 users:(("master",pid=3150))
53 users:(("unbound",pid=1864))
323 users:(("chronyd",pid=1876))
500 users:(("charon",pid=2817))
631 users:(("cups-browsed",pid=2744))
2628 users:(("dictd",pid=2825))
4001 users:(("emacs",pid=3578))
4500 users:(("charon",pid=2817))
5353 users:(("avahi-daemon",pid=1423))
6600 users:(("systemd",pid=3461))
8384 users:(("syncthing",pid=232169))
9050 users:(("tor",pid=2857))
21027 users:(("syncthing",pid=232169))
22000 users:(("syncthing",pid=232169))
33231 users:(("syncthing",pid=232169))
34953 users:(("syncthing",pid=232169))
35770 users:(("syncthing",pid=232169))
44944 users:(("syncthing",pid=232169))
47337 users:(("syncthing",pid=232169))
48903 users:(("mosh-client",pid=234126))
52774 users:(("syncthing",pid=232169))
52938 users:(("avahi-daemon",pid=1423))
54029 users:(("avahi-daemon",pid=1423))
anarcat@angela:~$
But that doesn't filter out the localhost stuff, lots of false positive (like emacs, above). And this is where it gets... not fun, as you need to match "localhost" but we don't resolve names, so you need to do some fancy pattern matching:
ss -pluntO --no-header | \
sed 's/^\([a-z]*\) *[A-Z]* *[0-9]* [0-9]* *[0-9]* */\1/;s/^tcp//;s/^udp//' | \
grep -v -e '^\[fe80::' -e '^127.0.0.1' -e '^\[::1\]' -e '^192\.' -e '^172\.' | \
sed 's/^[^:]*:\(:\]:\)\?//;s/\([0-9]*\) *[^ ]*/\1\t/;s/,fd=[0-9]*//' |\
sort -gu
This is kind of horrible, but it works, those are the actually open ports on my machine:
anarcat@angela:~$ sudo ss -pluntO --no-header | sed 's/^\([a-
z]*\) *[A-Z]* *[0-9]* [0-9]* *[0-9]* */\1/;s/^tcp//;s/^udp//' |
grep -v -e '^\[fe80::' -e '^127.0.0.1' -e '^\[::1\]' -e '^192\.' -
e '^172\.' | sed 's/^[^:]*:\(:\]:\)\?//;s/\([0-9]*\) *[^ ]*/\
1\t/;s/,fd=[0-9]*//' | sort -gu
22 users:(("sshd",pid=1830))
500 users:(("charon",pid=2817))
631 users:(("cups-browsed",pid=2744))
4500 users:(("charon",pid=2817))
5353 users:(("avahi-daemon",pid=1423))
6600 users:(("systemd",pid=3461))
21027 users:(("syncthing",pid=232169))
22000 users:(("syncthing",pid=232169))
34953 users:(("syncthing",pid=232169))
35770 users:(("syncthing",pid=232169))
48903 users:(("mosh-client",pid=234126))
52938 users:(("avahi-daemon",pid=1423))
54029 users:(("avahi-daemon",pid=1423))
Surely there must be a better way. It turns out that lsof
can do
some of this, and it's relatively straightforward. This lists all
listening TCP sockets:
lsof -iTCP -sTCP:LISTEN +c 15 | grep -v localhost | sort
A shorter version from Adam Shand is:
lsof -i @localhost
... which basically replaces the grep -v localhost
line.
In theory, this would do the equivalent on UDP
lsof -iUDP -sUDP:^Idle
... but in reality, it looks like lsof on Linux can't figure out the state of a UDP socket:
lsof: no UDP state names available: UDP:^Idle
... which, honestly, I'm baffled by. It's strange because ss
can
figure out the state of those sockets, heck it's how -l
vs -a
works after all. So we need something else to show listening UDP
sockets.
The following actually looks pretty good after all:
ss -pluO
That will list localhost
sockets of course, so we can explicitly ask
ss
to resolve those and filter them out with something like:
ss -plurO | grep -v localhost
oh, and look here! ss
supports pattern matching, so we can actually
tell it to ignore localhost
directly, which removes that horrible
sed
line we used earlier:
ss -pluntO '! ( src = localhost )'
That actually gives a pretty readable output. One annoyance is we can't really modify the columns here, so we still need some god-awful sed hacking on top of that to get a cleaner output:
ss -nplutO '! ( src = localhost )' | \
sed 's/\(udp\|tcp\).*:\([0-9][0-9]*\)/\2\t\1\t/;s/\([0-9][0-9]*\t[udtcp]*\t\)[^u]*users:(("/\1/;s/".*//;s/.*Address:Port.*/Netid\tPort\tProcess/' | \
sort -nu
That looks horrible and is basically impossible to memorize. But it sure looks nice:
anarcat@angela:~$ sudo ss -nplutO '! ( src = localhost )' | sed 's/\(udp\|tcp\).*:\([0-9][0-9]*\)/\2\t\1\t/;s/\([0-9][0-9]*\t[udtcp]*\t\)[^u]*users:(("/\1/;s/".*//;s/.*Address:Port.*/Port\tNetid\tProcess/' | sort -nu
Port Netid Process
22 tcp sshd
500 udp charon
546 udp NetworkManager
631 udp cups-browsed
4500 udp charon
5353 udp avahi-daemon
6600 tcp systemd
21027 udp syncthing
22000 udp syncthing
34953 udp syncthing
35770 udp syncthing
48903 udp mosh-client
52938 udp avahi-daemon
54029 udp avahi-daemon
Better ideas welcome.
You can use your Mastodon account to reply to this post.