freeBuf
主站

分类

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

特色

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

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

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

FreeBuf+小程序

FreeBuf+小程序

Z3专栏 | JNDI注入和工具分析
2021-12-28 12:12:34
所属地 辽宁省

介绍

之前学习过,jndi是个接口,通过这个接口,可以调用rmi或ldap。所以这次主要学习rmi与ldap的利用,通过分析jndi注入工具来学习,工具就选github有1.5k star的JNDI-Injection-Exploit

源码分析

源码结构如图

ServerStart为入口点,在这里设置了rmi、ldap、jetty的监听端口,待执行命令,并为它们仨分别创建线程并启动。

jettyServer

jettyServer使用jetty启动了一个web服务,用来下载ExecTemplateJDK7.class和ExecTemplateJDK8.class(就是一个简单的文件服务器)。但是它在返回ExecTemplateJDK7/8之前会插入命令。
如图

怎么将命令写入的?如图

这里用了ClassReader、ClassWriter、ClassVisitor三个类,它们都属于ASM 库,可以用来操作字节码
介绍如图

ClassVisitor 是一个抽象类,如图,以下方法交给子类实现

所以这段代码就是

创建一个reader读class

创建一个writer,传给了TransformClass,生成ClassVisitor对象

cr.accept(cv),从上面图片中的介绍可以知道,ClassReader接收一个ClassVisitor实现类,按顺序执行ClassVisitor中的方法

看一下TransformClass方法,如图,是一个ClassVisitor实现类,实现了visistMethod方法(上面图片红框有介绍)

visistMethod方法将命令传给了MethodVisitor。如图,看来就是在构造Rumtime.exec

所以,最后返回的字节码,只要被扫描类方法,就会命令执行。把生成的字节码反编译一下,如图。一番操作猛如虎,原来就是加了个静态代码块(动态生成代码太不容易了)

LDAPServer

ldapServer使用InMemoryDirectoryServer启动了一个ldap服务端。
如图,先创建个配置文件,然后设置配置文件,再用配置文件创建InMemoryDirectoryServer对象,再启动监听。

上面这些基本上是固定格式了,需要自定义的就是OperationInterceptor类。OperationInterceptor类是拦截器,可以拦截ldap通信,继承于InMemoryOperationInterceptor。
如图,ldap查询结果会传给processSearchResult处理,在sendResult方法中,获取到的javaFactory,就是template目录下的class文件,最后将这些信息写入到了result中。

注意这里的javaCodebase,Codebase是用来指定类的装载路径,如果设置为远程地址,则会从远程加载类。

这一步就是收到请求后,根据请求的名字(就是processSearchResult中的base)从Mapper里找出对应class文件,将路径写入Entry中。
客户端收到的返回数据如图,是一个Reference对象。

由于jdk1.8u191之后,不能通过这种方式jndi注入了,所以下面把jdk切换为1.7,调试下命令执行的原因
首先是InitialContext的lookup,创建了Url的Context对象(所有的lookup都要通过Context对象调用,Ctx是Context缩写),继续lookup。

到了ldapUrlContext的lookup,如图,调用了父类lookup。

如图,调用ldapCtx的lookup

如图,继续调用了p_lookup、c_lookup


最终在ldapCtx的c_lookup方法,获取到了查询结果,最终结果是一个Reference对象

后面又进入了DirectoryManager的getObjectInstance方法。refInfo就是上一步查询的Reference对象
然后调用了父类NamingManager的getObjectFactoryFromReference方法。
如图

而在这里,先尝试在本地加载ExecTemplateJDK7,获取不到,又从网络位置获取,第二个红框执行完后,弹出计算器。

从第三个红框可以看出,在加载类成功后,将对象转为ObjectFactiry类型,调用了NewInstance方法,将对象实例化。

再回头看一下getObjectInstance方法,看第二个红框,调用了恶意类的getObjectInstance方法。

所以,恶意类,不仅可以使用静态代码块命令执行,也可以继承ObjectFactory类,重写getObjectInstance方法(后面有例子)。

问题出来了,之前学习加载类时,不是说loadClass时不会对类初始化吗,那为什么loadClass后就会执行命令?
看一下的helper(VersionHelper12类型)的loadClass。

如图,可以看见VersionHelper12类的loadClass是通过Class.forName实现的,所以会初始化类。

总结一下,首先ldap查询结果是一个Reference对象,然后就会视同DirectoryManager的getObjectInstance方法,然后调用NamingManager的getObjectFactoryFromReference方法,导致命令执行。

RMIServer

之前在介绍RMI时已经写过RMIServer了,但是这个工具里,不是通过RMI相关类创建的RMIServer,,是直接通过socket,手工解析的协议。所以这部分代码很多,先想一下如何构造恶意rmi服务。
正常rmi服务,在注册中心注册后,在注册中心保留存根,在调用lookup查询类的时候,获得的是存根对象。
所以,我们要做的是,将这个存根对象改为Referencr对象(在学习ldap时已经知道,如果lookup获取的是Reference对象,就会远程加载并初始化指定类),这样在lookup时,就会导致命令执行。

搭建测试

网上找了份代码,学习下,如图搭建rmi服务端。

恶意类evil如图,将它编译为class,再用python开启一个文件服务器。

客户端如图,运行就打开了计算器。

看来不手工解析rmi的流量协议也可以构造恶意rmi服务端,猜测这个工具这么做事想,更清晰的获取到rmi调用信息。如图,这个是java.rmi.registry.LocateRegistry类不能实现的。

原理分析

重点看evil类。之前ldap部分 分析过,恶意类被加载后会被转为ObjectFactory类型,并调用getObjectInstance方法(本例就用这种方式命令执行)。
如图

下面分析下命令执行过程,调用栈如图。

在decodeObject方法中,获取到了Reference对象,就是var3,然后调用NamingManager.getObjectInstance实例化对象

这里很熟悉,回想一下ldap的命令执行过程:DirectoryManager的getObjectInstance方法 --> NamingManager的getObjectFactoryFromReference方法导致命令执行

看下NamingManager.getObjectInstance方法,如果调用了NamingManager的getObjectFactoryFromReference方法就会导致命令执行
如图,果然

后面的就不分析了,和上面分析ldap的一样

总结

ldap和rmi利用方式类似
rmi利用链是lookup --> ...... --> RegisterContext.decodeObjecct --> NamingManger.getObjectInstance --> NamingManger.getObjectFactoryFromReference
ldap利用链是lookup --> ...... --> DirectoryManager.getObjectInstance -> NamingManger.getObjectFactoryFromReference

可见问题根源就在NamingManger.getObjectFactoryFromReference方法

它们的利用方式都是在查询时,收到一个Reference对象,Reference对象的codebase指向恶意类,客户端加载恶意类,导致命令执行

ldap和rmi在Reference对象处理时,都会使用Class.forName方法加载Reference对象指向的远程.class文件,,而且会将它强转为ObjectFactory类型,并调用getObjectInstance方法

所以构造恶意.class文件时,可以使用静态代码块,也可以继承ObjectFactory,重写getObjectInstance方法

ldap服务搭建,上面演示过
rmi服务搭建,可以直接调用java原生的rmi相关的类搭建,很方便,也可以学习本例工具中的方式,手工解析rmi流量(这种方式更灵活)

不足

可以通过ldap或rmi执行任意代码,但不足之处就是,没有回显。实战中可以写内存马、反弹shell、上线cs等操作,但如果命令执行或代码出问题,但又没回显,会很难排查问题。后面会继续分析,如何写一个带回显的jndi注入工具

这两种方式都属于JNDI Reference 利用,对jdk版本有限制

下面是绕过高版本限制的几种方式,第十五篇 JNDI注入Plus(JNDI注入的绕过方式总结) 暂时搁置
大概有以下几种,通过这些关键字网上就能找到很详细的分析

  1. 使用BeanFactory

  2. ldap反序列化触发本地gadget

  3. 利用JRMP触发本地gadget

# java漏洞 # java # java反序列化 # Java代码审计 # JAVA安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录