什么是JNDI
JNDI 代表 Java 命名和目录接口(Java Naming and Directory Interface)。它是一组应用程序接口,为开发人员查找和访问各种资源提供统一的通用接口。它可用于定义用户、网络、机器和对象。和服务。
JNDI支持的服务主要包括:DNS、LDAP、CORBA、RMI等。
从简单的安全角度来看,JNDI是Java中的一组接口。它支持的服务中,最常用的是RMI和LDAP服务
RMI:远程方法调用注册表
LDAP:轻量级目录访问协议
通过这两个协议,目标服务器可以加载远程Class文件,攻击者可以通过构造Class文件来达到RCE的效果
以下包在jdk中提供了JDNI服务
javax.naming:主要用于命名操作。它包含命名服务的类和接口。该包定义了Context接口和InitialContext类;
www.sychzs.cnory:主要用于目录操作,它定义了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代码示例
导入javax.naming.InitialContext;
导入 javax.naming.NamingException;
公共类 jndi {
公共静态无效主(字符串[] args)抛出NamingException {
字符串uri =“rmi://127.0.0.1:1099/work”;
InitialContext initialContext = new InitialContext();//获取初始目录环境的引用
initialContext.lookup(uri);//获取指定的远程对象
}
}
如果获取到的远程对象是可控的,可以编写恶意的Class文件,让服务器加载该文件,达到命令执行的效果
比如
导入java.io.IOException;
公开课测试{
公共测试()抛出IOException {
Runtime.getRuntime().exec("notepad");//调用计算器
}
}
注入工具 JNDI-注入-利用
详细解释请参考
安全技术系列JNDI注入
Java 安全 JNDI 注入
如果想成功使用JNDI注入,需要观察当前服务器的JDK版本。不同版本号限制内容不同
Apache 的一个开源项目。通过使用Log4j,我们可以控制日志信息传输的目的地到控制台、文件、GUI组件,甚至socket服务器、NT事件记录器、UNIX Syslog守护进程等;我们还可以控制每条日志的输出格式;通过定义各个日志信息的级别,我们可以更详细地控制日志的生成过程。最有趣的是,这些可以通过配置文件灵活配置,而无需修改应用程序代码。
这里的漏洞原理是利用JNDI服务机制远程加载文件,实现命令执行
首先,log4j有四个级别的打印日志:debug、info、warn、error。无论采用哪种方式打印日志,在正常的日志处理过程中,都会检测到两个相邻的字符${。一旦遇到类似表达式结构的字符串就会触发替换机制。
一旦在日志字符串中检测到${},它将解析该字符串并尝试使用lookup()进行查询。因此,只要能够控制日志参数内容,就有机会利用该漏洞。
简单演示部分示例:
包com.example.log4jwebdemo;
导入 org.apache.logging.log4j.LogManager;
导入 org.apache.logging.log4j.Logger;
导入 javax.servlet.ServletException;
导入 javax.servlet.annotation.WebServlet;
导入 javax.servlet.http.HttpServlet;
导入 javax.servlet.http.HttpServletRequest;
导入 javax.servlet.http.HttpServletResponse;
导入java.io.IOException;
@WebServlet(“/log4j”)
公共类 Log4jServlet 扩展 HttpServlet {
//构造一个HTTP Web服务,并使用存在漏洞的Log4j版本来实现功能
私有静态最终 Logger log= LogManager.getLogger(Log4jServlet.class);
@覆盖
protected void doGet(HttpServletRequest req, HttpServletResponse resp) 抛出 ServletException, IOException {字符串代码 =req.getParameter("code");
log.error("{}",代码);
//1.开发源码中引用log4j等易受攻击的组件
//2.开发中使用的组件代码(触发漏洞的代码)
//3.可控变量传递Payload实施攻击
//4.该代码接受有效负载并需要进行 url 编码。
}
}
在前后端数据传输交互中,我们经常会遇到string、json、xml等格式的相互转换和解析。其中,json因其跨语言、跨前端、前端的优点,在开发中被频繁使用。基本上它是一种标准的数据交换格式。其界面简单易用,已广泛应用于缓存序列化、协议交互、Web输出等各种应用场景。 FastJson是阿里巴巴的开源库,用于解析和打包JSON格式的数据。
简单演示片段示例
首先定义User类
包com.Pengj;
//用于fastjson数据转换测试
公共类用户{
私有字符串名称;
私有整数年龄;
公共整数 getAge() {
返回年龄;
}
公共字符串 getName() {
返回名称;
}
公共无效setAge(整数年龄){
this.age = 年龄;
System.out.println(年龄);
}
公共无效setName(字符串名称){
www.sychzs.cn = 名称;
System.out.println(名称);
}
}
调用执行命令的文件
包com.Pengj;导入java.io.IOException;
公共课运行{
公共 Run() 抛出 IOException {
Runtime.getRuntime().exec("calc");
}
}
json数据序列化与反序列化
包com.Pengj;
导入com.alibaba.fastjson.JSON;
导入com.alibaba.fastjson.JSONObject;
导入 com.alibaba.fastjson.serializer.SerializerFeature;
//使用fastjson处理User类数据
公共类 FastjsonTest {
公共静态无效主(字符串[] args){
//实例化用户
用户 u = 新用户();
u.setAge(20);
u.setName("Pengj");
//json对象转换json数据转换结果:{"age":20,"name":"Pengj"}
字符串 jsonString = JSONObject.toJSONString(u);
System.out.println("这是json格式:"+jsonString);
//引入autotype函数后的对象转换为json数据。转换结果:{"@type":"com.Pengj.User","age":20,"name":"Pengj"}
字符串 jsonString1 = JSONObject.toJSONString(u, SerializerFeature.WriteClassName);
System.out.println(jsonString1);
//下面的 JSON -> 对象String test = "{\"@type\":\"www.sychzs.cn\",\"age\":20,\"name\":\"Pengj\"}";//修改包含类在信息后命名 json 数据
//反序列化json数据
JSONObject jsonObject = JSON.parseObject(测试);
System.out.println(jsonObject);
}
}
上面的代码片段中,使用JSONObject类中的toJSONString方法将对象转换为json数据,启用SerializerFeature.WriteClassName功能,输出带有@type标记的类名信息,完成对象转换为json数据过程。
使用 JSON.parseObject 方法将 json 数据反序列化为对象时,如果将类名信息修改为其他类,则程序在反序列化过程中会尝试将 @type 标记的类信息反序列化为对象。这样,@type标记的类就被加载了。但是@type标记的类可能是被恶意构造的。只需要合理构造一个JSON,并使用@type指定想要的攻击类库即可实现攻击。例如,可以通过将@type指定为包含lookup()方法的库来实现JNDI注入。
ParserConfig.getGlobalInstance().setAutoTypeSupport(false); //禁用自动输入
ParserConfig.getGlobalInstance().setSafeMode(true); //启用自动输入
以上内容仅供学习记录。如有错误或纰漏,敬请批评指正。感谢您的阅读。