14.3. Securing FreeBSD

This section describes methods for securing a FreeBSD system against the attacks that were mentioned in the previous section.

14.3.1. Securing the root Account

Most systems have a password assigned to the root account. Assume that this password is always at risk of being compromised. This does not mean that the password should be disabled as the password is almost always necessary for console access to the machine. However, it should not be possible to use this password outside of the console or possibly even with su(1). For example, setting the entries in /etc/ttys to insecure prevents root logins to the specified terminals. In FreeBSD, root logins using ssh(1) are disabled by default as PermitRootLogin is set to no in /etc/ssh/sshd_config. Consider every access method as services such as FTP often fall through the cracks. Direct root logins should only be allowed via the system console.

Since a sysadmin needs access to root, additional password verification should be configured. One method is to add appropriate user accounts to wheel in /etc/group. Members of wheel are allowed to su(1) to root. Only those users who actually need to have root access should be placed in wheel. When using Kerberos for authentication, create a .k5login in the home directory of root to allow ksu(1) to be used without having to place anyone in wheel.

To lock an account completely, use pw(8):

# pw lock staff

This will prevent the specified user from logging in using any mechanism, including ssh(1).

Another method of blocking access to accounts would be to replace the encrypted password with a single * character. This character would never match the encrypted password and thus blocks user access. For example, the entry for the following account:

foobar:R9DT/Fa1/LV9U:1000:1000::0:0:Foo Bar:/home/foobar:/usr/local/bin/tcsh

could be changed to this using vipw(8):

foobar:*:1000:1000::0:0:Foo Bar:/home/foobar:/usr/local/bin/tcsh

This prevents foobar from logging in using conventional methods. This method for access restriction is flawed on sites using Kerberos or in situations where the user has set up keys with ssh(1).

These security mechanisms assume that users are logging in from a more restrictive server to a less restrictive server. For example, if the server is running network services, the workstation should not be running any. In order for a workstation to be reasonably secure, run zero or as few services as possible and run a password-protected screensaver. Of course, given physical access to any system, an attacker can break any sort of security. Fortunately, many break-ins occur remotely, over a network, from people who do not have physical access to the system.

Using Kerberos provides the ability to disable or change the password for a user in one place, and have it immediately affect all the machines on which the user has an account. If an account is compromised, the ability to instantly change the associated password on all machines should not be underrated. Additional restrictions can be imposed with Kerberos: a Kerberos ticket can be configured to timeout and the Kerberos system can require that the user choose a new password after a configurable period of time.

14.3.2. Securing Root-run Servers and SUID/SGID Binaries

The prudent sysadmin only enables required services and is aware that third party servers are often the most bug-prone. Never run a server that has not been checked out carefully. Think twice before running any service as root as many daemons can be run as a separate service account or can be started in a sandbox. Do not activate insecure services such as telnetd(8) or rlogind(8).

Another potential security hole is SUID-root and SGID binaries. Most of these binaries, such as rlogin(1), reside in /bin, /sbin, /usr/bin, or /usr/sbin. While nothing is 100% safe, the system-default SUID and SGID binaries can be considered reasonably safe. It is recommended to restrict SUID binaries to a special group that only staff can access, and to delete any unused SUID binaries. SGID binaries can be almost as dangerous. If an intruder can break an SGID-kmem binary, the intruder might be able to read /dev/kmem and thus read the encrypted password file, potentially compromising user accounts. Alternatively, an intruder who breaks group kmem can monitor keystrokes sent through ptys, including ptys used by users who login through secure methods. An intruder that breaks the tty group can write to almost any user's tty. If a user is running a terminal program or emulator with a keyboard-simulation feature, the intruder can potentially generate a data stream that causes the user's terminal to echo a command, which is then run as that user.

14.3.3. Securing User Accounts

User accounts are usually the most difficult to secure. Be vigilant in the monitoring of user accounts. Use of ssh(1) and Kerberos for user accounts requires extra administration and technical support, but provides a good solution compared to an encrypted password file.

