IBM®
Skip to main content
    Country/region [select]      Terms of use
 
 
      
     Home      Products      Services & solutions      Support & downloads      My account     

developerWorks > Security >
developerWorks
Building secure software: Selecting technologies, Part 1
73KBe-mail it!
Contents:
Choosing a language
Choosing a distributed object platform
CORBA
DCOM
EJB and RMI
Resources
About the authors
Rate this article
Related content:
Building secure software: Selecting technologies, Part 2
Software security principles: Part 1
Software security principles: Part 2
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Choosing a programming language and a distributed object platform

John Viega (viega@securesw.com), Chief Technology Officer, Software Solutions
Gary McGraw (gem@cigital.com), Vice president, Reliable Software Technologies

01 Feb 2002

Before you select the security technologies that meet your needs, you've got to do your homework, according to Gary McGraw and John Viega, authors of Building Secure Software: How to Avoid Security Problems the Right Way (Addison-Wesley, 2001; reprinted by permission). This article and the one that follows are based on chapter 3, "Selecting technologies," which explores common choices faced by designers and programmers. Here in Part 1, the authors examine effective ways of choosing a programming language and a distributed object platform.

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 article 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 in the lifecycle, 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 article, we attempt to 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.

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, as most estimates we have seen indicate that Java programs will run at about 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 (though 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 drawbacks, a classic tradeoff situation.

One of the biggest mistakes companies make in choosing a language is the failure to consider impact on software security. Certainly, security should be properly weighed against many other concerns. However, many people either choose to ignore security completely, or seem to assume that all languages are created equal when it comes to security. Unfortunately, that 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 on the availability of the program in question easily. 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 due to 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, then the program will usually halt or otherwise fail. Java does a fairly reasonable job of forcing programmers to catch errors that might 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 properly recover 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 will 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 programs written in that language will tend to be. 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, where 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 many data are written into a buffer, overwriting adjacent memory that may be security critical (buffer overflows are discussed in chapter 7 of Building Secure Software [Viega, 2001]). Writing outside the bounds of a buffer in most other languages will result in an exceptional condition. Another broad category of problems that manifest themselves in some, but not all, languages involve input checking mistakes. For example, in some situations, some languages directly invoke a Unix command shell to run commands (think CGI); 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 of Building Secure Software [Viega, 2001].

On the positive side, some languages provide security features that might 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 API-level coder can remain oblivious to these constructs . . . but 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 will be automatically used in certain circumstances. The most notable case is when a Java Virtual Machine 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 might lead to a security violation. While this system doesn't catch every possible bug, it still works quite well in practice. We discuss taint mode in chapter 12 of Building Secure Software [Viega, 2001].

While 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, that is usually a bad idea. Protection specifiers are generally checked only at compile time. Anyone who can compile and link in code can generally circumvent such mechanisms with ease.

One exception to this technique for enforcing protection is when running Java applets. Usually, protection modifiers will 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 will effectively be changed to protected access1. This problem is largely due to the fact that inner classes were added to Java without supporting the notion of an inner class in the Java Virtual Machine. Java compilers largely perform "hacks" that affect access specifiers to circumvent the lack of VM 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.

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 never swaps 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 will 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 might 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 towards 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 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 EJB (Enterprise Java Beans) 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." This is meant 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'll give a high level overview of the security services provided by each of the major technologies in this area, CORBA, DCOM, RMI and EJB.

CORBA
CORBA implementations may come with a Security Service based on the specifications of the Object Management Group's 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 might be accomplished through use of a GUI, but with another, it might 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 so that 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 as to what the receiving object can do with those privileges. Consider an object A that calls B, where B then calls an object C. A has several choices as to how B may reuse those credentials:

  1. A could choose not to extend its credentials to B at all.
  2. A may pass its credentials to B, and allow B to do anything with them, including passing them to C.
  3. A can force composite delegation, where if B wants to use A's credentials, it must also pass its own.
  4. A can force combined delegation, where if B wants to use A's credentials, it must create a new credential that combines its own with A.
  5. 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 (i.e., access to the entire system) that might not be enough, since 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 [McGraw, 1999].

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. By contrast to COM's Windows-centric slant, CORBA is a product of the Unix-centric world.2

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 into 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 will be 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 as to 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, that 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. Since 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, since 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 so 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, since 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
Enterprise Java Beans (EJB) are Java's version of a distributed object platform. EJB client/server systems make use of Java's Remote Method Invocation (RMI) implementations for communication.

While 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 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 will gladly download one from the server. Since the network is insecure, a malicious attacker could act as the server, substituting a bogus security layer, and then masquerade as the client to the server.

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.

Next time: In Part 2 of this series, we will explore the pitfalls of choosing an operating system, as well as the security challenges of authentication systems.

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.

2 Of course, there are many CORBA implementations for the Windows world, and no DCOM implementations for the Unix world and other OS's (like OS/390, RTOS, etc). This is a classic condition.

Resources

  • Of course, you can find a wide range of security articles in the developerWorks Security topic.

  • The XML Security Suite, available on alphaWorks, provides security features such as digital signature, encryption, and access control for XML documents.

  • IBM security services can help you determine what your risks are, and then design a security program to address them.

About the authors
John Viega is the CTO of Secure Software Solutions. He's co-author of Building Secure Software (Addison-Wesley, 2001) and Network Security and Cryptography with OpenSSL (O'Reilly, 2002). John has authored more than 50 technical publications, primarily in the area of software security. He is also known for security tools, and for writing Mailman, the GNU Mailing List Manager. You can contact him at viega@securesw.com.


Gary McGraw, Ph.D., is the Chief Technology Officer at Cigital. Dr. McGraw is a noted authority on software security and has co-authored four popular books: Java Security: Hostile Applets, Holes, & Antidotes (Wiley, 1996) and Securing Java: Getting Down to Business with Mobile Code (Wiley, 1999) with Prof. Ed Felten of Princeton; Software Fault Injection: Inoculating Programs Against Errors (Wiley, 1998) with Cigital co-founder and Chief Scientist Dr. Jeffrey Voas; and Building Secure Software (Addison-Wesley, 2001) with John Viega. Dr. McGraw regularly contributes to popular trade publications and is often quoted in national press articles. You can contact him at gem@cigital.com.



73KBe-mail it!
Rate this article

This content was helpful to me:

Strongly disagree (1)Disagree (2)Neutral (3)Agree (4)Strongly agree (5)

Comments?



developerWorks > Security >
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact