3. Selecting Technologies

“First I’ll instruct thee in the rudiments,
And then will thou be perfecter than I.”

—CHRISTOPHER MARLOWE
DOCTOR FAUSTUS

In Chapter 2 we argued that one of the main principles of software risk management is making the right technology tradeoffs by being directly informed by the business proposition. This is particularly essential when it comes to choosing software security technologies.

This chapter is about comparing and contrasting technologies, and coming up with those that best meet derived requirements. Obviously, this is something that must usually be done early during the life cycle, most often during the course of specifying and designing a system.

Designers and programmers conscientiously select technologies, but only the rare few consider all the possible tradeoffs when selecting a technology. Nowhere is this problem more apparent than in security. Of course, the process of selecting technologies can be boiled down easily: Do your homework, and try to stay objective.

Nonetheless, thorough research on a technology is difficult to do. In this chapter we save you from some of this background work by discussing a number of the most common choices that technologists and security practitioners must make, and how those choices impact security. If any particular topic does not interest you, feel free to skip it.

Choosing a Language

The single most important technology choice most software projects face is which programming language (or set of languages) to use for implementation. There are a large number of factors that impact this choice. For example, efficiency is often a requirement, leading many projects to choose C or C++ as an implementation language. Other times, representational power takes precedence, leading to use of languages like LISP, ML, or Scheme.

It is decidedly common to place a large amount of weight on efficiency, using that as a sole justification for language choice. People who do this usually end up choosing C or C++. The choice of C often takes place with little or no consideration as to whether a project could be implemented in another language and still meet its efficiency requirements. The “choose-C-for-efficiency” problem may be an indicator that software risk management is not being properly carried out.

Even worse than the choose-C-for-efficiency problem is the choice of a language based largely on familiarity and comfort. Making such choices by gut feel indicates an immature software risk management process. Not enough people stop to consider the relative tradeoffs and benefits of using other languages. For example, developers with only C++ experience can likely transition to Java with only a moderate hit for ramping up on a new language. In this particular case, the ultimate efficiency of the product is not likely to be as good, because most estimates we have seen indicate that Java programs run at approximately half the speed of an equivalent C program, which is often fast enough (consider that languages like Python and Perl are much slower). However, Java ameliorates many (but certainly by no means all) security risks that are present in C, and also has portability advantages (although we’ve seen plenty of examples in practice where Java isn’t nearly as portable as claimed). Beyond that, Java’s lack of pointers and its inclusion of garbage collection facilities are likely to reduce costs in the long run by leading to a more efficient testing regimen. So there are benefits and draw-backs—a classic tradeoff situation.

One of the biggest mistakes companies make in choosing a language is the failure to consider the impact on software security. Certainly, security should be properly weighed against many other concerns. However, many people either choose to ignore it completely or seem to assume that all languages are created equal when it comes to security. Unfortunately, this is not the case.

As an example, consider that software reliability can have a significant impact on security when it comes to denial-of-service attacks. If a network service is prone to crashing, then it is likely that attackers will be able to launch attacks easily on the availability of the program in question. If the network service in question is written in C, it probably involves taking on too big a risk in the reliability and security area, because C programs tend to be unreliable (mostly because of unrestricted pointers and a lack of reasonable error-handling facilities). A number of interesting research projects have tested the reliability of C programs by sending random inputs to those programs. One of the best is the FUZZ program [Miller, 1990]. Experience with this tool reported in the research literature shows that a surprisingly large number of low-level programs have demonstrated abysmal reliability rates when tested in this strikingly simple manner. One problem is that few programs bother to allow arbitrarily long inputs. Those that don’t often fail to check the length of inputs. And even those that do rarely check for garbage input.

There are languages other than C that can expose you to denial-of-service problems. For example, in languages with exception handling (like Java), programmers usually fail to catch every possible exceptional condition. When an exception propagates to the top of the program, the program usually halts or otherwise fails. Java does a fairly reasonable job of forcing programmers to catch errors that may possibly be thrown, and therefore is a good language from this perspective. However, some common types of exceptions such as NullPointerExceptions do not need to be caught (if that were necessary, Java programs would be much harder to write). Also, programmers often leave empty error handlers, or otherwise fail to recover properly from an error. Interpreted languages are particularly subject to such problems. Programs in these languages may even contain syntax errors in code not yet tested by the developers. Such errors lead to the program terminating at runtime when the untested code is run for the first time.

