7.4 The Security Manager

 <  Day Day Up  >  

A Java environment can be subjected to four levels of attack:

  1. System modification , in which a program gets read/write access and makes some changes to the system

  2. Privacy invasion , in which a program gets read access and steals restricted information from the system

  3. Denial of service , in which a program uses up system resources without being invited

  4. Impersonation , in which a program masquerades as the real user of the system

The default Java 2 SecurityManager , in package java.lang , enforces restrictions based on security policy statements that are designed to prevent the first two types of attack and, to some extent, the last. In this section, we look at what the SecurityManager does and how it does it. Finally, we briefly consider the tricks that a program can use to perform the nuisance attacks ”denial of service and impersonation.

7.4.1 What the SecurityManager Does

In the Java 2 platform, SecurityManager is a concrete class, whose implementation supports the policy-driven security model discussed in Chapter 8 on page 253. Previously, SecurityManager was an abstract class that application developers, such as JVM and WAS manufacturers, were forced to extend to implement a set of access controls. Although the class was abstract, it did implement a set of check methods : for example, checkRead() , checkWrite() , and checkConnect() . The intent was for the application developer to override these methods with something that answered the question, "Is the applet allowed to do this?" either by quietly returning to the caller an implicit yes or by throwing a SecurityException , an emphatic no. As shipped, each method did have a default behavior in case the application did not override the method; it simply said no by throwing a SecurityException .

With the Java 2 platform, java.lang.SecurityManager is a fully functional, resource-level, access-control facility. Application developers need call only one method, checkPermission() , which takes a Permission object as a parameter and forwards the Permission checking to the checkPermission() method in class java.security.AccessController . AccessController is the class that enforces the security policy configuration imposed by the Java system administrator, which we introduced in Section 7.2.1 on page 207 (see Figure 7.14).

Figure 7.14. SecurityManager , AccessController , and the Current Security Policy

graphics/07fig14.gif

Even though the Java 2 SecurityManager offers a checkPermission() method taking a Permission object as a parameter, the other check methods are still available for backward compatibility. However, they now answer the question using the Java 2 permission model by turning the request into a Permission and calling SecurityManager.checkPermission() . All the check methods can still be overridden, if necessary.

Figure 7.15 summarizes which system resources are protected by the default SecurityManager , the methods that are invoked to check whether the rights to access the resources have been granted, and the Permission types that are passed to checkPermission() by each check method. This is also the Permission type to pass to checkPermission() when you call it directly.

Figure 7.15. Default SecurityManager Controls

graphics/07fig15.jpg

The default SecurityManager automatically grants a class file the java.io.FilePermission necessary to read to all files contained in the class's directory and all its subdirectories recursively, as illustrated in Figure 7.16. No explicit file read Permission is necessary in this case. However, this rule does not apply to class files contained in JAR files.

Figure 7.16. Automatic File Read Permission Granted to a Class File

graphics/07fig16.gif

Another Permission that is automatically granted by the default SecurityManager is the java.net.SocketPermission that allows a remote code to connect to, accept, and resolve the local host and the host the code is loaded from, including the host name of the local system, if loaded locally, as shown in Figure 7.17. No explicit SocketPermission is required in this case.

Figure 7.17. SocketPermission Automatically Granted to Remote Code

graphics/07fig17.gif

7.4.2 Operation of the SecurityManager

Although any Java program, such as a servlet, an enterprise bean, or an application, can instantiate a new SecurityManager , the JVM will allow only one SecurityManager to be active at a time. To make a SecurityManager active programmatically, you have to call the static method System.setSecurityManager() and pass it an instance of the desired SecurityManager .

In the Java 2 platform, the SecurityManager can be activated for local applications as well, even though this is not the default option, and an explicit command-line flag must be specified. [13] In other words, local applications can now, on demand, be subjected to the access-control restrictions of the SecurityManager , which enforces the security policy configuration imposed by the system administrator. This was not true prior to the Java 2 platform. The reason for this security enforcement is that today's application distribution may be performed through FTP or by shipping a CD-ROM in the mail. An application that has been obtained in one of these ways will be run locally, even though its origin is external to the local file system. It is therefore very important to be able to restrict, if necessary, the system resources that an application can have access to.

[13] In the J2SE reference implementation, you can set the system property java.security.manager as an option on the java command. The -Djava.security.manager command line option will activate the default SecurityManager , which, as we said, is the one in package java.lang . The option -Djava.security.manager= CustomSecurityManager will load and make class CustomSecurityManager the active SecurityManager . Note that CustomSecurityManager must be a SecurityManager subclass and must be reachable through one of the active class search paths (see Section 7.2.2 on page 207).

