|Building secure software: Selecting technologies, Part 2|
Choosing an operating system
As far as the average program is concerned, the details of the security implementation don't matter very much. For programs running in user space, there are common security restrictions implemented in the kernel of almost all modern operating systems (in one fashion or another). One of the most important is process space protection. In a good operating system, a single process is not allowed to access any of the memory allocated to other processes directly. Additionally, no process can directly access the memory currently marked as "in use" by the operating system. All inter-process communication is in this way mediated by the operating system. Windows NT/2000 and all Unix systems afford this kind of protection. Other Windows systems, up to and including Windows ME do not offer it. The upshot of this fact is that in an operating system like the PalmOS, or Windows 95, 98 and ME, it is often possible to change data in other programs by exploiting a bug in a single program. That's possible because all programs share a single address space. From a security perspective, that's awful.
This has bigger ramifications than most people realize. For example, if you store a shared secret on an Internet-enabled Palm Pilot, any other application on that Palm Pilot has access to the secret. It can be read or changed. Essentially, if you allow an attacker to run code on such a machine through any means, the attacker can completely take over the machine.
As part of standard user-level protections in more advanced operating systems, processes can't directly access devices attached to the computer, such as any hard drives, video cards, and the like, at least without special permission. Instead, special pieces of software inside the kernel called device drivers act as wrappers to these devices. User-level programs must make calls through the kernel to these device drivers in order to access hardware. Most frequently, such calls are made indirectly, though a system call interface. For example, in Unix, devices appear to the application as files on the file system; meaning the application communicates with the device by performing file reads and writes.
The Windows 95/98/ME family of operating systems was not originally designed to afford the kinds of protection modern operating systems provide. This product line descends from the original Windows, and ultimately the original versions of DOS! Dinosaur operating systems like DOS were designed in a time when security was not a significant issue, since most personal computers were single user devices that were only rarely connected to any network. While some of the basic security functionality has since been added to this line of product, certain aspects of the operating system design make it impossible to build a security system for the operating system that is not exploitable. As a result, these add-on features end up being more of a reliability mechanism than a security mechanism. It is still possible with Windows 98 and its ilk to protect a computer against a network. But once an attacker can run code on such a machine, the attacker instantly attains complete control.
In popular operating systems, there are generally no security checks in the kernel, except at the interfaces through which the end user calls into the OS. For example, there is rarely an effort made to protect one part of the kernel from other parts of the kernel; they are all explicitly trusted. This trust is usually extended to code that may not really count as part of the operating system but still needs to run in the kernel. In fact, that's the case for device drivers, which are often shipped by the manufacturer of a hardware device. Sometimes third-party device drivers are even used. Talk about blindly extending trust!
Kernels tend not to protect against themselves. That is, the entire operating system stands and falls as a whole. Thus if a bad enough security flaw is found in any part of the operating system, anyone able to exploit that flaw can exert complete control over an entire machine from software. Building a kernel that is capable of protecting against itself is difficult, and usually has a large negative impact on performance. That is why it is done only infrequently. Nonetheless, there do exist operating systems that afford this sort of protection, one of the more notable being Trusted Mach, which has been used primarily for research purposes. A few similar Unix operating systems are available. There is no such implementation for the Windows platform currently available.
Host-based authentication is generally a quick and dirty way to raise the bar a notch, but is close to useless. If you rely on MAC addresses, processor IDs, or cookies, remember that they are essentially self-reported by an untrusted client. An attacker sophisticated enough to download and use a tool can cause the client to report a lie by modifying packets traversing the network. If you provide encryption, then an attacker can generally still attack the technique by modifying your client. This security risk can be managed by using an appropriately skeptical design (one that doesn't require a great deal of trust in the client).
IP addresses and DNS addresses might seem more reliable, and in some sense they are. Let's say that an attacker forges the IP address in packets going out from the attacking box so that the packets appear to come from an address that should be authenticated (this is called IP spoofing). In most situations, that, by itself, is not good enough. There are several reasons why:
First, the attacker needs to make sure that the fake packets will actually be routed to the target. There are tools to automate this task.
Second, even if packets make it to the target, responses will be routed to the forged IP address. In order for the forgery to be useful (in the most common sorts of attack), the attacker needs to receive those responses. The only real way to accomplish that is for the attacker to become interposed somewhere on the route between the spoofed machine and the target. Usually, what happens in practice is that the attacker will break onto a machine that is on the same network segment as one of the two machines in question (usually the network of the target). Once you're on the same network segment, you're almost always able to see all traffic addressed to the machine of interest. (The major exception is being on a switched network segment.)
Third, spoofing attacks are difficult to execute. You don't often see script kiddies executing them. Vendor FUD (fear, uncertainty, and doubt spread to sell product) aside, there are really not that many attack tools that present a complete one-stop, no-brains-required spoofing solution. As a result, any application of IP spoofing requires a significant amount of technical depth.
So why even worry about IP spoofing? The problem is that it's not extraordinarily difficult for a skilled attacker to spoof IPs if the attacker can break on to your local network. While IP-related authentication certainly raises the bar high enough to keep out all except those of significant skill, it's not something you should ever consider likely to be perfect.
DNS authentication can be defeated with IP spoofing. There are additional ways to defeat DNS authentication. One is to send a fake response to the DNS lookup. Similarly, one can tamper with the actual response from a legitimate query. These kinds of attacks require the same level of skill as IP spoofing. Another kind of attack on DNS systems is a "cache poisoning" attack, where the malicious hacker relies on flaws in some implementations of DNS to "hijack" domain names. Such an attack can point valid domain names at attacker addresses. In some cases, mistakes by system administrators can carry out this attack accidentally (as was the case with Microsoft in 2000). While not all implementations of DNS are subject to this kind of attack, many real-world sites are completely susceptible. Plus, this attack is far easier to launch than the more sophisticated spoofing attacks. For this reason, we do not recommend ever using DNS names for security, especially since using IP numbers is a far more reliable (though not perfect) approach.
In the context of computer systems, one problem with physical tokens is that some sort of input device is necessary for every client to the system. If you have an application where you want any person on the Net with his or her own computer to be able to use your system, this requirement is problematic. Most people don't own a smart card reader. (Even most owners of American Express Blue cards haven't figured out how to install the ones they were sent for free.) In the case of credit cards, letting the user type in the credit card number nominally solves the problem. However, this solution suffers in that it doesn't really guarantee that the person typing in the credit card number is actually in possession of the card. The same risk that applies in the physical world with regard to use of credit cards over the phone applies to systems relying on credit card numbers for authentication on the Net.
Another problem with physical tokens is that they can be lost or stolen. In both cases, this can be a major inconvenience to the valid user. Moreover, many physical tokens can be duplicated easily. Credit cards and keys are good examples of things that are easily cloned. The equipment necessary to clone a magnetic stripe card is cheap and readily available. Hardware tamper proofing is possible and tends to work quite well for preventing duplication (though it is not infallible), but on the downside it is also quite expensive to place in practice.
Sometimes an attacker doesn't need to steal the physical token in order to duplicate it. A skilled locksmith can duplicate many types of keys just by looking at the original. Molding the lock is another option. Credit card information can be written down when you give your card to a waiter in a restaurant or a clerk at the video store.
Even if you're careful with your card, and only use it in ATMs, there's still the possibility for attack. Some hilarious but true cases exist where attackers go to the trouble of setting up a fake ATM machine in a public place. The ATM is programmed to appear to be broken once people put their card in the machine. However, the machine really does its nefarious job, copying all the relevant information needed to duplicate cards that get inserted. Other attackers have added hardware to valid ATMs to apply the same attacks with real success.
Examples of behavioral biometrics include handwritten signatures and voice prints. In the real world, we validate signatures by sight, even though a skilled attacker can reliably forge a signature that most people cannot distinguish from the original. If a biometric system were to capture the entire act of signing (pen speed, pressure, etc.) in some digital format, then it would be far more difficult to forge a real signature; high-quality forgeries usually take a fair bit of time.
Biometric authentication is a convenient technology, because people can't really forget their authentication information like a password or lose it like a physical token. Most people's eyes can't be removed and stored elsewhere. The necessary information is always with you. Nonetheless, there are plenty of problems with biometric authentication.
Much like authentication with physical tokens, biometric authentication has the limitation that you need to have access to a physical input device in order to be able to authenticate. Therefore, it's not appropriate for many types of applications.
Another problem that biometrics vendors often overlook is the security of the input mechanism. If an attacker can tamper with the authentication hardware, it might be possible to inject falsified digital data directly into the device. One might capture such information by observing the data generated for valid users of the system as it whizzes by on a wire. If it's possible to tamper with the authentication device, such data capture should not be all that difficult. And once your biometric pattern has been compromised, it's not possible to make a new one. You only get so many eyes! It's usually a good idea to have the security of important biometric input devices supplemented by having in-the-flesh guards, thus greatly reducing the risk of physical tampering.
Behavioral biometrics can be fickle. The best example is using a voiceprint for authentication. What happens if someone is sick? If the system isn't lenient enough to handle this situation, people may be wrongly denied authentication if they have a stuffy nose. However, if the system will accept a similar voice that sounds "sick," then it's more likely to fall prey to attackers who can reasonably mimic the voice of valid users of the system.
Another problem is that biometric identifiers are generally unique, but are not secret. If a system authenticates based solely on fingerprints, an attacker could reasonably construct a fake hand after carefully gathering fingerprints of an authorized user of the system. This kind of an attack just requires following someone into a bar and getting their beer glass. Additionally, note that such systems encourage people to steal parts of your body, which is never a good thing. To help thwart these kinds of attacks, better biometric systems will factor in "liveness" measures like temperature and blood flow to try to gain assurance that the fingerprints are from a live human being actually touching the device.
Nonetheless, if the physical security of the authentication device is an issue, what happens if a digital representation of someone's fingerprint is stolen? Yes, we can invalidate that fingerprint. But the user only has 10 fingers. What happens when an attacker steals all 10 fingerprints? In a password system, if your password gets compromised, you can just change it. You're not likely to be able to change your fingerprints quite as readily.
Another issue to consider is that a significant number of people believe that the collection of biometric information is an invasion of privacy. DNA might be the ultimate authentication mechanism for a human (at least one without an identical twin), but DNA encodes enough information for an insurance company to deny issuing insurance to you on the grounds of a disease you have not yet contracted, because your genetics show you to be susceptible to the disease.
Defense in depth and authentication
Defense in depth can also be used to solve the problem of protecting cryptographic authentication information (usually called a key) from people who break into your machine. The key can be encrypted, using a password (hopefully not weak) as the encryption key. Every time cryptographic authentication is to be used, a password must be given in order to decode the key. That way, even if someone steals the bits, they would still need the password.
This solution still poses a problem when using cryptographic keys in a server environment (host-to-host authentication instead of user-to-host authentication). That's because servers need to be able to use keys in an automated fashion, without user intervention. One option is to not encrypt the key. If the key is encrypted, you can save the password somewhere on the disk, and read it in when necessary. However, you're just moving the problem to another part of the disk if you do that. A third option is to require manual intervention once, at program startup, then keep the decrypted key only in memory, not on the disk (on the theory that it's usually a lot more difficult for an attacker to snag if it only exists in memory). This solution means that a server machine cannot reboot unattended. Someone needs to be around to feed passwords to any software needing encryption keys.