Securing Small Networks with OpenBSD, Part 4

First of all, I'd like to thank you for all of the mail I've been receiving from you recently. Your questions and comments are a great source of inspiration! I also hope that the praises are deserved. As promised in the last installment of "Securing Small Networks with OpenBSD," today we'll have a closer look at logging packets with pf.

Packet logging is a good network administration practice, because it lets us spot problems with communication and early signs of break-in attempts. Of course, logging packets alone won't help; we need to learn how to analyze and manage log files generated by pf. But first things first.

Enabling Packet Logging in pf

Packet logging is enabled when the log keyword is included in a pf rule. Therefore, all we have to do is edit /etc/pf.conf and put the log keyword after either the in or out keywords. For example

# block all incoming packets
block in on $ExtIF all

becomes

# block and log all incoming packets
block in log on $ExtIF all

Next, save changes made to /etc/pf.conf and update pf rules with

# /sbin/pfctl -R /etc/pf.conf

The packets caught by the modified rule are now sent to the pflog0 interface, where they're picked up by the pflogd daemon, which stores them in /var/log/pflog.

Because pf log files are binary files unfit for viewing with human eyes, we need a tool that translates them into plain text. That tool is the venerable tcpdump utility, one of the essential UNIX network monitoring programs. We can use it to watch pf logs in real time with

# /usr/sbin/tcpdump -i pflog0

(Typing the full access path to the binary you want to run, as I do above, is a little paranoid, but it is not a bad thing to be paranoid, when it comes to security.)