Once a SecurityManager is active, it cannot be replaced unless the program that attempts the replacement has the authority to create an instance of SecurityManager and set a SecurityManager instance as the active SecurityManager . These two rights translate into two java.lang.RuntimePermission s that the Java system administrator has to grant to the code attempting the SecurityManager replacement. The target parameters for these two RuntimePermissions are, respectively, "createSecurityManager" and "setSecurityManager" .

The reason a Permission is required to set a SecurityManager instance as active should be obvious; a SecurityManager instance could choose to authorize any operation that running programs attempt to perform. However, it is not so obvious why a Permission is required even to simply instantiate a SecurityManager , regardless of the fact that the new SecurityManager instance might never be set as the current SecurityManager of the Java system. The reason for this restriction is that any SecurityManager instance, whether active or inactive, can be used to inspect the current execution stack and obtain security-sensitive information about the current execution environment.

Once installed, a SecurityManager is active only on request; it does not check anything unless one of its check methods is called by other system functions. Figure 7.18 illustrates the flow for a specific restricted operation: establishing a network connection. The calling code creates a new java.net.Socket class, using one of the constructor methods it provides. This method invokes the checkConnect() method of the local SecurityManager subclass instance, passing a java.net.SocketPermission object as a parameter.

Figure 7.18. SecurityManager Operation

graphics/07fig18.gif

The SecurityManager has a very simple question to answer when one of its check methods is invoked: "Is this program allowed to perform the security-sensitive operation?" In Figure 7.18, this question becomes, "Can the application code connect to the specified host on the specified port?" In order to answer this question, the SecurityManager relies on the underlying AccessController class to check whether the running code has been granted the Permission entry necessary to perform the socket connection. If the connection is allowed, a Socket instance is created and made available to the application code. Otherwise , AccessController throws a SecurityException .

7.4.3 Types of Attack

Although we do not describe any attacks in detail, it is worth summarizing some of the security holes that have been discovered in previous Java releases. All the bugs reported in this section were found in vendor implementations of the JVM. If application developers and JVM implementors use the fully functional Java 2 SecurityManager as a base for their work, the number and variations of security implementations, and therefore possibilities for error, will be greatly reduced.

Flaws and the security exposures they might create are inevitable. However, the Java platform receives a great deal of attention by a wide audience. An encouraging consequence is that most of the flaws found to date were identified by field researchers attempting to find and close all holes. Fixes were provided rapidly by Sun Microsystems and application vendors . All this experience has influenced the evolution of the Java 2 security architecture.

7.4.3.1 Infiltrating Local Classes

Prior to the Java 2 platform, David Hopwood, once a student at Oxford and then a Netscape employee, discovered a vendor JVM implementation bug that allowed an applet to load a class from any directory on the browser system. This bug was quickly fixed.

Downloading code packages from the Internet has become a part of everyday life for many people. Any of those packages could have been modified to plant a Trojan horse class file along with their legitimate payload. Of course, this is not just a Java problem but more like a new form of computer virus. One solution lies in signed content, so that you know that the package you download comes from a trusted source and is not likely to have been tampered with.

Fully trusted classes are those that the JVM assumes and depends on being correct and well behaved. Java 2 fully trusted classes are limited to those on the boot class path; all other classes are subject to verification and security policy restrictions. Protecting the Java 2 trusted classes is a matter of limiting access to the directories and files on the boot class path. As part of the boot class path , those files are automatically considered fully trusted. The class-loading mechanism gives them the highest loading priority, as they are loaded by the primordial class loader and are exempted from class file verification and security policy restrictions. The underlying operating system should be configured to restrict writing access to the directories pointed to by the boot class path.

The extension framework, which we described on page 212, also offers a back door to hackers. Because extension classes are by default granted full access to the system resources, it is highly recommended that system administrators allow only trusted users to add extensions to the runtime environment. An alternative is for system administrators to change the policy configuration and reduce the set of authorizations granted to the extensions.

7.4.3.2 Type Confusion

The Java platform goes to great lengths to ensure that objects of a particular type are dealt with consistently. We see this both in the compiler and later in the third pass of the class file verifier. It is crucial that the class of an object and the level of access it allows, as specified by the private , protected , or public keywords, are preserved.