The more error checking a language can do statically, the more reliable the programs written in that language. For that reason, Java is a superior choice to C and C++ when it comes to reliability. Java has a distinct advantage because it has a much stronger static type system. Similarly, Java offers advantages over dynamic languages in which type errors and other mistakes only become apparent during runtime.

Reliability-related denial of service isn’t the only security concern that manifests itself in programming languages. C and C++ are notorious for buffer overflow attacks—a problem that exists in no other mainstream language (well, FORTRAN is still mainstream in some application domains). Buffer overflow attacks occur when too much data are written into a buffer, overwriting adjacent memory that may be security critical (buffer overflows are discussed in Chapter 7). Writing outside the bounds of a buffer in most other languages results in an exceptional condition. Another broad category of problems that manifests itself in some languages but not all involves input checking mistakes. For example, in some situations, some languages directly invoke a UNIX command shell to run commands (think CGI [Common Gateway Interface]). Malicious inputs might thus be able to trick a program into sending a malicious command to the shell. We discuss such problems in Chapter 12.

On the positive side, some languages provide security features that may be useful to your project. The most well-known example to date is the Java programming language, which offers several advanced security features. Unfortunately, most of the impressive Java security features were positioned solely as ways to handle untrusted mobile code (even though they are really much more versatile). We’ve found that most of the applications using Java today do not make use of these features, except perhaps “under the hood” in a distributed computing environment. Today, the application programming interface (API)-level coder can remain oblivious to these constructs. However, as technology continues to evolve and distributed systems become more commonplace, this will likely change.

The security features of a Java program are for the most part managed by an entity called the security manager. The goal of a security manager is to enforce a security policy, usually by moderating access to resources such as the file system. This approach has come to be called sandboxing. By default, a null security manager is used in most programs. Usually, an application can install a security manager with arbitrary policies. However, some default security managers come built in with Java, and are automatically used in certain circumstances. The most notable case is when a Java Virtual Machine (JVM) is running an untrusted applet in a Web browser. As a result of enforcement of security policy, such an applet is severely limited in the capabilities afforded to it. For example, applets cannot normally make arbitrary network connections or see much of the local file system. For more on mobile code security in Java, see Securing Java [McGraw, 1999].

Perl is another major language with a significant security feature. Perl may be run in “taint mode,” which dynamically monitors variables to see if untrusted user input leads to a security violation. Although this system doesn’t catch every possible bug, it still works quite well in practice. We discuss taint mode in Chapter 12.

Although high-level languages generally offer protection against common classes of problems—most notably buffer overflows—they can introduce new risks. For example, most object-oriented languages offer “information-hiding” mechanisms to control access to various data members. Programmers often assume that these mechanisms can be leveraged for use in security. Unfortunately, this is usually a bad idea. Protection specifiers are generally checked only at compile time. Anyone who can compile and link in code can usually circumvent such mechanisms with ease.

One exception to this technique for enforcing protection is when running Java applets. Usually, protection modifiers get checked at runtime. However, there are still problems with the mechanism. For example, when an inner (nested) class uses a private variable from an outer class, that variable is effectively changed to protected access.1 This problem is largely the result of the fact that inner classes were added to Java without supporting the notion of an inner class in the JVM. Java compilers largely perform “hacks” that affect access specifiers to circumvent the lack of JVM support. A better solution to this problem is known, but has yet to be integrated into a widely distributed version of Java [Bhowmik, 1999]. Nonetheless, developers should try not to count on information-hiding mechanisms to provide security.

1. The behavior may vary between compilers, and is usually a bit more complex. Instead of actually changing access specifiers, accessor methods are added to the class to give direct access to the variable. If the inner class only reads a variable from the inner class, then only a read method is added. Any added methods can be called from any code within the same package.