You can ignore the warning (tcpdump prints it, because the pflog interface does not have an IP address, which it doesn't need) and wait for packets to fall into your carefully planted honey pot. It is quite likely that you will not see any packets at all, for a long time. What went wrong? Nothing, actually. You are lucky and your firewall is not a target of a port scan or a cracking attempt. A series of repeated attempts to connect to unavailable ports is a sign that somebody might be trying to "probe" your machine for open ports. They are not necessarily trying to break in (they may be network researchers, or even your ISP), but you should be alert and if such attempts occur frequently, you might need to investigate them more closely. However, before you raise an alarm and call the authorities, make sure that these attempted connections are not responses to your own connection attempts!

It's a good feeling to know that all is quiet on the blocked ports, but I bet you'd like to see some action, if only to check that everything is working right. All right, open /etc/pf.conf again and add the log keyword to the pass out rules for the external interface. We can change

# allow TCP IPv4 connections to the outside world, keep state
pass out on $ExtIF inet proto tcp all flags S/SA modulate state
pass out on $ExtIF inet proto { udp, icmp } all keep state

to

# allow and log TCP IPv4 connections to the outside world, keep state
pass out log on $ExtIF inet proto tcp all flags S/SA modulate state
pass out log on $ExtIF inet proto { udp, icmp } all keep state

Save the changes and replace the old pf ruleset as described above. Then run tcpdump again, and point your Web browser to any external page. Now you should see a stream of packets similar to the one shown below:

What you see above is a group of packets sent from the network located behind a firewall to the O'Reilly Network servers, in a successful attempt to retrieve the previous installment of this series. The packets sent in response to that request are missing from the above list, because we did not ask pf to log them.

If you want to make sense of the data printed by tcpdump, it's a good idea to start with "Tools of the Trade," written by Carl Constantine. For your information, foo.foo.bar is the name of an imaginary host connected to the Internet, and dns.barab.fof is the name of an imaginary DNS server located outside of the network from which the lookup request was sent. The xxx.xxx.xxx.xxx and yyy.yyy.yyy.yyy strings are IP addresses of the oreillynet.com servers. In your case, all of these will have real names and numbers and be quite likely totally different from another reader's results.

If you are on a small network and only you are generating the traffic, the rate at which information will scroll before your eyes should not make it difficult to follow the flow of datagrams. But if you have more users actively surfing the Internet, then you will almost certainly not be able to catch up with the traffic. Fortunately, there are ways to manage that flood of packets.

Which Packets Do You Want to Capture?

Why do some packets get written to /var/log/pflog and some do not? It all boils down to the way pf works.

Packets are written to /var/log/pflog, when the last pf rule they match includes the log keyword. Packets that are not caught by rules with the log keyword won't be logged. Therefore, if you want to capture all traffic, you need to add the log keyword to all in and out rules in your /etc/pf.conf, save for the lo0 loopback device. On the flip side, if you want to limit logging to a smaller group of packets, add the log keyword only to those rules that catch the packets you want to log. For example, to log all inbound packets sent to your networks from the outside, add the log keyword to all pass in and block in rules for the external interface. Therefore, the rules for the tun0 (ExtIF) interface, described in the second installment of this series, would now look like this:

block in log quick on $ExtIF from $NoGoIPs to any
block out quick on $ExtIF from any to $NoGoIPs

# block and log all incoming packets

block in log on $ExtIF all

pass in log on $ExtIF inet proto tcp from any to $rdrIPs port $rdrPorts keep state
pass in log on $ExtIF inet proto udp from any to $rdrIPs port $rdrPorts keep state

# block all outgoing packets

block out on $ExtIF all

# allow TCP IPv4 connections to the outside world, keep state

pass out on $ExtIF inet proto tcp all flags S/SA modulate state
pass out on $ExtIF inet proto { udp, icmp } all keep state

Two special cases we have not discussed yet are the keep state and modulate state rules. We have a choice of using either of the log or log-all keywords here, depending on which packets we want to log. The log keyword will only log those packets that make state, while log-all will log all packets. Therefore, to capture all packets matching state rules, you'd need to change:

pass in log on $ExtIF inet proto tcp from any to $rdrIPs port $rdrPorts keep state
pass in log on $ExtIF inet proto udp from any to $rdrIPs port $rdrPorts keep state

to

pass in log-all on $ExtIF inet proto tcp from any to $rdrIPs port $rdrPorts keep state
pass in log-all on $ExtIF inet proto udp from any to $rdrIPs port $rdrPorts keep state

To log all traffic on ExtIF, we'd need to add the log keyword to the out rules.

Securing Small Networks with OpenBSD, Part 1 -- Small networks are often more vulnerable than large ones because they lack the money to implement good security. Artymiak Jacek explains how to secure a small network on a tight budget.

Securing Small Networks With OpenBSD, Part 2 -- OpenBSD switched from using IPFilter as its default firewall to PF, or Packet Filter, as the new default. Jacek Artymiak explains how to make a smooth transition from ipf to pf.

But if we are really concerned about security, shouldn't we be logging all packets arriving and leaving on all interfaces on the firewall? Ideally, yes, because that is the only way to ensure that you know what goes on over the boundary between your network and the rest of the world. But in such cases, you need to construct an efficient system for automated log analysis and management. Log files grow fast and take up a lot of storage space; it is essential that you gather only as much data as you can analyze.

OK, suppose that you decide to log all traffic on all interfaces. The first thing you need to do is turn global logging on by adding the log or log-all keywords (where appropriate) to every rule for every external interface on the firewall (ne0, ne1, tun0, or whatever the names of the interfaces that you are telling pf to monitor are). Load those new rules into pf, as described earlier in this article, and check that the packets are being logged into /var/log/pflog with:

# /usr/sbin/tcpdump -r /var/log/pflog

Rather than being a real time display, this command updates in short intervals. If all goes well, you are now monitoring all traffic passing, and attempting to pass, through the firewall. There is a lot of data to munch through, and if you are to manage it, you need to learn how to use tcpdump. The man page for tcpdump (man tcpdump) provides plenty of information, so instead of repeating it all, I'll only point you in the right direction.

You can decide which packets are displayed by tcpdump using one of many options and expressions:

You've go a lot of reading to do this time, so I'll leave you now to learn a few things, and will return soon to tackle the problem of managing pf logs and intrusion detection.

Until next time.

Jacek Artymiak
started his adventure with computers in 1986 with Sinclair ZX Spectrum. He's been using various commercial and Open Source Unix systems since 1991. Today, Jacek runs devGuide.net, writes and teaches about Open Source software and security, and tries to make things happen.