If, somehow, an attacker can create an object reference that is not of the type it claims to be, there is a possibility of breaking down the protection. Several examples have shown ways to achieve type confusion by taking advantage of various JVM implementation flaws, such as:

  • A bug that allowed a ClassLoader to be created but avoided calling the ClassLoader constructor, which normally invokes SecurityManager.checkCreateClassLoader() , as shown in Figure 7.15 on page 240

  • Flaws in JVM access checking that allowed a method or an object defined as private in one class to be accessed by another class as public

  • A JVM bug that failed to distinguish between two classes with the same name but loaded by different class loaders

7.4.3.3 Network Loopholes

The first security- related JVM flaw to get worldwide attention was a failure to check the source IP address of a remote program rigorously enough. This was exploited by abusing the DNS, a network service responsible for resolving names to addresses and vice versa, to fool the SecurityManager into allowing the remote program to connect to a host that would normally have been invisible to the server from which the program was loaded. In this way, the attacker could access a system that would normally be safe behind a firewall.

7.4.3.4 JavaScript Back Doors

A series of JavaScript exploits allowed a script to persist after the Web page it was invoked from had been exited. This flaw was used to track the user's Web accesses . The problem was fixed but then reappeared when Netscape introduced LiveConnect, which allows a JavaScript to create Java objects and invoke Java methods. Both Java and JavaScript have strict limitations on what they are allowed to do, but the limitations are different. By combining them, it was possible to get a union of the two protection schemes.

7.4.4 Malicious Code

Setting the rules for a program's environment is always a question of striking a balance. The program needs some system and/or network resources; otherwise, it will not be useful at all. On the other hand, it must not be allowed to have free reign over the system, especially if this program has been downloaded from a remote site. The need to find a compromise between allowing a program to access some system resources and restricting its access to other resources at the same time poses the conditions for some security exposures.

So far, we have talked about system modification and privacy invasion. What about denial of service and impersonation? These last two categories of exposure are allowed by the Java security framework because they cannot harm a system in a permanent way. However, they can still be annoying or damaging .

In theory, there is also another type of malice that is not Java specific. This is based on deception , that is, the attempt to trick users into entering information that they would not normally give away. This sort of thing is not specific to Java. In fact, there are much easier ways to do the same thing by using scripting languages or simple HTML forms, so we will not consider them further here.

7.4.4.1 Cycle Stealing

Denial-of-service attacks have long been a scourge of the Internet. Denial of service implies that the user can no longer use the system, because a server or even a whole site has been taken down. Cycle stealing is much more subtle: a cycle-stealing program is any program that consumes resources, whether computer or human, without the user's permission. The most extreme form of these are denial-of-service programs, but the most insidious ones may not be detected by their victim at all.

There are obvious denial-of-service attacks. For example, a program could try to create an infinite number of windows or could sit in a tight loop, using up CPU cycles. These attacks are very annoying and can have a real impact: for example, if the user has to reboot the machine to recover.

The key to this kind of program lies in persistent background threads. Every implementation of the JVM supports threads, implemented as Thread objects, and the Java language makes it very easy to use them. Normally, when a Java program stops, it will also stop any Thread s it created. However, there is nothing to assist the program in this task or to enforce that this is done. Indeed, if a Java program fails, intentionally or unintentionally, to explicitly stop the Thread s it created, they will continue to run until they end on their own or the application ”the J2EE container, for instance ”ends.

The attack described here is fairly benign . The attacker has obtained free use of machine cycles on your system. What sort of thing might he or she want to do with them?

One example might be to do brute-force cipher cracking . A feature of any good symmetric-key encryption algorithm is a uniform key space. That is, if you want to crack the code, there is no mathematical shortcut to finding the key; you simply have to try all possible keys until you find one that works. Several recent encryption challenges have been solved by using spare cycles on a large number of computers working as a loosely coupled complex, each being delegated a range of keys to try, under the direction of a central coordinator .

A number of other attacks along the same lines have been demonstrated, such as programs that kill the Thread s of other programs executing concurrently. This type of attack can be prevented by controlling which programs are allowed to run on the Java platform.

7.4.4.2 Impersonation

Internet e-mail is based on the Simple Mail Transfer Protocol (SMTP). Mail messages are passed from one SMTP gateway to another, using sessions on TCP/IP port 25. Abusing these connections to send bogus e-mail is an established nuisance of the Internet. A hacker can create mail messages that appear to come from someone else, which can be used to embarrass or annoy the receiver of the mail and the apparent sender.