There are other protections high-level languages may not afford. For example, local attackers may sometimes be able to get valuable information by reading sensitive data out of memory. Programmers should make sure valuable data are never swapped to disk, and should erase such data as quickly as possible. In C, these things aren’t that difficult to do. The call mlock() can prevent a section of memory from swapping. Similarly, sensitive memory can be directly overwritten. Most high-level programming languages have no calls that prevent particular data objects from swapping. Also, many high-level data structures are immutable, meaning that programmers cannot explicitly copy over the memory. The best a programmer can do is to make sure the memory is no longer used, and hope the programming language reallocates the memory, causing it to be written over. The string type in most languages (including Java, Perl, and Python) is immutable. Additionally, advanced memory managers may copy data into new memory locations, leaving the old location visible even if you do erase the variable.

We can imagine some people reading this section and thinking we have a bias toward Java, especially considering that one of the authors (McGraw) wrote a book on Java security. When we program, we do our best to select what we see as the best tool for the job. If you look at development projects in which we’re involved, we don’t use Java much at all. We’ve done our share of it, certainly, but we are actually quite prone to use C, C++, or Python on projects.

Choosing a Distributed Object Platform

These days, client/server applications are being constructed with software systems based on distributed objects, such as CORBA (Common Object Request Broker Architecture) and RMI (Java Remote Method Invocation). These technologies provide for remote availability of resources, redundancy, and parallelism with much less effort than old-fashioned raw socket programming. Many companies are making use of full-fledged application servers, including Enterprise JavaBeans (EJB) implementations that provide multiple high-level services like persistence management and automatic database connection pooling. For the sake of convenient nomenclature, we’re lumping all of these technologies together under the term container, which means to invoke imagery of component-based software and sets of interacting but distinct distributed components.

Each of these technologies has its relative benefits and drawbacks. For example, CORBA has an advantage over RMI in that it can easily integrate disparate code written in multiple programming languages. However, RMI benefits from being a relatively simple technology.

When it comes to security, each technology has different characteristics that should be considered when making a container choice. In this section we give a high-level overview of the security services provided by each of the major technologies in this area: CORBA, Distributed Component Object Model (DCOM), RMI, and EJB.

CORBA

CORBA implementations may come with a security service based on the specifications of the Object Management Group’s (OMG) standards. These standards define two levels of service in this context: Level 1 is intended for applications that may need to be secure, but where the code itself need not be aware of security issues. In such a case, all security operations should be handled by the underlying object request broker (ORB). Level 2 supports other advanced security features, and the application is likely to be aware of these.

Most of CORBA’s security features are built into the underlying network protocol: the Internet Inter-Orb Protocol (IIOP). The most significant feature of IIOP is that it allows for secure communications using cryptography. How this functionality manifests itself to the application developer is dependent on the ORB in use. For example, a developer may choose to turn on encryption and select particular algorithms. With one ORB, this may be accomplished through use of a graphic user interface (GUI), but with another it may be done through a configuration file.

CORBA automatically provides authentication services as another primary security service, which can be made transparent to the application. Servers are capable of authenticating clients, so that they may determine which security credentials (an identity coupled with permissions) to extend to particular clients. The end user can also authenticate the server, if so desired.

Access to particular operations (methods on an object) can be restricted in CORBA. Thus, restricted methods cannot be called except by an object with sufficient security credentials. This access control mechanism can be used to keep arbitrary users from accessing an administrative interface to a CORBA server. Without such a mechanism, it is difficult to secure administrative functionality. When administrative interface functionality can be used to run arbitrary commands on a remote machine, the consequences can be grave, even though finding and exploiting a problem is difficult and happens with low frequency. Note that this sort of access control is not often used in practice, even when ORBs implement the OMG security service.

CORBA also has a wide array of options for choosing how to manage privileges in a distributed system. It may be possible for one object in a system to delegate its credentials to another object. CORBA allows flexibility regarding what the receiving object can do with those privileges. Consider an object A that calls B, where B then calls object C. Object A has several choices regarding how object B may reuse those credentials:

1. Object A could choose not to extend its credentials to object B at all.

2. Object A may pass its credentials to object B, and allow object B to do anything with them, including passing them to object C.

3. Object A can force composite delegation, where if object B wants to use object A’s credentials, it must also pass its own.

