The next security problem is the Princeton class-loader attack. This was the most widely publicized of all Java security breaches. The problem was caused by mistakes in the way the Java system integrated separate pieces of code. By corrupting this integration or linking process, an attacker could break through Java's security and do anything at all. To help better understand this issue, the following section looks more closely at how Java manages the dynamic-linking process.
A Java program is composed of several separate pieces called classes. Each class is stored in a separate file, and the Java system uses a just-in-time strategy to load each class only when it is first needed. Just-in-time loading allows Java applets to start running quickly, without waiting for the entire applet to be pulled across the Net. It does have one drawback, however: A running applet is usually incomplete. When an applet is built from several code pieces, the system has to be clever enough to make sure that the right pieces are attached in the right places.
A Java class file contains a series of instructions telling the Java system how the class should behave. The instructions sometimes reference other classes by name. Since classes are stored separately, the Java system translates each name into the identity of another class. This may involve loading the mentioned class across the Net.
The core Java system does not do this translation itself, but outsources it to Java objects called Class Loaders. Outsourcing in this way allows programmers to create their own class loaders, extending Java's linking mechanism.
The interaction between a Class Loader and the core elements of Java is simple. When Java needs to determine which class corresponds to which name, the following steps are followed:
There are usually several class loaders in operation. When Java needs to translate a name, it asks the Class Loader that originally loaded the class referencing the name. Thus, each Class Loader is responsible for maintaining and defining its own part of the namespace.
Because Java has separate namespaces into which classes can be loaded, it can't simply have a unified "phone directory" tracking which class corresponds to which classname. Instead, the Java Virtual Machine maintains a separate directory for each class. These independent directories keep track of the names needed by each class.
For example, if class A has a reference to a class called B, the directory for A will have an entry for B that points to the actual class the name represents. Figure 5.5 shows a more complicated example with four classes referencing each other. A big applet could consist of more than four classes, but the idea is the same: The applet is a set of classes that reference each other.
The example described in Figure 5.5 shows reasonable, self-consistent namespaces. The Princeton team discovered that a hostile class loader was capable of setting up a twisted namespace in which different classes had different views of the Java environment. Such inconsistencies can be exploited to create type confusion. A hostile class loader could launch a system-penetration attack.
Figure 5.6 shows an example of what an evil class loader can do. The figure shows two classes, A and B, each of which refers to a classname "C". However, the two classes have different ideas of what the name "C" means. Class A points to the class we've labeled C1, while B points to C2.
Suppose that the Java code in class A allocates an object of type "C" and then passes that object to class B. The Java byte code Verifier thinks everything is okay, since an object whose class was named "C" is being passed into code that is expecting an object whose classname is "C". The Verifier allows the operation to proceed. But when class B accesses the object named "C", the true type will be C1, not the C2 that the Verifier approved. An object of type C1 is being treated as though it were of class C2. This is type confusion.
When this attack is carried out, the evil class loader is asked twice to say which class corresponds to the name "C". It gives back different answers: C1 for class A, and C2 for class B.
The class-loader attack should have been impossible. Java's security rules prohibit applets from creating class loaders. Unfortunately, the Princeton team discovered a flaw in the byte code Verifier that allowed this rule to be violated. Nothing stops an applet from declaring a new class that is a subclass of the ClassLoader superclass. It is up to the Security Manager to stop the actual construction from occurring. In this case, the Security Manager check is bypassed because of a bug. Read on for the gory details.
The rule against making class loaders is enforced by the object-oriented nature of Java. Every Java class extends its superclass. Each class can be thought of as being a specialized version of its superclass.3 Every class has one or more constructor functions, which properly initialize new objects. Java requires each constructor to call the constructor of its superclass, or another constructor of the same class, before it does anything else. For example, if you create a class called MyHashtable that extends the built-in class java.util.Hashtable, then you have to provide a constructor for MyHashtable. That constructor must call the constructor of java.util.Hashtable before it does anything else. The byte code Verifier ensures that these rules are followed.
To prevent applets from making class loaders, the constructor for the class ClassLoader consults the Security Manager, which generates a Security Exception if the class loader being constructed would belong to an applet. This Security Exception can abort the creation of such an object. If an applet defines a new EvilClassLoader class to extend the basic ClassLoader, then the new constructor is required to call Java's basic ClassLoader constructor. Doing so generates a Security Exception that prevents the applet from creating an EvilClassLoader.
What the Princeton team discovered was a trick by which a constructor could avoid calling its superclass constructor, without being caught by the Verifier. This allowed them to create an EvilClassLoader whose constructor did not call the basic ClassLoader constructor, and thus was not subject to the normal Security Manager check. The EvilClassLoader could then create type confusion.
Having created type confusion, the attacker could then exploit it to achieve full system intrusion; that is, the attacker could do anything at all on the victim's machine.
Sun Microsystems and Netscape had two options for fixing this problem. They could prevent the superclass-constructor-avoidance by fixing the Verifier, or they could find another way of forcing the basic ClassLoader constructor to be called. They chose to do the latter. They added an initialized data field to every class loader, and set the field to true only when the basic ClassLoader constructor was run. The basic ClassLoader would refuse to perform the crucial defineClass action unless the initialized field was true.
The implementation created a new private ClassLoader method called defineClass0. This does the real work of defineClass. Redefining defineClass to check the initialized flag and call defineClass0 only if the flag was true helps to block this particular security hole. The change does not prevent an attacker from making a class loader, but it does prevent an attacker from using the new class loader once it has been made.
The change took effect in Netscape Navigator 2.02. Unfortunately, future attacks managed to circumvent this fix.
This flaw received more press coverage than any of the others. It had more news interest than the DNS bug because it was more serious. Later bugs did not receive as much coverage because by the time they came to light, the novelty of bug discovery had worn off. That does not mean that the current and future security problems are not just as serious. Whether or not security problems are splashed on the front pages, they still need to be taken seriously.
Perhaps the press coverage partly reflected a backlash against the extremely positive hype surrounding most press stories about Java at the time. Java is great, but many of the exaggerated claims went much too far. There was even a story stating that if you wrote programs in Java you would never have to debug them because they would always be right the first time. To be fair, only a little of the hype came from Sun. Much of it came from freelance consultants, self-proclaimed experts, and trainers who had an interest in seeing their Java bandwagon become a juggernaut.
When the Applets Running Wild flaw was discovered, Sun Microsystems, Netscape, and the flaw's discoverers gained some valuable experience discussing these issues with each other and with the press. As a result, the parties did a better job of conveying simple and consistent information to the public. Hopefully, this will remain true when future security holes come to light.
Copyright ©1999 Gary McGraw and Edward Felten.