A napokban a blog.fireeye.com oldalon sikerült egy Java 7 sebezhetőséget találni, amelyet kihasználva egy Java applet képes kitörni a sandbox környezetéből. A hiba legvégén a Java 7 platformban megjelent ClassFinder osztály resolveClass metódusát találjuk, amely a sandbox előírásokat megkerülve képes betölteni bármilyen osztályt:

public static Class<?> resolveClass(String name, ClassLoader loader) throws ClassNotFoundException {
    Class<?> type = PrimitiveTypeMap.getType(name);
    return (type == null)
            ? findClass(name, loader)
            : type;
}

A meghívott findClass metódus pedig megkeresi és visszaadja az adott osztályt:

public static Class<?> findClass(String name) throws ClassNotFoundException {
    try {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader == null) {
            // can be null in IE (see 6204697)
            loader = ClassLoader.getSystemClassLoader();
        }
        if (loader != null) {
            return Class.forName(name, false, loader);
        }

    } catch (ClassNotFoundException exception) {
        // use current class loader instead
    } catch (SecurityException exception) {
        // use current class loader instead
    }
    return Class.forName(name);
}

Mivel az átadott ClassLoader példány értéke lehet null, ezért ebben az esetben lekérdezésre kerül a SystemClassLoader, amely vissza fogja adni a kért osztályt. A hiba kihasználásában sokat nem számít, de az itt látható kód igen "szép":

Ezt a ClassFinder osztályt nem tudja egy sandbox-ban futó applet meghívni, mivel a com.sun.beans.finder csomagban van, amelyet nem lehet elérni a sandbox környezetből, ám az Java 1.4 verzióban megjelenő java.beans.Expression osztályon keresztül igen:

new Expression(Class.class, "forName", new Object[] {"sun.awt.SunToolkit"}).invoke();

Az Expression.java osztály forrásában azt láthatjuk, hogy meghívja az ősosztályának a konstruktorát:

@ConstructorProperties({"target", "methodName", "arguments"})
public Expression(Object target, String methodName, Object[] arguments) {
    super(target, methodName, arguments);
}

A Statement.java osztályban láthatjuk az AccessController ellenőrzése alatti invoke hívást:

try {
    return AccessController.doPrivileged(
            new PrivilegedExceptionAction<Object>() {
                public Object run() throws Exception {
                    return invokeInternal();
                }
            },
            acc
    );
}
catch (PrivilegedActionException exception) {
    throw exception.getException();
}

Amely tartalmaz egy szép külön ágat arra az esetre, ha a Class.forName() kerülne meghívásra:

// Class.forName() won't load classes outside
// of core from a class inside core. Special
// case this method.
if (target == Class.class && methodName.equals("forName")) {
    return ClassFinder.resolveClass((String)arguments[0], this.loader);
}

Így viszont visszaadódik a kért osztály, mindenféle ellenőrzés nélkül, mert a ClassFinder bizony nem fog dobni PrivilegedActionException kivételt... ettől a ponttól már "triviális" a sandbox környezetből való kitörés, mivel a SunToolkit és a hozzá hasonló a belső használatú osztályok elvileg nem lennének betölthetőek a sandbox környezetben... (smile)

A Statement.java osztályban is van egy nagyon szép programozástechnikai hiba, amely közel két éve jelent meg a kódbázisban, és azóta is benne fekszik:

--- a/src/share/classes/java/beans/Statement.java	Thu Jan 29 15:34:50 2009 +0300
+++ b/src/share/classes/java/beans/Statement.java	Fri Jul 03 16:56:29 2009 +0400
@@ -66,6 +66,7 @@ public class Statement {
     Object target;
     String methodName;
     Object[] arguments;
+    ClassLoader loader;
 
     /**
      * Creates a new <code>Statement</code> object with a <code>target</code>,
@@ -157,7 +158,7 @@ public class Statement {
         // of core from a class inside core. Special
         // case this method.
         if (target == Class.class && methodName.equals("forName")) {
-            return ClassFinder.resolveClass((String)arguments[0]);
+            return ClassFinder.resolveClass((String)arguments[0], this.loader);
         }
         Class[] argClasses = new Class[arguments.length];
         for(int i = 0; i < arguments.length; i++) {

Noha a hibával nincs kapcsolatban, de belekerült a kódbázisba egy loader nevű példányváltozó, amelyet átadnak paraméterként a resolveClass metódus hívásánál, de sehol nem kap értéket. Elképesztő! Bármelyik statikus kódanalizáló program hangosan ugat egy ilyen hibára, amely két éve a Java egyik fontos osztályában fekszik... (smile)

Veszélyes?

A külföldi és hazai IT portálok igen kényes sebezhetőségként írták le ezt a problémát, de valószínűleg nagyobb a füstje, mint a lángja... szakmailag ugyanis nagyon érdekes egy ilyen hibát megtalálni, felfedni és a sebezhetőség okát kideríteni.. de a hitelesen aláírt Java appletek képesek a sandbox környezeten kívül is futni, és akik vissza szeretnének élni a helyzettel, azoknak nem okozhat nehézséget egy hamis, de érvényes és hiteles aláírás megszerzése... nem hiteles aláírással pedig csak egy felhasználói jóváhagyás kell, a felhasználó pedig jóvá fogja hagyni az applet futását...

...ennek ellenére tartsuk szárazon a puskaport és járjuk nyitott szemmel, de nem feltétlen szükséges a JRE/JDK eltávolítása a gépekről, ahogy azt több helyen is olvashatjuk, a helyzet kezelését rábízhatjuk a víruskereső szoftverekre, amelyeknek illene blokkolnia egy ilyen támadást, illetve legalább egy – akár false positive – figyelmeztetést küldeni a felhasználó felé, ha egy aláírás nélküli applet szeretne elindulni és benne van egy Expression hívás. A legbiztosabb megoldás az, ha letiltjuk a Java plugin futását a böngészőben és csak akkor engedélyezzük, ha arra kifejezetten szükségünk van.

Update

Van rá esély, hogy az OpenJDK 6 is érintett a sebezhetőségben, mivel ezen Java környezet forrásban úgy tűnik, hogy a fenti kódrészletek backportolásra kerültek, ugyanis mind az Oracle oldaláról letölthető OpenJDK 6 update 25 forrása, mind az online források között megtalálható a ClassFinder osztály, amely elvileg a Java 1.7 óta létezik:

JDK Release Genealogy

(forrás: https://blogs.oracle.com/darcy/entry/openjdk_6_genealogy)

Javítócsomag

Megérkezett a patch a hibával kapcsolatban: http://www.oracle.com/technetwork/topics/security/alert-cve-2012-4681-1835715.html