README.md
Rendering markdown...
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import sun.misc.BASE64Decoder;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.annotation.IncompleteAnnotationException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
//this import is only for java 1.8
//import java.util.Base64;
import java.security.Key;
import java.util.zip.GZIPInputStream;
/**
* Simples Servidor HTTP que desserializa dados recebidos nos seguintes formatos:
*
* 1) via HTTP POST em formato binário (ou seja, \xAC\xED)
* 2) via HTTP POST como valor de algum parâmetro (eg. "ViewState") nos formatos 1) base64 (rO0...) ou 2) gzip+base64 (H4sI...)
* 3) via cookies (header cookie) nos formatos base64 (rO0) ou gzip+base64 (H4sI) (eg. Cookie: JSESSIONID=rO0... ou Cookie: JSESSIONID=H4sI...)
* 4) via Cookie rememberMe (like Apache Shiro), criptografado com aes-128-cbc e chave hardcoded
* 5) via XML para explorar o XStream
*
* Após a desserialização, ele tenta fazer um cast para Integer, a fim de simular o que
* ocorre em um servidor "real" (erro de casting após a desserialização)
*
*
* OBS: Sobre Apache Shiro, ver:
* https://github.com/apache/shiro/blob/master/crypto/cipher/src/main/java/org/apache/shiro/crypto/JcaCipherService.java
* https://github.com/apache/shiro/blob/8acc82ab4775b3af546e3bbde928f299be62dc23/integration-tests/guice3/src/main/webapp/WEB-INF/shiro.ini
* Para geracao do payload, use CommonsCollections2 ou CommonsCollections4 do ysoserial e criptografe com aes-128-cbc
* Se preferir, existem mtos sccripts prontos para geracao do payload, veja:
* ex: https://github.com/leveryd/vulndocker/blob/78ba54edbd2dd81f09bb6d3f03a446555e6b7614/vuln/shiro/shirotest.py
* Análise: http://www.freebuf.com/articles/system/125187.html
*
* -----------------------------------------------------------------------
* Mais detalhes na 12a edição da H2HC (hackers to hackers) magazine:
* https://www.h2hc.com.br/revista/
* -----------------------------------------------------------------------
*
* **** USAGE ****
*
* Compilando:
* $ javac VulnerableHTTPServer.java -XDignore.symbol.file
*
* Executando
* $ java VulnerableHTTPServer
*
* Ou, caso deseje testar payloads para explorar gadgets de bibliotecas específicas, use o -cp. Exs:
* $ java -cp .:commons-collections-3.2.1.jar VulnerableHTTPServer
* $ java -cp .:xstream-1.4.6.jar:commons-collections-3.2.1.jar VulnerableHTTPServer
*
* @author @joaomatosf
*/
public class VulnerableHTTPServer {
public static void banner(){
System.out.println("* =============================================================== *");
System.out.println("* Simple Java HTTP Server for Deserialization Lab v0.01 *");
System.out.println("* https://github.com/joaomatosf/JavaDeserH2HC *");
System.out.println("* =============================================================== *");
System.out.println("You can inject java serialized objects in the following formats:");
System.out.println(
"\n 1) Binary in HTTP POST (ie \\xAC\\xED). Ex:\n" +
" $ curl 127.0.0.1:8000 --data-binary @ObjectFile.ser\n"+
"\n 2) Base64 or Gzip+Base64 via HTTP POST parameters. Ex:\n" +
" $ curl 127.0.0.1:8000 -d \"ViewState=rO0ABXNy...\"\n"+
" $ curl 127.0.0.1:8000 -d \"ViewState=H4sICAeH...\"\n"+
"\n 3) Base64 or Gzip+Base64 in cookies. Ex:\n"+
" $ curl 127.0.0.1:8000 -H \"Cookie: JSESSIONID=rO0ABXNy...\"\n"+
" $ curl 127.0.0.1:8000 -H \"Cookie: JSESSIONID=H4sICAeH...\"\n"+
"\n 4) Base64 of AES-CBC encrypted with hardcoded Apache Shiro key. Ex:\n" +
" $ curl 127.0.0.1:8000 -H \"Cookie: rememberMe=MTIzNDU2Nzg...\"\n"+
"\n 5) XML for XStream RCE vulnerability/serialization. Ex:\n" +
" $ curl 127.0.0.1:8000 -d @file.xml\n -H \"Content-Type: application/xml\"");
System.out.println("OBS: To test gadgets in specific libraries, run with -cp param. Ex:\n" +
"$ java -cp .:commons-collections-3.2.1.jar VulnerableHTTPServer");
System.out.println("==================================================================");
}
public static void main(String[] args) throws IOException {
banner();
int port = 8000;
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/", new HTTPHandler());
server.setExecutor(null); // creates a default executor
server.start();
System.out.println("\nJRE Version: "+System.getProperty("java.version"));
System.out.println("[INFO]: Listening on port "+port);
System.out.println();
}
static class HTTPHandler implements HttpHandler {
String aesHardedCodeKey = "kPH+bIxk5D2deZiIxcaaaA==";
public void handle(HttpExchange t) throws IOException {
System.out.println("[INFO]: Received "+t.getRequestMethod()+" "+t.getRequestURI()+" from: "+t.getRemoteAddress());
String responseMsg = null;
boolean containsCookie = t.getRequestHeaders().containsKey("cookie");
// if there's a cookie with serialized java object
if (containsCookie){
String object = t.getRequestHeaders().get("cookie").get(0);
object = getObjectValue(object);
if (object.startsWith("H4sI") || object.startsWith("rO0") )
responseMsg = deserialize(object);
else { // try deserialize aes-cbc encrypted object
byte[] plainText = decryptAES(object,aesHardedCodeKey);
if (plainText == null)
responseMsg = "\nAn error ocurred when decrypting the stream.\n";
else
responseMsg = deserialize(new ByteArrayInputStream(plainText));
}
}
else if (t.getRequestMethod().equals("POST")){
InputStream input = t.getRequestBody();
// take 2 bytes from header to check if it is a raw object
PushbackInputStream pbis = new PushbackInputStream( input, 2 );
byte [] header = new byte[2];
int len = pbis.read(header);
pbis.unread( header, 0, len );
StringBuffer headerResult = new StringBuffer();
for (byte b: header)
headerResult.append(String.format("%02x", b));
// deserialize raw
if (headerResult.toString().equals("aced"))
responseMsg = deserialize(pbis); // deserialize RAW
else{ // deserialize H4sI, rO0,...
// read input into string
InputStreamReader isr = new InputStreamReader(pbis, "utf-8");
BufferedReader br = new BufferedReader(isr);
String body = br.readLine();
String paramName = "";
String object = getObjectValue(body);
if (object.startsWith("H4sI") || object.startsWith("rO0") )
responseMsg = deserialize(object); // deserialize H4sI, rO0...
else if (object.startsWith("<") )
responseMsg = deserializeXStream(object); // xtream
}
}// end if POST
else{
responseMsg = "<html>" +
"\n<title>DeserLab v0.01</title> " +
"\n<br>DeserLab v0.01 - Vulnerable HTTP Server for Deserialization Vulnerabilities Tests." +
"\n<br>See examples at: <a href=\"https://github.com/joaomatosf/JavaDeserH2HC\">https://github.com/joaomatosf/JavaDeserH2HC</a>" +
"\n<br> <form id=\"0\" name=\"inicial\" method=\"post\" action=\"/post\" enctype=\"application/x-www-form-urlencoded\">" +
"\n<bbr> <input type=\"hidden\" name=\"javax.faces.ViewState\" id=\"javax.faces.ViewState\" value=\"H4sI\" />";
}
t.getResponseHeaders().add("Server", "Vulnerable Java HTTP Server v0.01");
t.getResponseHeaders().add("Info", "http://github.com/joaomatosf/JavaDeserH2HC");
t.getResponseHeaders().add("Content-Type", "x-java-serialized-object");
if (t.getRequestURI().getPath().contains("jexws") || t.getRequestURI().getPath().contains("jexinv"))
t.sendResponseHeaders(404, responseMsg.length());
else
t.sendResponseHeaders(200, responseMsg.length());
OutputStream os = t.getResponseBody();
os.write(responseMsg.getBytes());
os.close();
}
public boolean hasParam(String object){
if (object.indexOf("=")<40 && object.indexOf("=")>0 && object.split("=")[1].length() > 4)
return true;
else
return false;
}
public String getParamName(String object){
if (hasParam(object))
return object.substring(0, object.indexOf("=")+1).split("=")[0] + "=";
else
return "";
}
public String getObjectValue(String object){
if (hasParam(object)) {
String paramName = getParamName(object);
return object.split(paramName)[1];
}
else
return object;
}
public String deserialize(String object){
ObjectInputStream ois = null;
InputStream is = null;
GZIPInputStream gis = null;
// if payload is urlencoded
if (object.contains("%2B")) {
try {
object = URLDecoder.decode(object, "UTF-8");
} catch (UnsupportedEncodingException e) {
return "\nInvalid encoding. You should use URL Encode!\n";
}
}
try {
byte[] b64DecodedObj = new BASE64Decoder().decodeBuffer(object);
// This another implementation of Base64 is only for java >= 1.8
//byte[] b64DecodedObj = Base64.getDecoder().decode(object);
is = new ByteArrayInputStream(b64DecodedObj);
}catch (Exception e){
return "\nInvalid Base64!\n";
}
if (object.startsWith("H4sI")) {
try {
gis = new GZIPInputStream(is);
ois = new ObjectInputStream(gis);
} catch (IOException e) {
return "\nThe Stream not contains a Java Object!\n";
}
catch (Exception e) {
return "\nInvalid Gzip stream!\n";
}
}
else {
try {
ois = new ObjectInputStream(is);
}
catch (IOException e ){
return "\nThe Stream not contains a Java Object!\n";
}
catch (Exception e){
return e.toString()+"\n";
}
}
// Deserialization
try{
int number = (Integer) ois.readObject();
}
catch (ClassNotFoundException e) {
return "\nSerialized class not found in classpath\n";
}
catch (IOException e) {
return e.toString()+"\n";
}
catch (ClassCastException e){
e.printStackTrace();
} catch (IncompleteAnnotationException e){
e.printStackTrace();
System.out.println("\n[INFO] This payload not works in JRE >= 8u72. Try another version such as those\n" +
" which use TiedMapEntry + HashSet (by @matthiaskaiser).\n");
return "\nThis payload not works in JRE >= 8u72. Try another version such as those which use TiedMapEntry + HashSet (by @matthiaskaiser).\n";
}
catch (Exception e){
e.printStackTrace();
}
return "\nData deserialized!\n";
}
public String deserialize(InputStream is){
ObjectInputStream ois = null;
try{
ois = new ObjectInputStream(is);
}catch (EOFException e){
e.printStackTrace();
return "\nThe request body not contains a Stream!\n";
} catch (Exception e) {
return e.toString()+"\n";
}
try {
// This cast simulate what occurs in a real server
int number = (Integer) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
return "\nSerialized class not found in classpath\n";
} catch (ClassCastException e){
e.printStackTrace();
} catch (IncompleteAnnotationException e){
e.printStackTrace();
System.out.println("\n[INFO] This payload not works in JRE >= 8u72. Try another version such as those\n" +
" which use TiedMapEntry + HashSet (by @matthiaskaiser).\n");
return "\nThis payload not works in JRE >= 8u72. Try another version such as those which use TiedMapEntry + HashSet (by @matthiaskaiser).\n";
}
catch (Exception e){
e.printStackTrace();
}
return "\nData deserialized!\n";
}
public String deserializeXStream(String xml){
Class classXStream = null;
Class classDomDriver = null;
Class classHierarchicalStreamDriver = null;
//Class classJsonHierarchicalStreamDriver = null;
try {
classHierarchicalStreamDriver = Class.forName("com.thoughtworks.xstream.io.HierarchicalStreamDriver");
//classJsonHierarchicalStreamDriver = Class.forName("com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver");
classXStream = Class.forName("com.thoughtworks.xstream.XStream");
classDomDriver = Class.forName("com.thoughtworks.xstream.io.xml.DomDriver");
//Constructor ctrJsonDriver = classJsonHierarchicalStreamDriver.getDeclaredConstructor();
Constructor ctrDomDriver = classDomDriver.getDeclaredConstructor();
Constructor ctrXStream = classXStream.getDeclaredConstructor(classHierarchicalStreamDriver);
Object domDriverInstance = ctrDomDriver.newInstance();
//Object jsonDriverInstance = ctrJsonDriver.newInstance();
Object xstreamInstance = ctrXStream.newInstance(domDriverInstance);
//Desativado json...
//if (xml.startsWith("<"))
//xstreamInstance = ctrXStream.newInstance(domDriverInstance);
//else
// xstreamInstance = ctrXStream.newInstance(jsonDriverInstance);
Method m = xstreamInstance.getClass().getMethod("fromXML", String.class);
m.invoke(xstreamInstance, xml);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return "\nXStream lib not found in classpath. You must add \"xstream-1.4.6.jar\" in -cp param. Ex: \n" +
"java -cp .:xstream-1.4.6.jar:commons-collections-3.2.1.jar VulnerableServer\n\n";
} catch (Exception e){
e.printStackTrace();
return "\nError deserializing XML...\n";
}
return "\nXML deserialized!\n";
}
public byte[] decryptAES(String object, String aesKey){
byte[] iv = new byte[16];
String algorithmName = "AES";
byte[] cipherText = null;
byte[] plainTextWithIV = null;
byte[] plainText = null;
byte[] key = null;
try {
// first decode object from base64
cipherText = new BASE64Decoder().decodeBuffer(object);
// use the same harded code key from apache shino
key = new BASE64Decoder().decodeBuffer(aesKey);
} catch (Exception e) { e.printStackTrace(); return null; }
try {
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Key keySpec = new SecretKeySpec(key, algorithmName);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec,ivSpec);
// decrypt ciphertext and put the IV in the header
plainTextWithIV = cipher.doFinal(cipherText);
// remove the iv from header of plaintext in order to deserialize it later
plainText = new byte[plainTextWithIV.length - iv.length];
System.arraycopy(plainTextWithIV, iv.length, plainText, 0, plainText.length);
return plainText;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
}