4. Object A can force combined delegation, where if object B wants to use object A’s credentials, it must create a new credential that combines its own with object A’s.

5. Object A can force traced delegation, in which all clients are required to pass all credentials to anyone they call. Then, when a security decision is to be made, the entire set of credentials is examined. Even if a credential has WORLD privileges (in other words, access to the entire system) this may not be enough, because the checking object may require that every object represented in the trace have WORLD privileges as well. This notion is similar to stack inspection in Java, which is most often used when running untrusted applet code in a browser.

There are plenty of variances between CORBA implementations that anyone choosing CORBA should consider carefully. For example, many implementations of CORBA do not contain a security service at all. Others may only implement part of the specification. There are also proprietary extensions. For example, the CORBA standard currently does not specify how to support tunneling connections through a firewall. However, several vendors provide support for this feature. (The OMG is working on standardizing the tunneling feature for future versions of the specification, but there will always remain ORBs that do not support it.)

DCOM

DCOM is Microsoft’s Distributed Component Object Model technology, a competitor to CORBA that works exclusively with Microsoft platforms. In contrast to COM’s Windows-centric slant, CORBA is a product of the UNIX-centric world.2

2. Of course, there are many CORBA implementations for the Windows world, and no DCOM implementations for the UNIX world and other operating systems (like OS/390, RTOS, and so on). This is a classic condition.

From the point of view of security, the DCOM specification provides similar functionality to CORBA even though it looks completely different. Authentication, data integrity, and secrecy are all wrapped up in a single property called the authentication level. Authentication levels only apply to server objects, and each object can have its own level set. Higher levels provide additional security, but at a greater cost.

Usually, a DCOM user chooses the authentication level on a per-application basis. The user may also set a default authentication level for a server, which is applied to all applications on the machine for which specific authentication levels are not specified.

The DCOM authentication levels are as follows:

Level 1. No authentication. Allows the application in question to interact with any machine without any requirements regarding the identity associated with the remote object.

Level 2. Connect authentication. Specifies that the client will only be authenticated on connection. The authentication method depends on the underlying authentication service being used. Unless running Windows 2000, the NT LAN Manager protocol is the only authentication protocol available. Unfortunately, this protocol is extremely weak, and should be avoided if at all possible. Windows 2000 allows Kerberos as an option (which is much better than LAN Manager), but only between Windows 2000 machines. Unfortunately, subsequent packets are not authenticated, so hijacking attacks are possible.

Level 3. Default authentication. This level depends on the underlying security architecture. Because Windows currently only supports a single security architecture for the time being, default authentication is exactly the same as connect authentication.

Level 4. Call-level authentication. Individually authenticates every method call in an object. This type of security is marginally better than connect authentication because it tries to prevent forgery of remote calls. Note that the authentication of each call does not necessarily require the remote user to input a password over and over every time a remote method call is made, because the remote user’s machine can cache the password. The extra authentication is primarily intended to prevent hijacking attacks. However, data can still be manipulated by an attacker. First, calls are generally broken into multiple packets. Only one packet carries authentication information; the other packets can be completely replaced. Second, even the authenticated packet can be modified as long as the authentication information is not changed.

Level 5. Packet-level authentication. Authenticates each packet separately. This fixes the first problem of call-level authentication, but packets can still be modified.

Level 6. Packet integrity-level authentication. Improves on packet-level authentication by adding a checksum to each packet to avoid tampering. Hostile entities can still read data traversing the network, however.

Level 7. Packet privacy-level authentication. Fully encrypts all data, preventing attacks as long as the underlying encryption is sufficiently strong.

These levels tend to build off each other. As a result, higher levels can inherit many of the weaknesses of lower levels. For example, level 7 authentication turns out to be hardly better than level 2 on most machines because the LAN Manager-based authentication is so poor and level 7 doesn’t use anything more powerful!