Mail that has been forged in this way is not impossible to tell from the real thing, however. The SMTP gateways keep track of the original IP address, so you can trace the message back, if not to a person at least to a machine, unless the originator was also using a spoofed IP address.

A Java remote program allows this kind of errant behavior to go one stage further. In fact, a remote program is typically allowed to connect to port 25 and appear to be a mail client. However, the only system it can connect to is the one that it was originally loaded from, because of the sandbox restrictions (see Figure 7.17 on page 241). Therefore, if an attacker is able to load a program on a remote machine, the program would be allowed to connect back to the server and send e-mail to the target of the attack. When the recipient checks the IP address, it belongs to a complete stranger, who has no idea that anything has happened .

7.4.5 SecurityManager Extensions

The default Java 2 SecurityManager is a fully functional class that acts as an interface between programs running on top of the JVM and the underlying Java 2 security access-control system. The structure is very flexible, and most applications will find that the default Java 2 SecurityManager will give them all the function they need. However, sometimes an application vendor, such as a J2EE product provider, will want to extend or limit the default SecurityManager 's capabilities. Several examples follow.

  • You may want to prevent access to a system resource even if someone explicitly grants that Permission in the system policy database. This means that the SecurityManager overrides the security policy at runtime.

  • You may want to log all the requests for access to certain resources.

  • You may want to prompt users with a special password before a particular system resource can be accessed.

Because a SecurityManager is responsible for enforcing access-control restrictions, a SecurityManager extension should not be subjected to any security restrictions. In fact, a custom SecurityManager should be granted AllPermission .

7.4.5.1 Ignoring Policy

Sometimes, a JVM vendor or a J2SE/J2EE provider may wish to deny a certain Permission even when the Java system administrator has explicitly granted that Permission . The code fragment in Listing 7.7 shows how to override the checkPermission() method in the default SecurityManager to deny the Permission to print unconditionally.

Listing 7.7. Overriding SecurityManager.checkPermission()
 public void checkPermission(Permission perm) {    if (perm instanceof RuntimePermission &&       perm.getName() == "queuePrintJob")    {       System.out.println("Permission to print is denied");       throw new SecurityException();    }    super.checkPermission(perm); } 

The checkPermission() method shown in Listing 7.7 behaves exactly as its homonym in the SecurityManager superclass, except when it is invoked to check whether a RuntimePermission "queuePrintJob" has been granted to the running code. In this case, the custom SecurityManager will deny the Permission unconditionally by throwing a SecurityException .

7.4.5.2 Logging

In this section, we take an easy task and continue with our theme by implementing a simple audit log of Permission requests. Our example creates a log file during construction of the SecurityManager and overrides the checkPermission() method. Whenever a checkPermission() is received, this implementation of checkPermission() will log in file PermissionRequests.log that a check is being made to check whether a particular Permission is being checked by writing a String representation of the Permission to the file. Then, it will call the parent SecurityManager 's checkPermission() method, which will enforce the access-control restrictions as imposed by the security policy currently in effect. A log function such as the one implemented by this SecurityManager extension could be useful in a J2EE environment for auditing purposes. The code of this SecurityManager extension is shown in Listing 7.8.

