If a user encounters signed code, chances are that the code requires more than appropriate privileges to carry out its operations. It is in the interest of the users to verify the authenticity and integrity of the artifacts that they wish to deploy. Although it is usually a bad idea to sign code, some actions necessitate this step. For example, if the application needs to establish an http
connection with an external host to download plugins or extensions, a vendor may provide signed code instead of having the user deal with complex security policies. Because executing signed code can be extremely dangerous, verifying authenticity of origin is of utmost importance. Users are most often not the best judges of what code is safe to execute and what constitutes malicious code.
Java based technologies typically use the Java Archive (JAR) feature for packaging files to facilitate platform independent deployment. Be it desktop applications, Enterprise Java Beans (EJB), MIDlets (J2ME) or Weblogic Server J2EE applications, for example, JAR files are the preferred distribution mechanism. The point and click installation provided by Java Web Start also relies on the JAR file format for packaging. Vendors sign their JAR files when required, however, this should not be interpreted to be the case that the code cannot be misused.
...
This noncompliant code example demonstrates the JarRunner application that can be used to dynamically execute a particular class residing within a JAR file [Tutorials 08]. It creates a JarClassLoader
that loads an application update, plugin or patch over an untrusted network such as the Internet. The URL to fetch the code is specified as the first argument (for example, http://somewebsite.com/software-updates.jar) and any other arguments specify the arguments that are to be passed to the class to be loaded. Reflection is used to invoke the main
method of the loaded class. Unfortunately, there are no built in checks for verifying the signature. Any man in the middle can construct a class that will run with the permissions specified for this code source in the policy , by default, JarClassLoader
verifies the signature using the public key contained within the JAR file.
Code Block | ||
---|---|---|
| ||
public class JarRunner { public static void main(String[] args) { if (args.length < 1) { usage(); } URL url = null; try { url = new URL(args[0]); } catch (MalformedURLException e) { fatal("Invalid URL: " + args[0]); } // Create the class loader for the application jar file JarClassLoader cl = new JarClassLoader(url); // Get the application's main class name String name = null; try { name = cl.getMainClassName(); } catch (IOException e) { System.err.println("I/O error while loading JAR file:"); System.exit(1); } if (name == null) { fatal("Specified jar file does not contain a 'Main-Class'" + " manifest attribute"); } // Get arguments for the application String[] newArgs = new String[args.length - 1]; System.arraycopy(args, 1, newArgs, 0, newArgs.length); // Invoke application's main class try { cl.invokeClass(name, newArgs); } catch (ClassNotFoundException e) { fatal("Class not found: " + name); } catch (NoSuchMethodException e) { fatal("Class does not define a 'main' method: " + name); } catch (InvocationTargetException e) { e.getTargetException().printStackTrace(); System.exit(1); } } private static void fatal(String s) { System.err.println(s); System.exit(1); } private static void usage() { fatal("Usage: java JarRunner url [args..]"); } } class JarClassLoader extends URLClassLoader { private URL url; public JarClassLoader(URL url) { super(new URL[] { url }); this.url = url; } public String getMainClassName() throws IOException { URL u = new URL("jar", "", url + "!/"); JarURLConnection uc = (JarURLConnection) u.openConnection(); Attributes attr = uc.getMainAttributes(); return attr != null ? attr.getValue(Attributes.Name.MAIN_CLASS) : null; } public void invokeClass(String name, String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException { Class c = loadClass(name); Method m = c.getMethod("main", new Class[] { args.getClass() }); m.setAccessible(true); int mods = m.getModifiers(); if (m.getReturnType() != void.class || !Modifier.isStatic(mods) || !Modifier.isPublic(mods)) { throw new NoSuchMethodException("main"); } try { m.invoke(null, new Object[] { args }); } catch (IllegalAccessException e) { System.out.println("Access denied"); } } } |
...
Code Block | ||
---|---|---|
| ||
jarsigner -verify signed-updates-jar-file.jar |
An When the local system cannot be trusted, an explicit signature verification check should must be built within the invoking program. This can be achieved by obtaining the chain of certificates from the CodeSource
of the class being loaded and checking if any one of the certificates belongs to the trusted signer whose certificate has been obtained securely beforehand and stored in a local keystore. The invokeClass
method can be modified to do this as shown in this compliant solution.
...
It is not always the case that arbitrary code gets executed. By default, the URLClassLoader
and all its subclasses are only given enough permissions to interact with the URL
that was specified when the URLClassLoader
object was created. This means that the program can only interact with the specified host by default. However, this does not mitigate the risk completely as the loaded file may need to be granted appropriate privileges to perform more sensitive operations such as updating an existing local JAR file.
...