Contents
Security plays an important role in distributed systems. The Jini security model is based on the JDK 1.2 security system.
Security for Jini is based on the JDK 1.2 security model.
This makes use of a SecurityManager
to grant
or deny access to resources.
A few of the examples may work fine without a security manager.
Most will require an appropriate security manager in place.
Installing a suitable manager may be done by
System.setSecurityManager(new RMISecurityManager());
This should be done before any network-related calls.
The security manager will need to make use of a
security policy. This is typically
given in policy files which are in default locations or
are specified to the Java runtime. If policy.all
is a policy file in the current directory, then invoking the
runtime by
java -Djava.security.policy="policy.all" ...
will load the contents of the policy file.
A totally permissive policy file can contain
grant {
permission java.security.AllPermission "", "";
};
This will allow all permissions, and should never be used
outside of a test and development environment - and moreover, one
that is insulated from other potentially untrusted machines.
(Standalone is good here!)
The big advantage of this is that it gets you going on the
rest of Jini, without worrying about
security issues while you are grappling with other problems!
Granting all permissions to everyone is a very trusting act, in the potentially hostile world of the Internet. Not everyone is ``mister nice guy''. The client is vulnerable to attack because it is downloading code that satisfies a request for a service, and then executing that code. The checks that it is a genuine service are really non-existent: it has to implement the requested interface, and maybe satisfy conditions on associated Entry objects. If it passes these, then it can do anything.
As an example of a deliberately hostile service, a client asking for a simple file classifier could end up getting this object:
package hostile;
import common.MIMEType;
import common.FileClassifier;
/**
* HostileFileClassifier1.java
*
*
* Created: Mon Jul 12 14:22:13 1999
*
* @author Jan Newmarch
* @version 1.0
*/
public class HostileFileClassifier1 implements FileClassifier {
public MIMEType getMIMEType(String fileName) {
if (java.io.File.pathSeparator.equals("/")) {
// Unix - don't uncomment the next line!
// Runtime.getRuntime().exec("/bin/rm -rf /");
} else {
// DOS - don't uncomment the next line!
// Runtime.getRuntime().exec("format c: /u");
}
return null;
}
public HostileFileClassifier1() {
// empty
}
} // HostileFileClassifier1
This object would be exported from a hostile service to run completely in any
client unfortunate enough to download it.
It is not necessary to actually call a method on the downloaded object - the mere act of downloading can do the damage, if the object overrides the deserialization method:
package hostile;
import common.MIMEType;
import common.FileClassifier;
/**
* HostileFileClassifier2.java
*
*
* Created: Mon Jul 12 14:22:13 1999
*
* @author Jan Newmarch
* @version 1.0
*/
public class HostileFileClassifier2 implements FileClassifier,
java.io.Externalizable {
public MIMEType getMIMEType(String fileName) {
return null;
}
public void readExternal(java.io.ObjectInput in) {
if (java.io.File.pathSeparator.equals("/")) {
// Unix - don't uncomment the next line!
// Runtime.getRuntime().exec("/bin/rm -rf /");
} else {
// DOS - don't uncomment the next line!
// Runtime.getRuntime().exec("format c: /u");
}
}
public void writeExternal(java.io.ObjectOutput out)
throws java.io.IOException{
out.writeObject(this);
}
public HostileFileClassifier2() {
// empty
}
} // HostileFileClassifier2
The two classes above assume that clients will make requests for the implementation of a particular interface, and this means that the attacker would need to know this interface. It would require some knowledge of the clients it is attacking (that they will ask for this interface). At the moment, there are no standard interfaces, so this may not be a feasible way of attacking many clients. As interfaces such as those for a printer become specified and widely used, attacks based on hostile implementations of services may become more common.
Setting the security access to AllPermission
is easy and removes
all possible security issues that may hinder development of a Jini application.
But it leaves your system open, so that you must start using a more rigorous security
policy at some stage - hopefully before others have damaged your system.
The problem with moving away from this policy is that permissions are
additive rather than subtractive.
That is, you can't take permissions away from AllPermission
,
but have to start with an empty permission set and add to that.
Not giving enough permission can result in at least three cases:
rmi.FileClassifierServer
. The third occurs
for the client client.TestFileClassifier
.
There is a system property java.security.debug
that can be set to
print information about various types of access to the security mechanisms.
This can be used with a slack security policy to find out exactly what permissions
are being granted. Then, with the screws tightened, you can see where permission
is being denied. An appropriate value for this property is access
, as
in
java -Djava.security.debug=access ...
For example, running client.TestFileClassifier
with few permissions
granted may result in a trace including
...
access: access allowed (java.util.PropertyPermission socksProxyHost read)
access: access allowed (java.net.SocketPermission 127.0.0.1:1174 accept,resolve)
access: access denied (java.net.SocketPermission 130.102.176.249:1024 accept,resolve)
access: access denied (java.net.SocketPermission 130.102.176.249:1025 accept,resolve)
access: access denied (java.net.SocketPermission 130.102.176.249:1027 accept,resolve)
...
The denied access is an attempt to make a socket accept or resolve request
on my laptop (IP address 130.102.176.249), probably for RMI-related
sockets. Since the client just sits there indefinitely making this request
on one random port after another, this
permission needs to be opened up as the client otherwise appears to just hang.
The safest way for a Jini client or service to be part of a Jini federation is through abstinence: that is, refuse to take part. This doesn't get you very far in populating a federation. The JDK 1.2 security model allows a number of ways in which more permissive activity may take place:
grant {
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
permission java.net.SocketPermission "*.edu.au:80", "connect";
}
grant codebase "http://sunshade.dstc.edu.au/classes/" {
permission java.security.AllPermission "", "";
}
grant signedBy "sysadmin" {
permission java.security.AllPermission "", "";
}
In order to partake in a Jini federation, a service must become ``sufficiently'' visible. A service needs to find a service locator before it can advertise its services. This can be by unicast to particular locations or by multicast.
Unicast discovery does not need any particular permissions to be set. The discovery can be done without any policy file needed.
For the multicast case, the service must have DiscoveryPermission
for each group that it is trying to join. For all groups, the wildcard ``*''
can be used. So to join all groups, the permission granted should be
permission net.jini.discovery.DiscoveryPermission "*";
To join, say, the groups printers
and toasters
, the permission
would be (I'm not totally sure about this???)
permission net.jini.discovery.DiscoveryPermission,
"printers, toasters";
Once this permission is given, the service will make a multicast broadcast on
224.0.1.84. Socket permission for these requests and announcements
must be given by
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
The service may export a UnicastRemoteObject
. This will require
listening on a port for requests. The default constructor will assign a
random port (above 1024) for this. If desired, this port may be specified
by other constructors. This will require further socket permissions, such as
permission java.net.SocketPermission "localhost:1024-", "connect,accept";
permission java.net.SocketPermission "*.dstc.edu.au:1024-", "connect,accept";
to accept connections on any port above 1024 from the localhost
or any computer in the dstc.edu.au
domain.
A number of parameters may be set by preferences, such as
net.jini.discovery.ttl
. It does no harm to allow the Jini
system to look for these parameters, and this may be allowed by
permission java.util.PropertyPermission "net.jini.discovery.*", "read";
A fairly minimal policy file suitable for a service exporting an RMI object could then be
grant {
permission net.jini.discovery.DiscoveryPermission "*";
// multicast request address
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
// multicast announcement address
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
// RMI connections
permission java.net.SocketPermission "*.canberra.edu.au:1024-", "connect,accept";
permission java.net.SocketPermission "130.102.176.249:1024-", "connect,accept";
permission java.net.SocketPermission "127.0.0.1:1024-", "connect,accept";
// reading parameters
// like net.jini.discovery.debug!
permission java.util.PropertyPermission "net.jini.discovery.*", "read";
};
The client is most at risk in the Jini environment. The service exports objects; the lookup locator stores objects, but does not ``bring them to life'' and execute any of their methods; but the client brings an external object into its address space and runs it using all of the permissions that it has as a process running in an operating system. So it will run under the permissions of a particular user, in a particular directory, with user accesses to the local file system and network. It could destroy files, make network connections to undesirable sites (or desirable, depending on your tastes!) and download images from them, start processes to send obnoxious mail to anyone in your address book, and generally make a mess of your electronic identity!
A client using multicast search to find service locators will need to grant discovery permission and multicast announcement permission, just like the service
permission net.jini.discovery.DiscoveryPermission "*";
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
RMI connections on random ports may also be needed
permission java.net.SocketPermission "*.dstc.edu.au:1024-", "connect,accept"
In addition to these, class definitions will probably need to be uploaded so that services can actually run in the client. This is the most serious risk area for the client, as the code contained in these class definitions will be run in the client, and any errors or malicious code will have their effect because of this. The client view of the different levels of trust can be given as
permission java.net.SocketPermission "127.0.0.1:80", "connect,accept";
permission java.net.SocketPermission "*.dstc.edu.au:80", "connect,accept";
However, while this is will allow code to be downloaded on port 80, it may not block some malicious attempts. Any user can start an HTTP server on any port (Windows) or above 1024 (Unix). A service can then set its codebase to whatever port the HTTP server is using. Perhaps these other ports should be blocked. Unfortunately, RMI uses random ports, so these ports need to be open.
A fairly minimal policy file suitable for a client could then be
grant {
permission net.jini.discovery.DiscoveryPermission "*";
// multicast request address
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
// multicast announcement address
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
// RMI connections
// DANGER
// HTTP connections - this is where external code may come in - careful!!!
permission java.net.SocketPermission "127.0.0.1:1024-", "connect,accept";
permission java.net.SocketPermission "*.canberra.edu.au:1024-", "connect,accept";
permission java.net.SocketPermission "130.102.176.249:1024-", "connect,accept";
// DANGER
// HTTP connections - this is where external code may come in - careful!!!
permission java.net.SocketPermission "127.0.0.1:80", "connect,accept";
permission java.net.SocketPermission "*.dstc.edu.au:80", "connect,accept";
// reading parameters
// like net.jini.discovery.debug!
permission java.util.PropertyPermission "net.jini.discovery.*", "read";
};
The Jini distribution includes a transaction manager called ``mahalo''. This uses the new activation methods of RMI in JDK 1.2. Without worrying about any other arguments, the call to run this transaction manager is
java -jar mahalo.jar
(assuming the jar file is in the classpath).
The transaction manager is a Jini service, and will need class definitions
to be uploaded to clients. The class files are in mahalo-dl.jar
,
and will come from an HTTP server as specified in
the first command-line argument:
java -jar mahalo.jar http://localhost/mahalo-dl.jar
(or appropriate HTTP address).
In order to register the service with a service locator and to set internal
values for the transaction manager running as a service.
This is done using the normal java.security.policy
property
java -Djava.security.policy=policy.txn \
-jar mahalo.jar http://localhost/mahalo-dl.jar
This will allow the service to be registered and uploaded.
A suitable policy could be
grant {
// rmid wants this
permission java.net.SocketPermission "127.0.0.1:1098", "connect,resolve";
// other RMI calls want these, too
permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
permission java.net.SocketPermission "130.102.176.153:1024-", "connect,resolve";
// access t transaction manager log files
permission java.io.FilePermission "/tmp/mahalo_log", "read,write";
permission java.io.FilePermission "/tmp/mahalo_log/-", "read,write,delete";
// properties used by transaction manager
permission java.util.PropertyPermission "com.sun.jini.mahalo.managerName", "read";
permission java.util.PropertyPermission "com.sun.jini.use.registry", "read";
};
When the service is run, the
service has to be activated in some JVM - the one running
rmid
. This in turn may need its own security policy, which is
specified in the second command-line argument
java -Djava.security.policy=policy.txn -jar mahalo.jar \
http://localhost/mahalo-dl.jar \
policy.actvn
A suitable activation policy could be
grant {
// rmid wants this
permission java.net.SocketPermission "127.0.0.1:1098", "connect,resolve";
// other RMI calls want these, too
permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
permission java.net.SocketPermission "130.102.176.153:1024-", "connect,resolve";
// access t transaction manager log files
permission java.io.FilePermission "/tmp/mahalo_log", "read,write";
permission java.io.FilePermission "/tmp/mahalo_log/-", "read,write,delete";
// properties used by transaction manager
permission java.util.PropertyPermission "com.sun.jini.mahalo.managerName", "read";
permission java.util.PropertyPermission "com.sun.jini.use.registry", "read";
// needed for activation
permission java.net.SocketPermission "224.0.1.84", "connect,accept,resolve";
permission java.io.FilePermission "/tmp/mahalo_log/-", "read";
permission java.util.PropertyPermission "com.sun.jini.thread.debug", "read";
permission java.lang.RuntimePermission "modifyThreadGroup";
permission java.lang.RuntimePermission "modifyThread";
// for dowloading mahalo-dl.jar from HTTP server
permission java.net.SocketPermission "*:8080", "connect,accept,resolve";
permission net.jini.discovery.DiscoveryPermission "*";
};
An activatable service runs within a JVM started by rmid
.
It does so with the same user identity as rmid
. So if
rmid
is run by, say, the superuser root
on
a Unix system, then all activatable services will run with that same user id,
root
. This is a security flaw, as any user on that system can
write and start an activatable service, and then write and run a client
that makes calls on the service. This is a way to run programs from an
arbitrary user that run with superuser priviliges.
My own machine has only a few users, and all of them I trust not to write
deliberately malicious programs (and right now, I am the only one of them
who can write Jini services). However, most may not be in such a fortunate
position. Consequently, rmid
should be run in such a way that
even if it is attacked, it will not be able to do any damage.
On Unix, there are two ways of reducing the risk
nobody
. This can be done
by changing rmid
to be setuid to this user. Note that the program
rmid
in the Java bin
directory is actually a shell script that
eventually calls a program such as bin/i386/green_threads/rmid
,
and it is this program that needs to be setuid
chroot
mechanism to run rmid
in a subdirectory
that appears to be the top-level directory `/'. This will make all other files
invisible to rmid
On an NT system, rmid
should be set up so that it only runs under
general user access rights.