Listing 7.8. LogSecurityManager.java
 import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.Permission; /**  * LogSecurityManager extends the default SecurityManager in  * package java.lang by overriding the checkPermission()  * method. The implementation of checkPermission() offered  * by LogSecurityManager logs all the checkPermission()  * invocations by registering the name of the Permission  * being checked.  */ public class LogSecurityManager extends SecurityManager {    private DataOutputStream auditlog;    /**     * Public constructor. It calls the constructor in the     * SecurityManager superclass and then initializes the log     * function.     */    public LogSecurityManager()    {       super(); // initilize using parent constructor       try       {          auditlog = new DataOutputStream(new             FileOutputStream("PermissionRequests.log"));             auditlog.writeBytes("Log Started:\n");       }       catch (IOException e)       {          System.err.println             ("PermissionRequests.log file not opened " +             "properly\n" + e.toString());       }       System.out.println("LogSecurityManager constructed");    }    /**     * This method behaves exactly as checkPermission() in     * the SecurityManager superclass, the only difference     * being that all the Permission requests are being     * logged.     *     * @param perm a java.security.Permission object     *         representing the access right being checked     *         by this LogSecurityManager.     */    public void checkPermission(Permission perm)    {       try       {          auditlog.writeBytes("Checking: " + perm.toString() +             "\n");       }       catch (IOException e)       {          System.err.println             ("Could not write to log file\n" + e.toString());       }       // Invoke checkPermission() in the superclass.       super.checkPermission(perm);    } } 
7.4.5.3 Enforcing Password-Based Protection

This section shows how to program a SecurityManager implementing password-based authentication. This SecurityManager subclass asks the user for a password whenever a simple file read or write is attempted. This SecurityManager overrides the default implementation provided by the Java 2 API in package java.lang . We also show how to combine this password-based control with the policy-based access control of the default SecurityManager . Listing 7.9 gives the code.

Listing 7.9. RWSecurityManager.java
 import java.io.BufferedReader; import java.io.IOException; import java.io.FileDescriptor; import java.io.InputStreamReader; /**  * This class implements a SecurityManager that prompts the  * user to authenticate with a password every time there is  * a file read and write attempt.  */ public class RWSecurityManager extends SecurityManager {    private String rpasswd; // private read password    private String wpasswd; // private write password    /**     * Public constructor, used to set the read and write     * passwords.     *     * @param rpwd a String representing the read password.     * @param wpwd a String representing the write password.     */    public RWSecurityManager(String rpwd, String wpwd)    {       super();       // The class using this SecurityManager will set       // both the read and write passwords       this.rpasswd = rpwd;       this.wpasswd = wpwd;    }    /**     * This method overrides checkRead() in the superclass     * by asking the user for a password every time there is     * an attempt to perform a file read operation.     * Optionally, this method can call checkRead() in the     * superclass. In this case, entering the correct     * password will not be enough, and the code attempting     * to perform the file read operation will have to have     * been granted a java.io.FilePermission.     *     * @param fileName a String representing the name of     *        the file from which the code is attempting to     *        read.     */    public void checkRead(String filename)    {       String pwdgiven;       // Ask if the user has the required password       System.out.println          ("Enter the password for reading files.");       try       {          pwdgiven = new BufferedReader             (new InputStreamReader(System.in)).                readLine();          if (pwdgiven.equals(rpasswd))             System.out.println                ("Permission to read files granted.");          else             throw new SecurityException                ("Permission to read files denied");       }       catch (IOException e)       {          throw new SecurityException             ("Permission to read files denied");       }       // Uncomment the line below if you want to call       // SecurityManager.checkRead() at this time       // super.checkRead(filename);    }    /**     * This method overrides checkWrite() in the superclass     * by asking the user for a password every time there is     * an attempt to perform a file write operation.     * Optionally, this method can call checkWrite() in the     * superclass. In this case, entering the correct     * password will not be enough, and the code attempting     * to perform the file write operation will have to have     * been granted a java.io.FilePermission.     *     * @param fileName a String representing the name of     *        the file to which the code is attempting to     *        write.     */    public void checkWrite(String filename)    {       String pwdgiven;       // Ask if the user has the required password       System.out.println          ("Enter the password for writing to files.");       try       {          pwdgiven = new BufferedReader(new             InputStreamReader(System.in)).readLine();          if (pwdgiven.equals(wpasswd))             System.out.println                ("Permission to write files granted");          else             throw new SecurityException                ("Permission to write files denied");       }       catch (IOException e)       {          throw new SecurityException             ("Permission to write files denied");      }      // Uncomment the line below if you want to call      // SecurityManager.checkWrite() at this time      // super.checkWrite(filename);    } } 

If an instance of RWSecurityManager is set as the active SecurityManager of a Java system, code attempting to read and write files will cause a SecurityException to be thrown unless the user running the program enters the correct authenticating password. However, it is not necessary to grant the code the FilePermission to read and write files. The reason is that the methods checkRead() and checkWrite() of the superclass SecurityManager are completely overwritten. If invoked, those methods would call checkPermission() in AccessController . The RWSecurityManager class bases its policy decision on a password. If the application developer wants to keep the behavior of SecurityManager , which requires specific read and write FilePermission s enabled in the active policy, RWSecurityManager has to call super.checkRead() and super.checkWrite() . The code in Listing 7.9 shows the calls to these two methods commented out. Uncommenting those lines will enable the default SecurityManager functions. At that point, the Java system administrator will need to modify the active security policy of the Java system in order to have the application work correctly.

 <  Day Day Up  >  


Enterprise Java Security. Building Secure J2EE Applications
Enterprise Javaв„ў Security: Building Secure J2EEв„ў Applications
ISBN: 0321118898
EAN: 2147483647
Year: 2004
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net