14.3.4. Securing the Password File

The only sure fire way is to star out as many passwords as possible and use ssh(1) or Kerberos for access to those accounts. Even though the encrypted password file (/etc/spwd.db) can only be read by root, it may be possible for an intruder to obtain read access to that file even if the attacker cannot obtain root-write access.

Security scripts should be used to check for and report changes to the password file as described in the Checking file integrity section.

14.3.5. Securing the Kernel Core, Raw Devices, and Filesystems

Most modern kernels have a packet sniffing device driver built in. Under FreeBSD it is called bpf. This device is needed for DHCP, but can be removed in the custom kernel configuration file of systems that do not provide or use DHCP.

Even if bpf is disabled, /dev/mem and /dev/kmem are still problematic. An intruder can still write to raw disk devices. An enterprising intruder can use kldload(8) to install his own bpf, or another sniffing device, on a running kernel. To avoid these problems, run the kernel at a higher security level, at least security level 1.

The security level of the kernel can be set in a variety of ways. The simplest way of raising the security level of a running kernel is to set kern.securelevel:

# sysctl kern.securelevel=1

By default, the FreeBSD kernel boots with a security level of -1. This is called insecure mode because immutable file flags may be turned off and all devices may be read from or written to. The security level will remain at -1 unless it is altered, either by the administrator or by init(8), because of a setting in the startup scripts. The security level may be raised during system startup by setting kern_securelevel_enable to YES in /etc/rc.conf, and the value of kern_securelevel to the desired security level.

Once the security level is set to 1 or a higher value, the append-only and immutable files are honored, they cannot be turned off, and access to raw devices is denied. Higher levels restrict even more operations. For a full description of the effect of various security levels, refer to security(7) and init(8).

Note:

Bumping the security level to 1 or higher may cause a few problems to Xorg, as access to /dev/io will be blocked, or to the installation of FreeBSD built from source as installworld needs to temporarily reset the append-only and immutable flags of some files. In the case of Xorg, it may be possible to work around this by starting xdm(1) early in the boot process, when the security level is still low enough. Workarounds may not be possible for all secure levels or for all the potential restrictions they enforce. A bit of forward planning is a good idea. Understanding the restrictions imposed by each security level is important as they severely diminish the ease of system use. It will also make choosing a default setting much simpler and prevent any surprises.

If the kernel's security level is raised to 1 or a higher value, it may be useful to set the schg flag on critical startup binaries, directories, script files, and everything that gets run up to the point where the security level is set. A less strict compromise is to run the system at a higher security level but skip setting the schg flag. Another possibility is to mount / and /usr read-only. It should be noted that being too draconian about what is permitted may prevent detection of an intrusion.

14.3.6. Checking File Integrity

One can only protect the core system configuration and control files so much before the convenience factor rears its ugly head. For example, using chflags(1) to set the schg bit on most of the files in / and /usr is probably counterproductive, because while it may protect the files, it also closes an intrusion detection window. Security measures are useless or, worse, present a false sense of security, if potential intrusions cannot be detected. Half the job of security is to slow down, not stop, an attacker, in order to catch him in the act.

The best way to detect an intrusion is to look for modified, missing, or unexpected files. The best way to look for modified files is from another, often centralized, limited-access system. Writing security scripts on the extra-security limited-access system makes them mostly invisible to potential attackers. In order to take maximum advantage, the limited-access box needs significant access to the other machines, usually either through a read-only NFS export or by setting up ssh(1) key-pairs. Except for its network traffic, NFS is the least visible method, allowing the administrator to monitor the filesystems on each client box virtually undetected. If a limited-access server is connected to the client boxes through a switch, the NFS method is often the better choice. If a limited-access server is connected to the client boxes through several layers of routing, the NFS method may be too insecure and ssh(1) may be the better choice.