Just as CORBA provides delegation, DCOM provides facilities for limiting the ability of server objects to act on behalf of the client. In DCOM, this is called impersonation. There are multiple levels of impersonation. The default is the identity level, in which a remote machine can get identity information about the client, but cannot act in place of the client. The impersonate level allows the server to act in place of the client when accessing objects on the server. It does not allow the remote server to mimic the client to third parties, nor does it allow the server to give third parties authority to act on behalf of the client. The DCOM specification defines two other impersonation levels. One is the anonymous level, which forbids the server from getting authentication information about the client; the other is the delegate level, which allows the server to give third parties authority to act on your behalf, with no restrictions. As of this writing, neither of these levels is available to the DCOM developer.

EJB and RMI

EJB is Java’s version of a distributed object platform. EJB client/server systems make use of Java’s RMI implementations for communication.

Although the EJB specification only provides for access control, most implementations usually provide encryption facilities that are configurable from the server environment. You need to make sure they’re turned on in your system; they may not be by default. On the other hand, authentication is very often left to the developer.

The goals of the EJB access control system are to move access control decisions into the domain of the person assembling the application from various components. Under this scheme, instead of a component developer writing security policies that are hard coded into the software, someone not associated with development can specify the policy. This kind of strategy may make it more likely that a system administrator or someone more likely to have “security” in their job description is doing the work (at least in the optimistic case). Programmatic access to the security model is available as well. The access control mechanism is a simple principal mechanism that allows for access control in the tradition of CORBA. However, it is not capable of delegation, or anything as complex.

A critical security issue to note is that EJB implementations are built on top of RMI, and may inherit any problems associated with RMI implementations. RMI has a poor reputation for security. For a long time, RMI had no security whatsoever. These days, you can get encrypted sockets with RMI. There are also implementations for RMI that work over IIOP (see this book’s Web site for applicable links). However, there are still significant security problems with this technology [Balfanz, 2000]. Usually, RMI is configured to allow clients to download required code automatically from the server when it isn’t present. This feature is generally an all-or-nothing toggle. Unfortunately, this negotiation is possible before a secure connection has been established. If the client doesn’t have the right security implementation, it gladly downloads one from the server. Because the network is insecure, a malicious attacker could act as the server, substituting a bogus security layer, and may then masquerade as the client to the server (a “man-in-the-middle” attack; see Appendix A for more on this attack).

The short and long of it is that we currently don’t recommend RMI-based solutions, including EJB for high-security systems, unless you turn off dynamic downloading of all stub classes. Of course, if you can get some assurance that this problem has been addressed in a particular version, that version would probably be worth using.

Choosing an Operating System

Modern operating systems are logically divided into a system kernel and user-level code (often called user space). Programs run in user space, but occasionally call down into the kernel when special services are needed. Many critical services are run in kernel space. The kernel usually has some sort of security model that manages access to devices, files, processes, and objects. The underlying mechanism and the interface to that mechanism tend to be significantly different, depending on the 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. In this way, all interprocess communication is 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/ME, it is often possible to change data in other programs by exploiting a bug in a single program. This is possible because all programs share a single address space. From a security perspective, this is 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 to access hardware. Most frequently, such calls are made indirectly, through 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 because most PCs were single-user devices that were only rarely connected to a network. Although 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. However, 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 operating system. 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 the 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.

Authentication Technologies

Authentication problems are probably the most pervasive class of security problems if we ignore software bugs. Meaning, choosing a reasonable authentication technology is important. Part of the reason is that even a well-designed password-based system is usually easy to break because users almost always pick bad passwords. We talk about the challenges of designing a reasonable password-based system in Chapter 13. However, a password-based authentication approach is unfortunately not the only kind of authentication that is frequently weak. There are many diverse types of authentication mechanism, and each is difficult to get right.

Host-Based Authentication

A common way to authenticate network connections is to use the Internet Protocol (IP) address attached to the connection. This technique is popular with firewall products, for example. Sometimes, people will instead authenticate against a set of DNS (Domain Name Service) names, and thus will do a lookup on the IP address before authenticating. Both of these techniques are fairly easy to implement because the information on how to do so is readily available. Similar authentication techniques use the MAC (medium access control) address of the remote host’s network card, or any sort of unique identification (ID) associated with that machine (such as a Pentium III processor ID, if available). You can also place identifiers on a client the first time they connect, and then have these data sent on subsequent connections. Such identifiers are often referred to as cookies or tickets.

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 may 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, this by itself is not good enough. There are several reasons why. First, the attacker needs to make sure that the fake packets are actually routed to the target. There are tools to automate this task.

