ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Discovering a Java Application's Security Requirements

by Mark Petrovic
01/03/2007

Application security is not easy - we know that. But we also know there are steps we can take to mitigate the risk of security failures. If we are network engineers, we invest in knowledge of network partitioning and packet filters. If we program in C, we protect against buffer overflow. If we program in Java, we consider running our application under the protection of a security manager. In each case, we use knowledge of best practices to give ourselves an advantage over inadvertent system failure.

The security provisions for Java applications are well-documented, and form a superset of the discussion in this article. Our discussion focuses on aspects of Java security managers, a topic which is a small subset of the Java security architecture.

A security manager is a class that is or extends java.lang.SecurityManager, and which checks certain application actions for permissibility during runtime. Once under the control of a security manager, the application can only perform actions that are specifically allowed by an associated security policy. By default that policy is specified in a plaintext policy file. The actions in question include writing files to specific directories, writing system properties, and making network connections to specific hosts, to name only a few. It takes no more than a simple JVM command line option to force a Java application to run under a security manager, and the policy file is easily created with any text editor.

While editing such a security policy file and adding the various rules of interest is not difficult, getting the policy right can be more challenging. And although no one can get that policy right for us, we can use tools to help us understand what that policy should be. Developing and using such a tool is what we are about to undertake. And once we have the broad, fine-grained policy it discovers, we can use it as a starting point in developing a production runtime policy or simply study it to better understand and appreciate our application's security needs.

The core code of this article is a custom security manager whose use requires Sun's JSE 5 JVM. The JSE 5 requirement derives from the security manager's dependence on the Java Reflection API to acquire private member data from a particular Java system class. Use of the Reflection API is required because of a lack of public access to certain private member data which the manager needs to function. The consequence of using reflection of private members is that the manager becomes strongly bound to the JVM internals on which it runs. This is not a serious consequence, however, as the manager is a development tool, not a production component. Once the manager has suggested a policy starting point, we can take that policy and run our application subject to it on any modern JVM.

The Default Java Security Manager

The vast majority of Java code we see written, discussed, and invoked in the contemporary literature represents the application that does not run subject to a security manager. Such applications therefore have full access to all machine resources, including disk, network, and application shutdown. Such broad access is easily restricted, however. An application can be forced to run under the default Java security manager simply by setting the -Djava.security.manager option on the JVM command line.

Consider this simple application, designed to read and print the user's home directory

public class PrintHome {
   public static void main(String[] argv) {
      System.out.println(System.getProperty("user.home"));
   }
}

Compile the code, and run it subject to the default security manager

$ cd $HOME/Projects/CustomSecurityManager

$ javac PrintHome.java

$ java -Djava.security.manager PrintHome
Exception in thread "main" java.security.AccessControlException: 
access denied (java.util.PropertyPermission user.home read)
...
at PrintHome.main(PrintHome.java:5)

whereupon the application fails to acquire and print the user.home property, and where we have omitted most of the stacktrace for readability. The application fails to execute because the default security manager running with the default security policy forbids accessing the property user.home. This privilege must be granted explicitly in a runtime policy file.

Creating a policy file policy.txt containing the single rule

grant codeBase "file:/home/cid/Projects/CustomSecurityManager/"{
   permission java.util.PropertyPermission "user.home", "read";
};

and rerunning the application with a reference to that policy file solves the problem of read access to user.home:

$ java -Djava.security.policy=policy.txt -Djava.security.manager PrintHome 
/home/cid

Note that we refer the JVM to the policy file by setting the system property java.security.policy=policy.txt. We also assume the class PrintHome resides in the directory /home/cid/Projects/CustomSecurityManager. The rule in the policy.txt file permits any code contained in a file in that directory to read system property user.home. As a result, the rule allows PrintHome to run as intended. The file in which code is contained is termed a codebase. A codebase is therefore a class or jar file.

A Security Policy Profiler: ProfilingSecurityManager

As we mentioned earlier, creating a security policy file by hand is not difficult, even though utilities like policytool are available to assist. And there are syntax shortcuts allowed in the policy file that are quite powerful, allowing the creation of very expressive, efficient rules. Using such advanced rules notation, we can specify, for example, codebase URLs that refer recursively to entire directory trees. While such recursive URL specification is quite useful and convenient, it can also mask to the human reader the true, fine-grained depth of the resource needs of the application. It is precisely this detail that we seek.

So our goal is two-fold: Firstly we are interested in running our application subject to a security policy, or at the very least we want to determine the security needs of that application. Secondly, we want a programmatic method of determining those needs.

With those goals in mind, we introduce the custom security manager secmgr.ProfilingSecurityManager. This class extends java.lang.SecurityManager, but does not enforce a security policy in the sense discussed thus far. Instead, it reports what that security policy would be if the application were granted access to everything it requests at runtime. We can then take that reportage and cast it into a starting point for a runtime security policy. Thus, both our goals are satisfied.

To use ProfilingSecurityManager, we first compile and strategically place it by itself in its own jar file (the source code can be found in the resources section). Placing ProfilingSecurityManager alone in its own jar file will allow us to filter and suppress outputting rules that arise from actions originating in its own jar file codebase. ProfilingSecurityManager is aware of its own unique codebase via

if( url.toString().equals(thisCodeSourceURLString) ) {
   return null;
}

and can therefore prevent reporting on itself. Compile and jar the tool:

$ mkdir -p classes lib

$ rm -rf classes/* lib/*

$ javac -d classes ProfilingSecurityManager.java

$ jar cf lib/psm.jar -C classes secmgr/manager

$ rm -rf classes/secmgr/manager

Before we proceed too much further, we should say something about how to activate ProfilingSecurityManager as our application's security manager. Recall earlier, we forced our applications to run subject to the default Java security manager by setting the system property -Djava.security.manager specifically without a corresponding property value. We need to take this one step further and specify the custom security manager as the security manager by assigning a value to the system property: -Djava.security.manager=secmgr.ProfingSecurityManager. Thus activated, ProfilingSecurityManager will write to System.out the rules needed in a policy file that will allow the application to run without throwing security violation exceptions. However, these rules cannot be processed into a final, useable form until the application has completed its run under ProfilingSecurityManager. Why? Because only then is it known that the application has finished requesting access to checked resources. So for processing and tidying the rules when the application has finished running under ProfilingSecurityManager, we provide a simple Perl script parsecodebase.pl (also in the sample code) to aggregate, format, and output the rules in a readable format, sorted and grouped by codebase.

OK, so running the simple PrintHome application with ProfilingSecurityManager specified as the security manager and with the rules output processed by parsecodebase.pl produces

$ java -cp .:lib/psm.jar -Djava.security.manager=secmgr.ProfilingSecurityManager PrintHome  > raw.out 

$ parsecodebase.pl < raw.out > policy.txt

$ cat policy.txt
grant codeBase "file:/home/cid/Projects/CustomSecurityManager/" {
   permission java.util.PropertyPermission "user.home", "read";
};

$ java -cp . -Djava.security.manager -Djava.security.policy=policy.txt PrintHome 
/home/cid

So we see again the ProfilingSecurityManager satisfies both our design goals:

  • Our application runs under a security manager with a well-defined application-specific policy
  • We determined that policy programmatically

How does ProfilingSecurityManager work? ProfilingSecurityManager overrides both versions of java.lang.SecurityManager's checkPermission method. The two forms of this method are the central chokepoints for examining to which resource or action the application requested access. The overridden checkPermission methods always return without throwing exceptions -- essentially meaning "access allowed" -- but not until they build and output the rule to allow the action responsible for their being called in the first place.

Pages: 1, 2

Next Pagearrow