Once a limited-access box has been given at least read access to the client systems it is supposed to monitor, create the monitoring scripts. Given an NFS mount, write scripts out of simple system utilities such as find(1) and md5(1). It is best to physically md5(1) the client system's files at least once a day, and to test control files such as those found in /etc and /usr/local/etc even more often. When mismatches are found, relative to the base md5 information the limited-access machine knows is valid, it should alert the sysadmin. A good security script will also check for inappropriate SUID binaries and for new or deleted files on system partitions such as / and /usr.

When using ssh(1) rather than NFS, writing the security script is more difficult. For example, scp(1) is needed to send the scripts to the client box in order to run them. The ssh(1) client on the client box may already be compromised. Using ssh(1) may be necessary when running over insecure links, but it is harder to deal with.

A good security script will also check for changes to hidden configuration files, such as .rhosts and .ssh/authorized_keys, as these files might fall outside the purview of the MD5 check.

For a large amount of user disk space, it may take too long to run through every file on those partitions. In this case, consider setting mount flags to disallow SUID binaries by using nosuid with mount(8). Scan these partitions at least once a week, since the objective is to detect a break-in attempt, whether or not the attempt succeeds.

Process accounting (see accton(8)) is a relatively low-overhead feature of FreeBSD which might help as a post-break-in evaluation mechanism. It is especially useful in tracking down how an intruder broke into a system, assuming the file is still intact after the break-in has occurred.

Finally, security scripts should process the log files, and the logs themselves should be generated in as secure a manner as possible and sent to a remote syslog server. An intruder will try to cover his tracks, and log files are critical to the sysadmin trying to track down the time and method of the initial break-in. One way to keep a permanent record of the log files is to run the system console to a serial port and collect the information to a secure machine monitoring the consoles.

14.3.7. Paranoia

A little paranoia never hurts. As a rule, a sysadmin can add any number of security features which do not affect convenience and can add security features that do affect convenience with some added thought. More importantly, a security administrator should mix it up a bit. If recommendations, such as those mentioned in this section, are applied verbatim, those methodologies are given to the prospective attacker who also has access to this document.

14.3.8. Denial of Service Attacks

A DoS attack is typically a packet attack. While there is not much one can do about spoofed packet attacks that saturate a network, one can generally limit the damage by ensuring that the attack cannot take down servers by:

  1. Limiting server forks.

  2. Limiting springboard attacks such as ICMP response attacks and ping broadcasts.

  3. Overloading the kernel route cache.

A common DoS attack scenario is to force a forking server to spawn so many child processes that the host system eventually runs out of memory and file descriptors, and then grinds to a halt. There are several options to inetd(8) to limit this sort of attack. It should be noted that while it is possible to prevent a machine from going down, it is not generally possible to prevent a service from being disrupted by the attack. Read inetd(8) carefully and pay specific attention to -c, -C, and -R. Spoofed IP attacks will circumvent -C to inetd(8), so typically a combination of options must be used. Some standalone servers have self-fork-limitation parameters.

Sendmail provides -OMaxDaemonChildren, which tends to work better than trying to use Sendmail's load limiting options due to the load lag. Specify a MaxDaemonChildren when starting Sendmail which is high enough to handle the expected load, but not so high that the computer cannot handle that number of Sendmail instances. It is prudent to run Sendmail in queued mode using -ODeliveryMode=queued and to run the daemon (sendmail -bd) separate from the queue-runs (sendmail -q15m). For real-time delivery, run the queue at a much lower interval, such as -q1m, but be sure to specify a reasonable MaxDaemonChildren to prevent cascade failures.

syslogd(8) can be attacked directly and it is strongly recommended to use -s whenever possible, and -a otherwise.

Be careful with connect-back services such as reverse-identd, which can be attacked directly. The reverse-ident feature of TCP Wrappers is not recommended for this reason.

