| ★ wanayoo — archive 1999 http://java.sun.com/security/seccodeguide.html | Nouvelle recherche | Portail wanayoo |
Overview
A lot of emphasis has been put into the design and implementation of the JavaTM programming language to keep the system as secure as possible. However, a chain only is as strong as its weakest link, and when you add new system code, you are adding a new link to the security chain. This document is intended to help you write code that is free of security holes. It identifies potential pitfalls that you should avoid and shows you how to write your code so that it will not be vulnerable to security attacks.The code guidelines are grouped into the following three categories:
Privileged Code Guidelines
The JavaTM 2 Platform's access control mechanism protects system resources from unauthorized access by making sure that the calling code has the appropriate permissions for accessing that resource. Generally when a resource access is attempted, all code traversed by the execution thread up to that point must have appropriate permissions in order for the access to be allowed. There are many cases, however, where system code needs to access a system resource in order to be able to perform its functionality, although the code which called that system service does not have the appropriate permissions for accessing the resource.As an example, assume client code has RuntimePermission to load a certain native library. It calls upon the loadLibrary service, which, in order to accomplish its task, needs read access to the native library file. However, the client code does not have the appropriate FilePermission that allows it access to this file, and if the execution thread would be checked for FilePermission, the operation would fail, when it should have succeeded.
To solve this problem, the API for privileged blocks has been devised, which allows marking a code block as privileged. When a code block is marked as privileged, it can call services based on its permissions even if some of its callers do not have those permissions.
Please refer to http://java.sun.com/j2se/sdk/1.2/docs/guide/security/doprivileged.html for a detailed description of the API for privileged blocks.
Use privileged code sparingly, if at all. Write your code without using privileged blocks; if your code bumps into a security exception, then consider the possible need to use privileged blocks.
When writing privileged code, the following guidelines should be used.
There are certain privileged services that can be performed by system code on behalf of unauthorized clients even if the code is not encapsulated within a privileged block. Please read carefully the Exceptions to the privileged-block mechanism section, as using these services without care can make your code expose security holes.
Keep privileged code as short as possible
Always try to keep privileged code as short as possible. Remember that when a privileged block gets executed within your code, it can access any resource your code has permissions to access, even if its callers do not have these permissions. For example, if your code is an installed standard extension (which by definition resides in the <java-home>/lib/ext directory), then by default (if the default policy file is not modified) it can load libraries, read any file, read system properties, etc. If these operations happen in privileged blocks, then they can be completed on behalf of unprivileged clients. By enabling privileges for as little code as possible it is much easier to audit the code to make sure it is only accessing the minimal amount of protected resources.Pitfalls and other things to watch out for
An important concern should be public methods (and/or non-public methods that can get invoked by public methods) which wrap privileged blocks that deal with tainted variables. Here tainted means the variables are set by the caller (i.e., passed in as parameters), and thus not under the control of the privileged code.For example, consider the following method, which might seem like a handy way to access properties:
public static String getProp(final String name) {return (String) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // privileged code goes here, for example: return System.getProperty(name); } });The problem with this method is that since it is public, anyone can call it, and it takes a tainted parameter, namely the name parameter. This would allow any piece of code (say, an applet) access to all system properties, assuming the above method is in a protection domain that allows access to all system properties (i.e., it is on the boot-classpath). If the getProp function was a protected function within a public class, then this would still be problematic (unless the class is in a restricted package. See Protecting packages ), because anyone could extend the class and be able to call the function.Always make sure you are not using tainted variables within your privileged code that could compromise the security of such code. If you do need such helper methods, make sure they are private methods and cannot be called from outside your class.
When to wrap code in a privileged block
You must wrap code in a privileged block when the code performs tasks that would normally not be allowed by an applet or untrusted code, but when the tasks need to be done on behalf of this untrusted code. Such tasks include:
- reading any system properties
- reading files, even if they are in java.home
- opening sockets
- writing files, such as saving out properties in appletviewer
- loading dynamic libraries with System.loadLibrary or Runtime.getRuntime.loadLibrary
Note that you only need to wrap such calls if they could (and need) be invoked by untrusted code, either directly or indirectly. For example, the compiler does not (and should not) need privileged blocks because it is not run by untrusted code such as an applet inside the appletviewer.
Java Code Guidelines
When writing general code, the following guidelines should be used.
- Static fields
- Reducing scope
- Public methods and fields
- Protecting packages
- The equals methods
- Make objects immutable if possible
- Never return a reference to an internal array that contains sensitive data
- Never store user-supplied arrays directly
- Serialization
- Native methods
- Clear sensitive information
Static fields
- Refrain from using non-final public static variables
To the extent possible, refrain from using non-final public static variables because there is no way to check whether the code that changes such variables has appropriate permissions.
- In general, be careful with any mutable static states that can cause unintended interactions between supposedly independent subsystems
Reducing scope
As a rule, reduce the scope of methods and fields as much as possible. Check whether package-private members could be made private, whether protected members could be made package-private/private, etc.Public methods/fields
Refrain from using public variables. Instead, let the interface to your variables be through accessor methods. In this way it is possible to add centralized security checks, if required.Make sure that any public method that has access to and/or modifies any sensitive internal states includes a security check.
See the following example in which it is possible that untrusted code could set the value for TimeZone.
private static TimeZone defaultZone = null; public static synchronized void setDefault(TimeZone zone) { defaultZone = zone; }Protecting packages
It is sometimes desirable to globally protect a package against access by untrusted code. This section describes a few techniques for doing this.
- Protecting against package-insertion: Untrusted code that wants to gain access to package-protected members of a class can try to define new classes of its own within the attacked package in order to gain access to these members. There are two ways to protect against such an attack:
- The package can be protected from insertion of rogue classes by adding the following line to the java.security properties file:
...package.definition=Package#1 [,Package#2,...,Package#n]...This causes a class loader's defineClass method to throw an exception when an attempt is made to define a new class within these package, unless the code has been granted the following permission:
...
RuntimePermission("defineClassInPackage."+package)...
- Another way to protect against package-insertion is by putting the package's classes in a sealed JAR file.
(see http://java.sun.com/j2se/sdk/1.2/docs/guide/extensions/spec.html)By using this technique, no code can be granted permission to extend the package and hence there is no need to modify the java.security properties file.
- Protecting against package-access: Package members can be protected from access by untrusted code by restricting access to the package and granting access permissions only to specified code. This can be done by adding the following line to the java.security properties file:
...package.access=Package#1 [,Package#2,...,Package#n]...This causes a class loader's loadClass method to throw an exception when an attempt is made to access a class from these packages, unless the code has been granted the following permission:
...
RuntimePermission("accessClassInPackage."+package)...Make objects immutable if possible
Make your object immutable if possible. If that is not possible, make them cloneable and return copies. If you return objects like arrays, Vectors, Hashtables, etc., remember that these objects are not immutable, and the caller can change the contents of these objects which may have security implications. Additionally, immutable objects can improve concurrency, since no locking is needed. See Clear sensitive information for an exception to this rule.Never return a reference to an internal array that contains sensitive data
This is just a variant on the immutable rule, but is mentioned here because it is a common mistake. Even if your array contains objects that are immutable (such as Strings), you need to return a copy so the the caller cannot change which Strings are in the array. Instead of passing back an array, make a copy of the array and return the copy.Never store user given array of objects directly
This is another variant of the immutable rule. Constructors and methods taking arrays of objects, such as arrays of PublicKeys, should clone the arrays before saving them internally rather then directly assigning the array reference to an internal variable of similar type. Without this precaution, any changes made by the user to the external array (after creating the object using the constructor in question) could accidentally change the internal state of the object even though the object might otherwise be immutable.Serialization
When an object is serialized - and until it is deserialized - it is outside of the control of the Java runtime environment, and is therefore is outside the control of the security provided by the Java platform.Here are some things to keep in mind when implementing the Serializable interface:
- transient
Use the transient keyword for the fields that contain direct handles to system resources and that contain information relative to an address space. If a resource such as a file handle was not declared transient, the object could be altered while in its serialized state, enabling it to have improper access to resources after it is deserialized.
- Class specific serializing/deserializing methods
To guarantee that a deserialized object does not have a state which violates some set of invariants that need to be guaranteed, a class should define its own deserializing method and use the ObjectInputValidation interface to validate invariants.If a class defines its own serializing method, then it should not pass an internal array to any DataInput/DataOuput method that takes an array. All DataInput/DataOutput methods can be overridden. Note that the default serialization does not expose private byte array fields to DataInput/DataOutput byte array methods.
If a Serializable class passes a private array directly to a DataOutput(write(byte [] b)) method, then a hacker could subclass ObjectOutputStream and override the write(byte [] b) method to enable him to access and modify the private array. The following example illustrates the problem.
Your class:
public class YourClass implements Serializable { private byte [] internalArray; .... private synchronized void writeObject(ObjectOutputStream stream) { ... stream.write(internalArray); ... } }Hacker's code:public class HackerObjectOutputStream extends ObjectOutputStream() { public void write (byte [] b) { Modify b } } ... YourClass yc = new YourClass(); ... HackerObjectOutputStream hoos = new HackerObjectOutputStream(); hoos.writeObject(yc);Encrypting a byte stream
Another way of protecting a bytestream outside the virtual machine is to encrypt the stream produced by the serialization package. Encrypting the byte stream prevents the decoding and the reading of a serialized object's private state. If you decide to encrypt, you have to manage the keys, the location in which they are stored, the way they will be given to the deserialization programs, etc.,
Other things to watch out for
If untrusted code has a restriction in creating an object, then make sure that untrusted code has the same restriction when it deserializes the object. Remember that deserialzing an object is a type of object creation.For example, if an applet creates a frame, that frame will be created with a warning label. If a frame is serialized by an application, and then deserialized by an applet, make sure that it comes up with the same warning banner.
Native methods
Native methods should be examined for:
- What they return
- What they take as parameters
- Whether they bypass security checks
- Whether they are public, private, ....
- Whether they contain method calls which bypass package-boundaries, thus bypassing package protection
Clear Sensitive Information
When storing sensitive information such as credentials, strive to keep it in mutable data types such as arrays rather than in immutable objects such as Strings. This will allow the sensitive information to be explicitly cleared at the earliest possible time. Do not trust the Java platform's automatic garbage collection to do that for you because the memory may never be reclaimed by the collector, or it may be reclaimed much later on. Clearing this information as soon as possible makes a heap-inspection attack from outside the virtual machine more difficult.C Code Guidelines
These guidelines aren't rigorous or deep, but they represent a simple checklist for C programs.
- Check all input arguments for validity
- Never use the unix "system()" call
- Never use scanf; use fgetc; use the Java-software versions of printf
- Check environment variables for validity
- Beware of setuid root, and beware of programs that ship with setuid root
- Never open a file as root
- Check all functions for valid returns
- Strip binaries
- Consider logging things like UIDs, file accesses, and so on.
- Don't use chmod(), chown(), chgrp(): Use fchmod(), fchown() instead
Check all input arguments for validity
Here, validity means not just the C type, but also program-specific type information. For example, if an input argument is supposed to be an executable file, check that the input file is an executable and that the user has permission to run the file.Never use the unix "system()" call
The system() command is a very flexible command, but it will try to execute whatever you pass to it. Don't use the system command inside utilities. For the same reason, do not invoke shell(), popen or exec*p from inside your code. Additionally, don't use setuid on a whole program. Just use setuid root for the small part where you do need to be root, and then setuid back to the user.Never use scanf; use fgetc; use Java software alternatives for printf
Scanf will read anything. It will do all the tedious formatting for you, which makes it attractive to use. However, its behavior when given a string that doesn't match the format expected is undefined, and often opens up holes. Therefore, don't use scanf to read input from users. Use fgetc.There are Java software alternatives to printf that should be used to avoid stack overflows when hackers try to have some internal function print out a string that is too long.
jio_fprintf jio_snprintf jio_vsnprintfIn 1.1.x the jio functions are found in src/share/sun/jio.c, and in the build, build/lib/sparc/green_threads/libjava.soIn 1.2 the jio functions are found in src/share/javavm/runtime/util.c, and in the build, build/solaris/lib/sparc/libjava.so.
Check environment variables for validity
Don't just read them and use them.Beware of setuid root, and beware of programs that ship with setuid root.
Only run with setuid as root for the very short time that you need to actually DO something as root. Try to not setuid root at all. Create a new user, like "foo" or "duke", and use that UID. The OS reserves the first 200 UIDs for internal use, so there are plenty of UIDs that can be defined. This reservation of UIDs is part of the ABI. If you MUST change uid, use seteuid first rather than setuid, and use setreuid as a backup. This makes it easy to revert back to either the real uid or the saved uid.
Avoid setuid shell-scripts altogether!!!
setuid shell-scripts are even more dangerous than setuid programs, as they can be subverted in many ways.
Consequently use of setuid shell-scripts is not recommended.Never open a file as root
Don't open the file as root. Open the file as user. If you need to be root, first open the file as user, then become root, then go back to user before changing the file.Check all functions for valid returns
If a function doesn't return a valid value, notice it and then do the right thing. Don't ignore return values.Strip binaries
Believe it or not, a lot of information can be obtained from the binaries just by running "strings" on them.Consider logging things like UIDs, file accesses, and so on.
Don't use syslog for logging purposes, though. Syslog does not check the file system environment variable for validity.
Don't use chmod(), chown(), chgrp(): Use fchmod(), fchown() instead
The rational behind this recommendation is the following. Compare the following two code fragments:
code snippet #1
"
fd = open("name");
write(fd, "#!/bin/sh...", 24);
close(fd);
chmod("name");
"and...
code snippet #2
"
fd = open("name");
write(fd, "#!/bin/sh...", 24);
fchmod(fd, 666);
close(fd);
"If someone races you, they can do the following
if (access("name") == OK)
{
remove("name");
symlink("name", "target_name");
}Now in code snippet #1, you may have just chmod'd your target_name, which could be something like /etc/passwd.
Apart from this security consideration, code snippet #2 is more efficient because fchmod(...) does not have to do a separate look-up of the name, whereas chmod(..) must do so.
|
Copyright © 2000 Sun Microsystems, Inc. All Rights Reserved. Please send comments to: java-security@sun.com |
|