Second, even if packets make it to the target, responses are 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 these responses. The only real way to accomplish this 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 breaks into a machine that is on the same network segment as one of the two machines in question (usually the network of the target). Once the attacker is on the same network segment, he or she is almost always able to see all traffic addressed to the machine of interest.

Third, spoofing attacks are difficult to execute. However, that is rapidly changing. Just a couple of years ago, any application of IP spoofing required a significant amount of technical depth. Now, there are tools that largely automate individual spoofing attacks, such as DNS spoofing and even TELNET spoofing. Still, “no-brains-required” solutions are extremely rare. For the time being, spoofing attacks still require at least a bit 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 IP addresses if the attacker can break into your local network. Although 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, in which 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). Although 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 because using IP numbers is a far more reliable (although not perfect) approach.

Physical Tokens

One common technique for authentication is to use physical tokens, such as a key, a credit card, or a smart card. Without the physical token, the argument goes, authentication should not be possible. This sort of authentication is widespread, but has a number of associated problems.

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 for which you want any person on the Internet 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 Internet.

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 tamperproofing is possible and tends to work quite well for preventing duplication (although it is not infallible), but on the downside it is also quite expensive to place into practice.

Sometimes an attacker doesn’t need to steal the physical token 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 automatic teller machines (ATMs), there’s still the possibility of attack. Some hilarious but true cases exist in which attackers went 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.

Biometric Authentication

Biometric authentication is measuring physical or behavioral characteristics of a human and using these characteristics as a metric for authentication. There are a number of different types of biometric authentication that are used in real-world systems. Physical characteristics that can be measured include fingerprints, features of the eye, and facial features.

Examples of behavioral biometrics include handwritten signatures and voiceprints. 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, and so on) 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 as they can a password, or lose it as they can 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 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 may be possible to inject falsified digital data directly into the device. One may 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’ve only got two 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 accepts 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 his or her 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 ten fingers. What happens when an attacker steals all ten 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 may 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.

Cryptographic Authentication

Cryptographic authentication uses mathematics and a digital secret to authenticate users. This type of authentication can be seen as a digital analog to having a physical token. Although physical access to an input device is no longer a problem, the same sorts of issues we raised earlier apply. Most important, cryptographic authentication information can be stolen. Unfortunately, it’s often quite easy to steal digital data. Because it is so important to software security, we discuss cryptographic authentication in Chapter 11.

Defense in Depth and Authentication

We believe that the only viable strategy for authenticating users is to apply the defense-in-depth principle (which we discuss in depth in Chapter 5), mixing a number of authentication techniques. In terms of ATM withdrawals, credit card companies have done very well by mixing physical tokens with a password-based scheme. Just relying on a physical token would make it easy for wild adolescents to deplete the coffers of their parents. Similarly, relying only on the simple four-digit personal identification number (PIN), but no physical token, would be absolutely horrible. Attackers would be free to try to break into your account at their leisure, and would be able to succeed fairly quickly, especially if they have some help. (There are only 10,000 possible PINs, which is not that large a number in the grand scheme of things.) However, when these two technologies are combined, they provide a fairly high bar to jump. The attacker needs to steal your card and try an expected 5,000 PINs. Hopefully, a victim reports the card missing before the attacker gets that far.

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 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). This is because servers need to be able to use keys in an automated fashion, without user intervention. One option is not to 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 start-up, then keep the decrypted key only in memory, not on the disk (based on the theory that it’s usually a lot more difficult for an attacker to snag if it exists only 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.

Conclusion

In this chapter we emphasized the importance of comparing and contrasting technologies and coming up with those that best meet system security requirements. We did this by frankly discussing some of the risks, pitfalls, and design techniques that surround common technology decisions. The key to using this material well is to remain cognizant of security when security-critical technology choices are being made. Using the best available cryptography on the weakest platform on the planet won’t buy you much security! We discussed a number of the most common choices that technologists and security practitioners must make, and how they impact security.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.19.217