It is recommended to protect internal services from external access by firewalling them at the border routers. This is to prevent saturation attacks from outside the LAN, not so much to protect internal services from network-based root compromise. Always configure an exclusive firewall which denies everything by default except for traffic which is explicitly allowed. The range of port numbers used for dynamic binding in FreeBSD is controlled by several net.inet.ip.portrange sysctl(8) variables.

Another common DoS attack, called a springboard attack, causes the server to generate responses which overloads the server, the local network, or some other machine. The most common attack of this nature is the ICMP ping broadcast attack. The attacker spoofs ping packets sent to the LAN's broadcast address with the source IP address set to the machine to attack. If the border routers are not configured to drop ping packets sent to broadcast addresses, the LAN generates sufficient responses to the spoofed source address to saturate the victim, especially when the attack is against several dozen broadcast addresses over several dozen different networks at once. A second common springboard attack constructs packets that generate ICMP error responses which can saturate a server's incoming network and cause the server to saturate its outgoing network with ICMP responses. This type of attack can crash the server by running it out of memory, especially if the server cannot drain the ICMP responses it generates fast enough. Use the sysctl(8) variable net.inet.icmp.icmplim to limit these attacks. The last major class of springboard attacks is related to certain internal inetd(8) services such as the UDP echo service. An attacker spoofs a UDP packet with a source address of server A's echo port and a destination address of server B's echo port, where server A and B on the same LAN. The two servers bounce this one packet back and forth between each other. The attacker can overload both servers and the LAN by injecting a few packets in this manner. Similar problems exist with the chargen port. These inetd-internal test services should remain disabled.

Spoofed packet attacks may be used to overload the kernel route cache. Refer to the net.inet.ip.rtexpire, rtminexpire, and rtmaxcache sysctl(8) parameters. A spoofed packet attack that uses a random source IP will cause the kernel to generate a temporary cached route in the route table, viewable with netstat -rna | fgrep W3. These routes typically timeout in 1600 seconds or so. If the kernel detects that the cached route table has gotten too big, it will dynamically reduce the rtexpire but will never decrease it to less than rtminexpire. This creates two problems:

  1. The kernel does not react quickly enough when a lightly loaded server is suddenly attacked.

  2. The rtminexpire is not low enough for the kernel to survive a sustained attack.

If the servers are connected to the Internet via a T3 or better, it may be prudent to manually override both rtexpire and rtminexpire via sysctl(8). Never set either parameter to zero as this could crash the machine. Setting both parameters to 2 seconds should be sufficient to protect the route table from attack.

14.3.9. Access Issues with Kerberos and ssh(1)

There are a few issues with both Kerberos and ssh(1) that need to be addressed if they are used. Kerberos is an excellent authentication protocol, but there are bugs in the kerberized versions of telnet(1) and rlogin(1) that make them unsuitable for dealing with binary streams. By default, Kerberos does not encrypt a session unless -x is used whereas ssh(1) encrypts everything.

While ssh(1) works well, it forwards encryption keys by default. This introduces a security risk to a user who uses ssh(1) to access an insecure machine from a secure workstation. The keys themselves are not exposed, but ssh(1) installs a forwarding port for the duration of the login. If an attacker has broken root on the insecure machine, he can utilize that port to gain access to any other machine that those keys unlock.

It is recommended that ssh(1) is used in combination with Kerberos whenever possible for staff logins and ssh(1) can be compiled with Kerberos support. This reduces reliance on potentially exposed SSH keys while protecting passwords via Kerberos. Keys should only be used for automated tasks from secure machines as this is something that Kerberos is unsuited to. It is recommended to either turn off key-forwarding in the SSH configuration, or to make use of from=IP/DOMAIN in authorized_keys to make the key only usable to entities logging in from specific machines.

All FreeBSD documents are available for download at http://ftp.FreeBSD.org/pub/FreeBSD/doc/

Questions that are not answered by the documentation may be sent to <freebsd-questions@FreeBSD.org>.
Send questions about this document to <freebsd-doc@FreeBSD.org>.