RSS Feed
更好更安全的互联网
  • CVE-2020-1362 漏洞分析

    2020-11-03

    作者:bybye@知道创宇404实验室
    时间:2020年7月24日

    漏洞背景

    WalletService 服务是 windows 上用来持有钱包客户端所使用的对象的一个服务,只存在 windows 10 中。

    CVE-2020-1362 是 WalletService 在处理 CustomProperty 对象的过程中出现了越界读写,此漏洞可以导致攻击者获得管理员权限,漏洞评级为高危。

    微软在 2020 年 7 月更新对漏洞发布补丁。

    环境搭建

    1. 复现环境:windows 10 专业版 1909 (内部版本号 18363.815)
    2. 设置 WalletService 服务启动类型为自动
    3. 调试环境:windbg -psn WalletService 即可。

    漏洞原理与分析

    漏洞点是设置 CustomProperty 对象的 Group 的 get 方法和 set 方法没有检查边界。

    1. get 方法的 a2 参数没有检查边界导致可以泄露堆上的一些地址。
    2. set 方法的 a2 参数没有检查边界,可以覆盖到对象的虚表指针,从而控制程序流。

    漏洞利用过程

    创建 CustomProperty 对象

    WalletService 服务由 WalletService.dll 提供,WalletService.dll 实际上是一个动态链接库形式的 Com 组件,由 svchost.exe 加载。我们可以在自己写的程序(下面称为客户端)中使用 CoCreateInstance() 或者 CoGetClassObject() 等函数来创建对象,通过调用获得的对象的类方法来使用服务提供的功能。

    如何创建出漏洞函数对应的对象呢?最简单的办法是下载 msdn 的符号表,然后看函数名。

    我们想要创建出 CustomProperty 对象,ida 搜索一下,发现有两个创建该对象的函数:Wallet::WalletItem::CreateCustomProperty() 和 Wallet::WalletXItem::CreateCustomProperty()。

    img

    所以我们创建一个 CustomProperty 需要一个 WalletXItem 对象或者 WalletItem 对象,那么使用哪个呢?继续用 ida 搜索 CreateWalletItem 或者 CreateWalletXItem,会发现只有 CreateWalletItem。

    img

    那到这里我们需要一个 WalletX 对象,继续用 ida 搜索会发现找不到 CreateWalletX,但是如果搜索 WalletX,会发现有个 WalletXFactory::CreateInstance(),如果有过 Com 组件开发经验的同学就会知道,这个是个工厂类创建接口类的函数,上面提到的 CoCreateInstance() 函数会使 WalletService 调用这个函数来创建出接口类返回给客户端。

    img

    那么如何调用 WalletXFactory::CreateInstance() 并创建出 WalletX 对象呢?我们需要在客户端使用 CoCreateInstance() 。

    1. 首先,我们需要 WalletXFactory 的 CLSID,可以使用 OLEViewDotNet 这个工具查看。
    2. 其次,我们需要一个 WalletX 的 IID,这个可以用 ida 直接看 WalletXFactory::CreateInstance() 这个函数。

    有了 WalletXFactory 的 CLSID 和 WalletX 的 IID,然后在客户端调用 CoCreateInstance(),WalletService 就会调用 CLSID 对应的工厂类 WalletXFactory 的 CreateInstance(), 创建出 IID 对应的 WalletX 对象,并返回对象给客户端。

    然后按照上面的分析,使用 WalletX::CreateWalletItem() 创建出 WalletItem 对象,然后使用 WalletItem::CreateCustomProperty() 创建出 CustomProperty 对象。

    对于上面的步骤有疑问的同学可以去学一学 Com 组件开发,尤其是进程外组件开发。

    伪造虚表,覆盖附表指针

    由于同一个动态库,在不同的进程,它的加载基址也是一样的,我们可以知道所有dll里面的函数的地址,所以可以获得伪造的虚表里面的函数地址。

    那么把虚表放哪里呢?直接想到的是放堆上。

    但如果我们继续分析,会发现,CustomProperty 类里面有一个 string 对象,并且可以使用 CustomProperty::SetLabel() 对 string 类进行修改,所以,我们可以通过修改 string 类里面的 beg 指针 和 end 指针,然后调用 CustomProperty::SetLabel() 做到任意地址写。

    img

    有了任意地址写,我们选择把虚表放在 WalletService.dll 的 .data 节区,以避免放在堆上可能破坏堆上的数据导致程序崩溃。

    控制程序流到 LoadLibrary 函数

    使用伪造 vtable 并覆盖虚表指针的办法,我们可以通过调用虚函数控制 WalletService 的程序流到任意地址了。

    那么怎么提权呢?在 windows 服务提权中,通常的办法是把程序流控制到可以执行 LoadLibrary() 等函数来加载一个由我们自己编写的动态链接库,因为在加载 dll 的时候会执行 dll 里面的 DllMain(),这个方法是最强大的也是最实用的。

    这里使用漏洞提交者的方法,把虚表的某个地址覆盖成 dxgi.dll 里面的 ATL::CComObject\::`vector deleting destructor(),因为这个函数调用的 LoadLibraryExW() 会使用一个全局变量作为想要加载的 dll 的路径。

    img

    我们可以通过上面的 SetLabel() 进行任意地址写,修改上图的全局变量 Src,使其指向我们自己实现的动态链接库的路径,然后调用对应的虚表函数,使程序流执行到 LoadLibrarExW() 即可。

    实现一个动态链接库

    在 DllMain() 里面写上我们希望以高权限执行代码,然后调用虚表里面对应的函数是 WalletService 的程序流运行到 LoadLibraryEx() 即可。

    注意,因为 windows 服务运行在后台,所以需要在 DllMain() 里面使用命名管道或者 socket 等技术来进行回显或者交互,其次由于执行的是 LoadLibraryExW(),所以这里的 dll 路径要使用宽字符。

    其它

    在控制虚表函数程序流到 LoadLibraryExW() 时,需要绕过下面两个 check。

    第一个是需要设置 this+0x80 这个地址的值,使得下面的 and 操作为 true。

    image-20200724111317787

    第二个是要调整 qword_C5E88 和 qword_C5E80 是下面的变量 v4 指向具有写权限的内存。

    image-20200724111819875

    漏洞利用结果

    可以获得管理员权限

    image-20200724121314068

    补丁前后对比

    可以看到,打了补丁之后,get 方法和 set 方法都对 a2 参数添加了边界检测。

    img
    img

    参考链接

    [1] PoC链接
    [2] 微软更新公告
    [3] nvd漏洞评级


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1276/

    作者:江 | Categories:漏洞分析 | Tags:
  • CVE-2019-5786 漏洞原理分析及利用

    2020-11-02

    作者:Kerne7@知道创宇404实验室
    时间:2020年6月29日

    从补丁发现漏洞本质

    首先根据谷歌博客收集相关CVE-2019-5786漏洞的资料:High CVE-2019-5786: Use-after-free in FileReader,得知是FileReader上的UAF漏洞。

    然后查看https://github.com/chromium/chromium/commit/ba9748e78ec7e9c0d594e7edf7b2c07ea2a90449?diff=split上的补丁

    对比补丁可以看到DOMArrayBuffer* result = DOMArrayBuffer::Create(raw_data_->ToArrayBuffer()),操作放到了判断finished_loading后面,返回值也从result变成了array_buffer_result_(result的拷贝)。猜测可能是这个返回值导致的问题。

    分析代码

    raw_data_->ToArrayBuffer()可能会返回内部buffer的拷贝,或者是返回一个指向其偏移buffer的指针。

    根据MDN中FileReader.readAsArrayBuffer()的描述:

    FileReader 接口提供的 readAsArrayBuffer() 方法用于启动读取指定的 Blob 或 File 内容。当读取操作完成时,readyState 变成 DONE(已完成),并触发 loadend 事件,同时 result 属性中将包含一个 ArrayBuffer 对象以表示所读取文件的数据。

    FileReader.onprogress事件在处理progress时被触发,当数据过大的时候,onprogress事件会被多次触发。

    所以在调用FileReader.result属性的时候,返回的是WTF::ArrayBufferBuilder创建的WTF::ArrayBuffer对象的指针,Blob未被读取完时,指向一个WTF::ArrayBuffer副本,在已经读取完的时候返回WTF::ArrayBufferBuilder创建的WTF::ArrayBuffer自身。

    那么在标志finished_loading被置为ture的时候可能已经加载完毕,所以onprogress和onloaded事件中返回的result就可能是同一个result。通过分配给一个worker来释放其中一个result指针就可以使另一个为悬挂指针,从而导致UAF漏洞。

    漏洞利用思路

    我选择的32位win7环境的Chrome72.0.3626.81版本,可以通过申请1GB的ArrayBuffer,使Chrome释放512MB保留内存,通过异常处理使OOM不会导致crash,然后在这512MB的内存上分配空间。

    调用FileReader.readAsArrayBuffer,将触发多个onprogress事件,如果事件的时间安排正确,则最后两个事件可以返回同一个ArrayBuffer。通过释放其中一个指针来释放ArrayBuffer那块内存,后面可以使用另一个悬挂指针来引用这块内存。然后通过将做好标记的JavaScript对象(散布在TypedArrays中)喷洒到堆中来填充释放的区域。

    通过悬挂的指针查找做好的标记。通过将任意对象的地址设置为找到的对象的属性,然后通过悬挂指针读取属性值,可以泄漏任意对象的地址。破坏喷涂的TypedArray的后备存储,并使用它来实现对地址空间的任意读写访问。

    之后可以加载WebAssembly模块会将64KiB的可读写执行存储区域映射到地址空间,这样的好处是可以免去绕过DEP或使用ROP链就可以执行shellcode。

    使用任意读取/写入原语遍历WebAssembly模块中导出的函数的JSFunction对象层次结构,以找到可读写可执行区域的地址。将WebAssembly函数的代码替换为shellcode,然后通过调用该函数来执行它。

    通过浏览器访问网页,就会导致执行任意代码

    帮助

    本人在初次调试浏览器的时候遇到了很多问题,在这里列举出一些问题来减少大家走的弯路。

    因为chrome是多进程模式,所以在调试的时候会有多个chrome进程,对于刚开始做浏览器漏洞那话会很迷茫不知道该调试那个进程或者怎么调试,可以通过chrome自带的任务管理器来帮我们锁定要附加调试的那个进程ID。

    这里新的标签页的进程ID就是我们在后面要附加的PID。

    Chrome调试的时候需要符号,这是google提供的符号服务器(加载符号的时候需要翻墙)。在windbg中,您可以使用以下命令将其添加到符号服务器搜索路径,其中c:\Symbols是本地缓存目录:

    因为Chrome的沙箱机制,在调试的过程中需要关闭沙箱才可以执行任意代码。可以在快捷方式中添加no-sandbox来关闭沙箱。

    由于这个漏洞机制的原因,可能不是每次都能执行成功,但是我们可以通过多次加载脚本的方式来达到稳定利用的目的。

    在github上有chromuim的源码,在分析源码的时候推荐使用sourcegraph这个插件,能够查看变量的定义和引用等。

    在需要特定版本Chrome的时候可以自己去build源码或者去网络上寻找chrome历代发行版收集的网站。

    在看exp和自己编写的时候需要注意v8引擎的指针问题,v8做了指针压缩,所以在内存中存访的指针可能和实际数据位置地址有出入。

    参考链接:

    1. https://www.anquanke.com/post/id/194351
    2. https://blog.exodusintel.com/2019/01/22/exploiting-the-magellan-bug-on-64-bit-chrome-desktop/
    3. https://blog.exodusintel.com/2019/03/20/cve-2019-5786-analysis-and-exploitation/

    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1257/

    作者:江 | Categories:漏洞分析 | Tags:
  • Nexus Repository Manager 2.x 命令注入漏洞 (CVE-2019-5475) 两次绕过

    2020-11-02

    作者: Badcode and Longofo@知道创宇404实验室 
    时间: 2020年2月9日 
    English Version:https://paper.seebug.org/1261/

    前言

    2019年9月初我们应急了Nexus Repository Manager 2.x 命令注入漏洞(CVE-2019-5475),其大致的原因和复现步骤在 hackerone 上公布了,在应急完这个漏洞之后,我们分析该漏洞的修复补丁,发现修复不完全,仍然可以绕过,本篇文章记录该漏洞的两次绕过。虽然早发布了两次的修复版本,由于官方第二次更新公告太慢https://support.sonatype.com/hc/en-us/articles/360033490774,所以现在才发。

    几次更新时间线:

    • CVE-2019-5475(2019-08-09)
    • 第一次绕过,CVE-2019-15588(2019-10-28)
    • 第二次绕过,未分配CVE,更新了公告影响版本(2020-3-25)

    注:原始漏洞分析、第一次绕过分析、第二次绕过分析部分主要由Badcode师傅编写,第二次绕过分析+、最新版本分析主要由Longofo添加。

    原始漏洞分析

    利用条件

    • 需管理员权限(默认认证:admin/admin123)

    漏洞分析

    以下分析的代码基于 2.14.9-01 版本。

    漏洞点是出现在 Yum Repository 插件中,当配置 Yum 的 createrepo或者mergerepo

    代码层面会跳到 YumCapabilityactivationCondition方法中。

    在上面Path of "createrepo"中设置的值会通过getConfig().getCreaterepoPath()获取到,获取到该值之后,调用this.validate()方法

    传进来的path是用户可控的,之后将path拼接--version之后传递给commandLineExecutor.exec()方法,看起来像是执行命令的方法,而事实也是如此。跟进CommandLineExecutor类的exec方法

    在执行命令前先对命令解析,CommandLine.parse(),会以空格作为分隔,获取可执行文件及参数。

    最终是调用了Runtime.getRuntime().exec()执行了命令。

    例如,用户传入的 command 是cmd.exe /c whoami,最后到getRuntime().exec()方法就是Runtime.getRuntime().exec({"cmd.exe","/c","whoami"})

    所以漏洞的原理也很简单,就是在createrepo或者mergerepo路径设置的时候,该路径可以由用户指定,中途拼接了--version字符串,最终到了getRuntime.exec()执行了命令。

    漏洞复现

    Path of "createrepo"里面传入 payload。

    Status栏可以看到执行的结果

    第一次绕过分析

    第一次补丁分析

    官方补丁改了几个地方,关键点在这里

    常规做法,在执行命令前对命令进行过滤。新增加了一个getCleanCommand()方法,对命令进行过滤。

    allowedExecutables是一个 HashSet,里面只有两个值,createrepomergerepo。先判断用户传入的command是否在allowedExecutables里面,如果在,直接拼接params--version直接返回。接着对用户传入的command进行路径判断,如果是以nexus的工作目录(applicationDirectories.getWorkDirectory().getAbsolutePath())开头的,直接返回 null。继续判断,如果文件名不在allowedExecutables则返回 null,也就是这条命令需要 以/createrepo或者/mergerepo结尾。都通过判断之后,文件的绝对路径拼接--version 返回。

    第一次补丁绕过

    说实话,看到这个补丁的第一眼,我就觉得大概率可以绕。

    传入的命令满足两个条件即可,不以nexus的工作目录开头,并且以/createrepo或者/mergerepo结尾即可。

    看到补丁中的getCleanCommand()方法,new File(command)是关键,new File()是通过将给定的路径名字符串转换为抽象路径名来创建新的File实例。 值得注意的是,这里面路径字符串是可以使用空格的,也就是

    这种是合法的,并且调用file.getName()取到的值是shadow。结合这个特性,就可以绕过补丁里面的判断。

    运行结果

    可以看到,file.getName()的值正是createrepo,满足判断。

    第一次绕过测试

    测试环境
    • 2.14.14-01 版本
    • Linux
    测试步骤

    Path of "createrepo"里面传入 payload。

    Status栏查看执行的结果

    可以看到,成功绕过了补丁。

    在 Windows 环境下面就麻烦点了,没有办法使用cmd.exe /c whoami这种形式执行命令了,因为cmd.exe /c whoami经过new File() 之后变成了cmd.exe \c whoami,后面是执行不了的。可以直接执行exe,注意后面是还会拼接--version的,所以很多命令是执行不了的,但是还是有办法利用能执行任意exe这点来做后续的攻击的。

    第二次绕过分析

    第二次补丁分析

    在我提交上述绕过方式后,官方修复了这种绕过方式,看下官方的补丁

    getCleanCommand() 方法中增加了一个file.exists()判断文件是否存在。之前的/bin/bash -c whoami /createrepo这种形式的肯定就不行了,因为这个文件并不存在。所以现在又多了一个判断,难度又加大了。难道就没有办法绕过了?不是的,还是可以绕过的。

    第二次补丁绕过

    现在传入的命令要满足三个条件了

    • 不以nexus的工作目录开头
    • /createrepo或者/mergerepo结尾
    • 并且这createrepo或者mergerepo这个文件存在

    看到file.exists()我就想起了 php 中的 file_exists(),以前搞 php 的时候也遇到过这种判断。有个系统特性,在 Windows 环境下,目录跳转是允许跳转不存在的目录的,而在Linux下面是不能跳转不存在目录的。

    测试一下

    Linux

    可以看到,file.exists()返回了 false

    Windows

    file.exists()返回了 true

    上面我们说了new File(pathname),pathname 是允许带空格的。在利用上面WIndows环境下的特性,把cmd设置成 C:\\Windows\\System32\\calc.exe \\..\\..\\win.ini

    经过parse() 方法,最终到getRuntime.exec({"C:\\Windows\\System32\\calc.exe","\\..\\..\\win.ini"}),这样就能执行calc了。

    在上面这个测试win.ini是确实存在的文件,回到补丁上面,需要判断createrepo或者mergerepo存在。首先从功能上来说,createrepo 命令用于创建 yum 源(软件仓库),即为存放于本地特定位置的众多rpm包建立索引,描述各包所需依赖信息,并形成元数据。也就是这个createrepo在Windows下不太可能存在。如果这个不存在的话是没有办法经过判断的。既然服务器内不存在createrepo,那就想办法创建一个,我首先试的是找个上传点,尝试上传一个createrepo,但是没找到上传之后名字还能保持不变的点。在Artifacts Upload处上传之后,都变成Artifact-Version.Packaging这种形式的名字了,Artifact-Version.Packaging这个是不满足第二个判断的,得以createrepo结尾。

    一开始看到file.exists()就走进了思维定势,以为是判断文件存在的,但是看了官方的文档,发现是判断文件或者目录存在的。。这点也就是这个漏洞形成的第二个关键点,我不能创建文件,但是可以创建文件夹啊。在Artifacts Upload上传Artifacts 的时候,可以通过GAV Parameters来定义。

    当 Group设置为test123Artifact设置为test123Version设置成1,当上传Artifacts的时候,是会在服务器中创建对应的目录的。对应的结构如下

    如果我们将Group设置为createrepo,那么就会创建对应的createrepo目录。

    结合两个特性来测试一下

    可以看到,file.exists()返回了true,file.getName()返回了createrepo,都符合判断了。

    最后到getRuntime()里面大概就是getRuntime.exec({"C:\Windows\System32\notepad.exe","\..\..\..\nexus\sonatype-work\nexus\storage\thirdparty\createrepo","--version"})

    是可以成功执行notepad.exe的。(calc.exe演示看不到进程哈,所以换成Notepad.exe)

    第二次绕过测试

    测试环境
    • 2.14.15-01 版本
    • Windows
    测试步骤

    Path of "createrepo"里面传入 payload。

    查看进程,notepad.exe启动了

    可以看到,成功绕过了补丁。

    第二次绕过分析+

    经过Badcode师傅第二次绕过分析,可以看到能成功在Windows系统执行命令了。但是有一个很大的限制:

    1. nexus需要安装在系统盘
    2. 一些带参数的命令无法使用

    在上面说到的Artifacts Upload上传处是可以上传任意文件的,并且上传后的文件名都是通过自定义的参数拼接得到,所以都能猜到。那么可以上传自己编写的任意exe文件了。

    第二次绕过分析+测试

    测试环境
    • 2.14.15-01 版本
    • Windows
    测试步骤

    导航到Views/Repositories->Repositories->3rd party->Configuration,我们可以看到默认本地存储位置的绝对路径(之后上传的内容也在这个目录下):

    导航到Views/Repositories->Repositories->3rd party->Artifact Upload,我们可以上传恶意的exe文件:

    该exe文件将被重命名为createrepo-1.exe(自定义的参数拼接的):

    同样在Path of "createrepo"里面传入 payload(这时需要注意前面部分这时是以nexus安装目录开头的,这在补丁中会判断,所以这里可以在最顶层加..\或者弄个虚假层aaa\..\等):

    可以看到createrepo-1.exe已经执行了:

    最新版本分析

    最新版本补丁分析

    第二次补丁绕过之后,官方又进行了修复,官方补丁主要如下:

    删除了之前的修复方式,增加了YumCapabilityUpdateValidator类,在validate中将获取的值与properties中设置的值使用equals进行绝对相等验证。这个值要修改只能通过sonatype-work/nexus/conf/capabilities.xml

    最新版本验证

    前端直接禁止修改了,通过抓包修改测试:

    YumCapabilityUpdateValidator.validate断到:

    可以看到这种修复方式无法再绕过了,除非有文件覆盖的地方覆盖配置文件,例如解压覆盖那种方式,不过没找到。

    不过Artifacts Upload那里可以上传任意文件的地方依然还在,如果其他地方再出现上面的情况依然可以利用到。


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1260/

    作者:江 | Categories:漏洞分析 | Tags:
  • From Deserialization to Type Confusion Vulnerability —— A Real Use in Ecshop Lastest

    2020-11-02

    Author: LoRexxar'@Knownsec 404 Team
    Date: March 31,2020.
    Chinese Version: https://paper.seebug.org/1267

    This article was originally completed on March 31, 2020. Because it involves 0day utilization, it was reported to the vendor on March 31, 2020, and released after the 90-day vulnerability disclosure period.


    A few days ago, I accidentally saw a vulnerability report submitted on Hackerone. In this vulnerability, the vulnerability discoverer proposed a very interesting use. The author makes use of a type confusion vulnerability of GMP and cooperates with the corresponding utilization chain to construct a code execution of mybb. Here we take a look at this vulnerability.

    https://hackerone.com/reports/198734

    Some details of the following article, thanks to the vulnerability discoverer @taoguangchen for his help.

    GMP type confusion vulnerability

    -https://bugs.php.net/bug.php?id=70513

    Vulnerability conditions

    • php 5.6.x
    • Deserialization entry point
    • The trigger point that can trigger __wakeup (below php < 5.6.11, you can use the built-in class)

    Vulnerability details

    gmp.c

    zend_object_handlers.c

    From the snippet in gmp.c, we can roughly understand the original words of vulnerability discoverer taoguangchen.

    Magic methods such as __wakeup can cause ZVAL to be modified in memory. Therefore, an attacker can convert **object to an integer or bool type ZVAL, then we can access any object stored in the object storage through Z_OBJ_P, which means that any object can be overwritten through zend_hash_copy attributes, which may cause a lot of problems, and can also cause security problems in certain scenarios.

    Perhaps it is impossible to understand the above words only with code snippets, but we can take a look at the actual test.

    First let's look at a test code

    In the code, I show the environment in many different situations.

    Let's see what the result is?

    I successfully modified the first declared object.

    But what happens if I change the deserialized class to b?

    Obviously, it will not affect other class variables

    If we add a __Wakeup function to class b, then it will produce the same effect.

    But if we set the variable in the wakeup magic method to 2

    The results returned can be seen, we successfully modified the second declared object.

    But if we change ryat to 4, then the page will return 500 directly, because we modified the unallocated object space.

    After completing the previous experiments, we can simplify the conditions for exploiting the vulnerability.

    If we have a controllable deserialization entry, the target backend PHP has a GMP plugin installed (this plugin is not installed by default in the original PHP, but some packaging environments will bring it), If we find a controllable __wakeup magic method, we can modify the object properties declared before deserialization and cooperate with the scene to produce actual security problems.

    If the target php version is in 5.6 <= 5.6.11, we can directly use the built-in magic method to trigger this vulnerability.

    Real world case

    After discussing the GMP type confusion vulnerability, we must discuss how this vulnerability is used in real scenarios.

    Taoguang Chen, the discoverer of the vulnerability, submitted a related exploit in mybb.

    https://hackerone.com/reports/198734

    Here we do not continue to discuss this vulnerability, but discuss the use in ecshop from scratch.

    Vulnerable Environment

    • ecshop 4.0.7
    • php 5.6.9

    Deserialization Vulnerability

    First we need to find an entry point for deserialization. Here we can search for unserializeglobally. Looking at each of them, we can find two controllable deserialization entries.

    One of them is search.php line 45

    This is an entrance to the front desk, but unfortunately, the initialization file is introduced after deserialization, which also makes us unable to find a target that can override the properties of class variables, and there is no way to use it further.

    Another one is admin/order.php line 229

    This function of the form page in the background meets our requirements. Not only can it be controlled, but also urlencode can be used to bypass ecshop's filtering of global variables.

    In this way, we have found a controllable and suitable deserialization entry point.

    Find the appropriate class attribute utilization chain

    Before looking for a utilization chain, we can use

    To determine the class that has been declared when deserializing.

    In my local environment, I found 13 classes in addition to the PHP built-in classes

    You can also see from the code that multiple library files are imported in the file header

    Here we mainly focus on init.php, because most common classes of ecshop are declared in this file.

    When looking at the class variables one by one, we can keenly see a special variable. Due to the special background structure of ecshop, most of the page content is compiled from templates, and this template class happens to be in init.php. statement

    Back in order.php, we are looking for methods related to $smarty, it is not difficult to find, mainly concentrated in two methods

    Here we mainly focus on the display method.

    A rough look at the logic of the display method:

    The more important code will be defined in the function make_compiled

    When the process reaches this point, we need to find out what our goal is first?

    Re-examining the code of cls_template.php, we can find that there are only a few functions involved in the code execution.

    get_para is only called in select, but there is no place to trigger the select.

    Then pop_vars

    We can control the $this->_temp_key variable just in conjunction with GMP, so as long as we can call this method anywhere in the above process, we can construct a code execution with variable coverage.

    Looking back at the code flow just now, we found such code from the compiled PHP file

    order_info.htm.php

    After traversing the form, pop_vars will be triggered.

    In this way, as long as we control the _temp_key attribute that overrides the cls_templatevariable, we can complete a getshell

    Final use effect

    Timeline

    • 2020.03.31 Find the vulnerability.
    • 2020.03.31 Report the vulnerability to the manufacturer, CVE, CNVD, etc.
    • 2020.07.08 Announce details after 90-day vulnerability disclosure period.

    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1268/

    作者:江 | Categories:漏洞分析 | Tags:
  • Fastjson 反序列化漏洞史

    2020-11-02

    作者:Longofo@知道创宇404实验室 
    时间:2020年4月27日 
    英文版本:https://paper.seebug.org/1193/

    Fastjson没有cve编号,不太好查找时间线,一开始也不知道咋写,不过还是慢慢写出点东西,幸好fastjson开源以及有师傅们的一路辛勤记录。文中将给出与Fastjson漏洞相关的比较关键的更新以及漏洞时间线,会对一些比较经典的漏洞进行测试及修复说明,给出一些探测payload,rce payload。

    Fastjson解析流程

    可以参考下@Lucifaer师傅写的fastjson流程分析,这里不写了,再写篇幅就占用很大了。文中提到fastjson有使用ASM生成的字节码,由于实际使用中很多类都不是原生类,fastjson序列化/反序列化大多数类时都会用ASM处理,如果好奇想查看生成的字节码,可以用idea动态调试时保存字节文件:

    插入的代码为:

    生成的类:

    但是这个类并不能用于调试,因为fastjson中用ASM生成的代码没有linenumber、trace等用于调试的信息,所以不能调试。不过通过在Expression那个窗口重写部分代码,生成可用于调式的bytecode应该也是可行的(我没有测试,如果有时间和兴趣,可以看下ASM怎么生成可用于调试的字节码)。

    Fastjson 样例测试

    首先用多个版本测试下面这个例子:

    说明

    • 这里的@type就是对应常说的autotype功能,简单理解为fastjson会自动将json的key:value值映射到@type对应的类中
    • 样例User类的几个方法都是比较普通的方法,命名、返回值也都是常规的符合bean要求的写法,所以下面的样例测试有的特殊调用不会覆盖到,但是在漏洞分析中,可以看到一些特殊的情况
    • parse用了四种写法,四种写法都能造成危害(不过实际到底能不能利用,还得看版本和用户是否打开了某些配置开关,具体往后看)
    • 样例测试都使用jdk8u102,代码都是拉的源码测,主要是用样例说明autotype的默认开启、checkautotype的出现、以及黑白名白名单从哪个版本开始出现的过程以及增强手段
    1.1.157测试

    这应该是最原始的版本了(tag最早是这个),结果:

    下面对每个结果做一个简单的说明

    JSON.parse(serializedStr)

    在指定了@type的情况下,自动调用了User类默认构造器,User类对应的setter方法(setAge,setName),最终结果是User类的一个实例,不过值得注意的是public sex被成功赋值了,private address没有成功赋值,不过在1.2.22, 1.1.54.android之后,增加了一个SupportNonPublicField特性,如果使用了这个特性,那么private address就算没有setter、getter也能成功赋值,这个特性也与后面的一个漏洞有关。注意默认构造方法、setter方法调用顺序,默认构造器在前,此时属性值还没有被赋值,所以即使默认构造器中存在危险方法,但是危害值还没有被传入,所以默认构造器按理来说不会成为漏洞利用方法,不过对于内部类那种,外部类先初始化了自己的某些属性值,但是内部类默认构造器使用了父类的属性的某些值,依然可能造成危害。

    可以看出,从最原始的版本就开始有autotype功能了,并且autotype默认开启。同时ParserConfig类中还没有黑名单。

    JSON.parseObject(serializedStr)

    在指定了@type的情况下,自动调用了User类默认构造器,User类对应的setter方法(setAge,setName)以及对应的getter方法(getAge,getName),最终结果是一个字符串。这里还多调用了getter(注意bool类型的是is开头的)方法,是因为parseObject在没有其他参数时,调用了JSON.toJSON(obj),后续会通过gettter方法获取obj属性值:

    JSON.parseObject(serializedStr, Object.class)

    在指定了@type的情况下,这种写法和第一种JSON.parse(serializedStr)写法其实没有区别的,从结果也能看出。

    JSON.parseObject(serializedStr, User.class)

    在指定了@type的情况下,自动调用了User类默认构造器,User类对应的setter方法(setAge,setName),最终结果是User类的一个实例。这种写法明确指定了目标对象必须是User类型,如果@type对应的类型不是User类型或其子类,将抛出不匹配异常,但是,就算指定了特定的类型,依然有方式在类型匹配之前来触发漏洞。

    1.2.10测试

    对于上面User这个类,测试结果和1.1.157一样,这里不写了。

    到这个版本autotype依然默认开启。不过从这个版本开始,fastjson在ParserConfig中加入了denyList,一直到1.2.24版本,这个denyList都只有一个类(不过这个java.lang.Thread不是用于漏洞利用的):

    1.2.25测试

    测试结果是抛出出了异常:

    从1.2.25开始,autotype默认关闭了,对于autotype开启,后面漏洞分析会涉及到。并且从1.2.25开始,增加了checkAutoType函数,它的主要作用是检测@type指定的类是否在白名单、黑名单(使用的startswith方式)

    以及目标类是否是两个危险类(Classloader、DataSource)的子类或者子接口,其中白名单优先级最高,白名单如果允许就不检测黑名单与危险类,否则继续检测黑名单与危险类:

    增加了黑名单类、包数量,同时增加了白名单,用户还可以调用相关方法添加黑名单/白名单到列表中:

    后面的许多漏洞都是对checkAutotype以及本身某些逻辑缺陷导致的漏洞进行修复,以及黑名单的不断增加。

    1.2.42测试

    与1.2.25一样,默认不开启autotype,所以结果一样,直接抛autotype未开启异常。

    从这个版本开始,将denyList、acceptList换成了十进制的hashcode,使得安全研究难度变大了(不过hashcode的计算方法依然是公开的,假如拥有大量的jar包,例如maven仓库可以爬jar包下来,可批量的跑类名、包名,不过对于黑名单是包名的情况,要找到具体可利用的类也会消耗一些时间):

    checkAutotype中检测也做了相应的修改:

    1.2.61测试

    与1.2.25一样,默认不开启autotype,所以结果一样,直接抛autotype未开启异常。

    从1.2.25到1.2.61之前其实还发生了很多绕过与黑名单的增加,不过这部分在后面的漏洞版本线在具体写,这里写1.2.61版本主要是说明黑名单防御所做的手段。在1.2.61版本时,fastjson将hashcode从十进制换成了十六进制:

    不过用十六进制表示与十进制表示都一样,同样可以批量跑jar包。在1.2.62版本为了统一又把十六进制大写:

    再之后的版本就是黑名单的增加了

    Fastjson漏洞版本线

    下面漏洞不会过多的分析,太多了,只会简单说明下以及给出payload进行测试与说明修复方式。

    ver<=1.2.24

    从上面的测试中可以看到,1.2.24及之前没有任何防御,并且autotype默认开启,下面给出那会比较经典的几个payload。

    com.sun.rowset.JdbcRowSetImpl利用链

    payload:

    测试(jdk=8u102,fastjson=1.2.24):

    结果:

    触发原因简析:

    JdbcRowSetImpl对象恢复->setDataSourceName方法调用->setAutocommit方法调用->context.lookup(datasourceName)调用

    com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl利用链

    payload:

    测试(jdk=8u102,fastjson=1.2.24):

    结果:

    触发原因简析:

    TemplatesImpl对象恢复->JavaBeanDeserializer.deserialze->FieldDeserializer.setValue->TemplatesImpl.getOutputProperties->TemplatesImpl.newTransformer->TemplatesImpl.getTransletInstance->通过defineTransletClasses,newInstance触发我们自己构造的class的静态代码块

    简单说明:

    这个漏洞需要开启SupportNonPublicField特性,这在样例测试中也说到了。因为TemplatesImpl类中_bytecodes_tfactory_name_outputProperties_class并没有对应的setter,所以要为这些private属性赋值,就需要开启SupportNonPublicField特性。具体这个poc构造过程,这里不分析了,可以看下廖大师傅的这篇,涉及到了一些细节问题。

    ver>=1.2.25&ver<=1.2.41

    1.2.24之前没有autotype的限制,从1.2.25开始默认关闭了autotype支持,并且加入了checkAutotype,加入了黑名单+白名单来防御autotype开启的情况。在1.2.25到1.2.41之间,发生了一次checkAutotype的绕过。

    下面是checkAutoType代码:

    在上面做了四个位置标记,因为后面几次绕过也与这几处位置有关。这一次的绕过是走过了前面的1,2,3成功进入位置4加载目标类。位置4 loadclass如下:

    去掉了className前后的L;,形如Lcom.lang.Thread;这种表示方法和JVM中类的表示方法是类似的,fastjson对这种表示方式做了处理。而之前的黑名单检测都是startswith检测的,所以可给@type指定的类前后加上L;来绕过黑名单检测。

    这里用上面的JdbcRowSetImpl利用链:

    测试(jdk8u102,fastjson 1.2.41):

    结果:

    ver=1.2.42

    在1.2.42对1.2.25~1.2.41的checkAutotype绕过进行了修复,将黑名单改成了十进制,对checkAutotype检测也做了相应变化:

    黑名单改成了十进制,检测也进行了相应hash运算。不过和上面1.2.25中的检测过程还是一致的,只是把startswith这种检测换成了hash运算这种检测。对于1.2.25~1.2.41的checkAutotype绕过的修复,就是红框处,判断了className前后是不是L;,如果是,就截取第二个字符和到倒数第二个字符。所以1.2.42版本的checkAutotype绕过就是前后双写LL;;,截取之后过程就和1.2.25~1.2.41版本利用方式一样了。

    用上面的JdbcRowSetImpl利用链:

    测试(jdk8u102,fastjson 1.2.42):

    结果:

    ver=1.2.43

    1.2.43对于1.2.42的绕过修复方式:

    在第一个if条件之下(L开头,;结尾),又加了一个以LL开头的条件,如果第一个条件满足并且以LL开头,直接抛异常。所以这种修复方式没法在绕过了。但是上面的loadclass除了L;做了特殊处理外,[也被特殊处理了,又再次绕过了checkAutoType:

    用上面的JdbcRowSetImpl利用链:

    测试(jdk8u102,fastjson 1.2.43):

    结果:

    ver=1.2.44

    1.2.44版本修复了1.2.43绕过,处理了[

    删除了之前的L开头、;结尾、LL开头的判断,改成了[开头就抛异常,;结尾也抛异常,所以这样写之前的几次绕过都修复了。

    ver>=1.2.45&ver<1.2.46

    这两个版本期间就是增加黑名单,没有发生checkAutotype绕过。黑名单中有几个payload在后面的RCE Payload给出,这里就不写了

    ver=1.2.47

    这个版本发生了不开启autotype情况下能利用成功的绕过。解析一下这次的绕过:

    1. 利用到了java.lang.class,这个类不在黑名单,所以checkAutotype可以过
    2. 这个java.lang.class类对应的deserializer为MiscCodec,deserialize时会取json串中的val值并load这个val对应的class,如果fastjson cache为true,就会缓存这个val对应的class到全局map中
    3. 如果再次加载val名称的class,并且autotype没开启(因为开启了会先检测黑白名单,所以这个漏洞开启了反而不成功),下一步就是会尝试从全局map中获取这个class,如果获取到了,直接返回

    这个漏洞分析已经很多了,具体详情可以参考下这篇

    payload:

    测试(jdk8u102,fastjson 1.2.47):

    结果:

    ver>=1.2.48&ver<=1.2.68

    在1.2.48修复了1.2.47的绕过,在MiscCodec,处理Class类的地方,设置了cache为false:

    在1.2.48到最新版本1.2.68之间,都是增加黑名单类。

    ver=1.2.68

    1.2.68是目前最新版,在1.2.68引入了safemode,打开safemode时,@type这个specialkey完全无用,无论白名单和黑名单,都不支持autoType了。

    在这个版本中,除了增加黑名单,还减掉一个黑名单:

    这个减掉的黑名单,不知道有师傅跑出来没,是个包名还是类名,然后能不能用于恶意利用,反正有点奇怪。

    探测Fastjson

    比较常用的探测Fastjson是用dnslog方式,探测到了再用RCE Payload去一个一个打。同事说让搞个能回显的放扫描器扫描,不过目标容器/框架不一样,回显方式也会不一样,这有点为难了...,还是用dnslog吧。

    dnslog探测

    目前fastjson探测比较通用的就是dnslog方式去探测,其中Inet4Address、Inet6Address直到1.2.67都可用。下面给出一些看到的payload(结合了上面的rand:{}这种方式,比较通用些):

    一些RCE Payload

    之前没有收集关于fastjson的payload,没有去跑jar包....,下面列出了网络上流传的payload以及从marshalsec中扣了一些并改造成适用于fastjson的payload,每个payload适用的jdk版本、fastjson版本就不一一测试写了,这一通测下来都不知道要花多少时间,实际利用基本无法知道版本、autotype开了没、用户咋配置的、用户自己设置又加了黑名单/白名单没,所以将构造的Payload一一过去打就行了,基础payload:

    下面是个小脚本,可以将基础payload转出各种绕过的变形态,还增加了\u\x编码形式:

    例如JdbcRowSetImpl结果:

    有些师傅也通过扫描maven仓库包来寻找符合jackson、fastjson的恶意利用类,似乎大多数都是在寻找jndi类型的漏洞。对于跑黑名单,可以看下这个项目,跑到1.2.62版本了,跑出来了大多数黑名单,不过很多都是包,具体哪个类还得去包中一一寻找。

    参考链接

    1. https://paper.seebug.org/994/#0x03
    2. https://paper.seebug.org/1155/
    3. https://paper.seebug.org/994/
    4. https://paper.seebug.org/292/
    5. https://paper.seebug.org/636/
    6. https://www.anquanke.com/post/id/182140#h2-1
    7. https://github.com/LeadroyaL/fastjson-blacklist
    8. http://www.lmxspace.com/2019/06/29/FastJson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/#v1-2-47
    9. http://xxlegend.com/2017/12/06/%E5%9F%BA%E4%BA%8EJdbcRowSetImpl%E7%9A%84Fastjson%20RCE%20PoC%E6%9E%84%E9%80%A0%E4%B8%8E%E5%88%86%E6%9E%90/
    10. http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/
    11. http://gv7.me/articles/2020/several-ways-to-detect-fastjson-through-dnslog/#0x03-%E6%96%B9%E6%B3%95%E4%BA%8C-%E5%88%A9%E7%94%A8java-net-InetSocketAddress
    12. https://xz.aliyun.com/t/7027#toc-4
    13. https://zhuanlan.zhihu.com/p/99075925
    14. ...

    太多了,感谢师傅们的辛勤记录。

    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1192/

    作者:江 | Categories:漏洞分析 | Tags:
  • 使用 ZoomEye 寻找 APT 攻击的蛛丝马迹

    2020-11-02

    作者:Heige(a.k.a Superhei) of KnownSec 404 Team 
    时间:2020年5月25日 
    英文链接:https://paper.seebug.org/1220/

    今年一月发布的ZoomEye 2020里上线了ZoomEye的历史数据查询API接口,这个历史数据接口还是非常有价值的,这里就介绍我这几天做的一些尝试追踪APT的几个案例。

    在开始之前首先你需要了解ZoomEye历史api接口的使用,参考文档:https://www.zoomeye.org/doc#history-ip-search 这里可以使用的是ZoomEye SDK https://github.com/knownsec/ZoomEye 另外需要强调说明下的是:ZoomEye线上的数据是覆盖更新的模式,也就是说第2次扫描如果没有扫描到数据就不会覆盖更新数据,ZoomEye上的数据会保留第1次扫描获取到的banner数据,这个机制在这种恶意攻击溯源里其实有着很好的场景契合点:恶意攻击比如Botnet、APT等攻击使用的下载服务器被发现后一般都是直接停用抛弃,当然也有一些是被黑的目标,也是很暴力的直接下线!所以很多的攻击现场很可能就被ZoomEye线上缓存。

    当然在ZoomEye历史api里提供的数据,不管你覆盖不覆盖都可以查询出每次扫描得到的banner数据,但是目前提供的ZoomEye历史API只能通过IP去查询,而不能通过关键词匹配搜索,所以我们需要结合上面提到的ZoomEye线上缓存数据搜索定位配合使用。

    案例一:Darkhotel APT

    在前几天其实我在“黑科技”知识星球里提到了,只是需要修复一个“bug”:这次Darkhotel使用的IE 0day应该是CVE-2019-1367 而不是CVE-2020-0674(感谢廋肉丁@奇安信),当然这个“bug”不影响本文的主题。

    从上图可以看出我们通过ZoomEye线上数据定位到了当时一个Darkhotel水坑攻击现场IP,我们使用ZoomEye SDK查询这个IP的历史记录:

    列举ZoomEye历史数据里收录这个IP数据的时间节点及对应端口服务

    我们再看看被植入IE 0day的进行水坑攻击的时间节点及端口:

    很显然这个水坑攻击的大致时间区间是从2019-10-06 05:24:44到2020-01-28 10:58:02,另外这个IP很显然不是攻击者购买的VPS之类,而是直接攻击了某个特定的网站来作为“水坑”进行攻击,可以确定的是这个IP网站早在2019-10-06之前就已经被入侵了!从这个水坑的网站性质可以基本推断Darkhotel这次攻击的主要目标就是访问这个网站的用户!

    我们继续列举下在2019年这个IP开了哪些端口服务,从而帮助我们分析可能的入侵点:

    很典型的JSP运行环境,在2019年5月的时候开了8009端口,Tomcat后台管理弱口令等问题一直都是渗透常用手段~~

    顺带提一句,其实这次的攻击还涉及了另外一个IP,因为这个IP相关端口banner因为更新被覆盖了,所以直接通过ZoomEye线上搜索是搜索不到的,不过如果你知道这个IP也可以利用ZoomEye历史数据API来查询这个IP的历史数据,这里就不详细展开了。

    案例二:毒云藤(APT-C-01)

    关于毒云藤(APT-C-01)的详细报告可以参考 https://ti.qianxin.com/uploads/2018/09/20/6f8ad451646c9eda1f75c5d31f39f668.pdf我们直接把关注点放在

    “毒云藤组织使用的一个用于控制和分发攻击载荷的控制域名 http://updateinfo.servegame.org

    “然后从 hxxp://updateinfo.servegame.org/tiny1detvghrt.tmp 下载 payload”

    URL上,我们先尝试找下这个域名对应的IP,显然到现在这个时候还没有多大收获:

    在奇安信的报告里我们可以看到使用的下载服务器WEB服务目录可以遍历

    所以我们应该可以直接尝试搜索那个文件名“tiny1detvghrt.tmp”,果然被我们找到了

    这里我们可以基本确定了updateinfo.servegame.org对应的IP为165.227.220.223 那么我们开始老套路查询历史数据:

    继续看看这个tiny1detvghrt.tmp部署的时间区间:

    最起码可以确定从2017年11月底就已经开始部署攻击了,那么在这个时间节点之前还有一个时间节点2017-10-04 05:17:38,我们看看他的banner数据:

    从这个banner数据里可以得出结论,这个跟第一个案例里目标明确的入侵后植入水坑不一样的是,这个应该是攻击者自主可控的服务器,从doajksdlfsadk.tmp这些文件命名方式及文件大小(都为4.9k)基本可以推断这个时间节点应该是攻击者进行攻击之前的实战演练!所以这个IP服务器一开始就是为了APT攻击做准备的,到被发现后就直接抛弃!

    总结

    网络空间搜索引擎采用主动探测方式在网络攻击威胁追踪上有很大的应用空间,也体现了历史数据的价值,通过时间线最终能复盘攻击者的攻击手段、目的及流程。最后感谢所有支持ZoomEye的朋友们,ZoomEye作为国际领先的网络空间测绘搜索引擎,我们一直在努力!


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1219/

    作者:江 | Categories:漏洞分析 | Tags:
  • CVE-2020-0796 Windows SMBv3 LPE Exploit POC 分析

    2020-11-02

    作者:SungLin@知道创宇404实验室 
    时间:2020年4月2日 
    英文版本:https://paper.seebug.org/1165/

    0x00 漏洞背景

    2020年3月12日微软确认在Windows 10最新版本中存在一个影响SMBv3协议的严重漏洞,并分配了CVE编号CVE-2020-0796,该漏洞可能允许攻击者在SMB服务器或客户端上远程执行代码,3月13日公布了可造成BSOD的poc,3月30日公布了可本地特权提升的poc, 这里我们来分析一下本地特权提升的poc。

    0x01 漏洞利用原理

    漏洞存在于在srv2.sys驱动中,由于SMB没有正确处理压缩的数据包,在解压数据包的时候调用函数Srv2DecompressData处理压缩数据时候,对压缩数据头部压缩数据大小OriginalCompressedSegmentSize和其偏移Offset的没有检查其是否合法,导致其相加可分配较小的内存,后面调用SmbCompressionDecompress进行数据处理时候使用这片较小的内存可导致拷贝溢出或越界访问,而在执行本地程序的时候,可通过获取当前本地程序的token+0x40的偏移地址,通过发送压缩数据给SMB服务器,之后此偏移地址在解压缩数据时候拷贝的内核内存中,通过精心构造的内存布局在内核中修改token将权限提升。

    0x02 获取Token

    我们先来分析下代码,POC程序和smb建立连接后,首先会通过调用函数OpenProcessToken获取本程序的Token,获得的Token偏移地址将通过压缩数据发送到SMB服务器中在内核驱动进行修改,而这个Token就是本进程的句柄的在内核中的偏移地址,Token是一种内核内存结构,用于描述进程的安全上下文,包含如进程令牌特权、登录ID、会话ID、令牌类型之类的信息。

    以下是我测试获得的Token偏移地址:

    0x03 压缩数据

    接下来poc会调用RtCompressBuffer来压缩一段数据,通过发送这段压缩数据到SMB服务器,SMB服务器将会在内核利用这个token偏移,而这段数据是'A'*0x1108+ (ktoken + 0x40)

    而经压缩后的数据长度0x13,之后这段压缩数据除去压缩数据段头部外,发送出去的压缩数据前面将会连接两个相同的值0x1FF2FF00BC,而这两个值将会是提权的关键。

    0x04 调试

    我们先来进行调试,首先因为这里是整数溢出漏洞,在srv2!Srv2DecompressData函数这里将会因为加法0xffff ffff + 0x10 = 0xf导致整数溢出,并且进入srvnet!SrvNetAllocateBuffer分配一个较小的内存。

    在进入了srvnet!SmbCompressionDecompress然后进入nt!RtlDecompressBufferEx2继续进行解压,最后进入函数nt!PoSetHiberRange,再开始进行解压运算,通过OriginalSize= 0xffff ffff与刚开始整数溢出分配的UnCompressBuffer存储数据的内存地址相加得一个远远大于限制范围的地址,将会造成拷贝溢出。

    但是我们最后需要复制的数据大小是0x1108,所以到底还是没有溢出,因为真正分配的数据大小是0x1278,通过srvnet!SrvNetAllocateBuffer进入池内存分配的时候,最后进入srvnet!SrvNetAllocateBufferFromPool调用nt!ExAllocatePoolWithTag来分配池内存:

    虽然拷贝没有溢出,但是却把这片内存的其他变量给覆盖了,包括srv2!Srv2DecompressDatade的返回值, nt!ExAllocatePoolWithTag分配了一个结构体来存储有关解压的信息与数据,存储解压数据的偏移相对于UnCompressBuffer_address是固定的0x60,而返回值相对于UnCompressBuffer_address偏移是固定的0x1150,也就是说存储UnCompressBuffer的地址相对于返回值的偏移是0x10f0,而存储offset数据的地址是0x1168,相对于存储解压数据地址的偏移是0x1108

    有一个问题是为什么是固定的值,因为在这次传入的OriginalSize= 0xffff ffffoffset=0x10,乘法整数溢出为0xf,而在srvnet! SrvNetAllocateBuffer中,对于传入的大小0xf做了判断,小于0x1100的时候将会传入固定的值0x1100作为后面结构体空间的内存分配值进行相应运算。

    然后回到解压数据这里,需解压数据的大小是0x13,解压将会正常进行,拷贝了0x1108 个'A'后,将会把8字节大小token+0x40的偏移地址拷贝到'A'的后面。

    解压完并复制解压数据到刚开始分配的地址后正常退出解压函数,接着就会调用memcpy进行下一步的数据拷贝,关键的地方是现在rcx变成了刚开始传入的本地程序的token+0x40的地址!!

    回顾一下解压缩后,内存数据的分布0x1100(‘A’)+Token=0x1108,然后再调用了srvnet!SrvNetAllocateBuffer函数后返回我们需要的内存地址,而v8的地址刚好是初始化内存偏移的0x10f0,所以v8+0x18=0x1108,拷贝的大小是可控的,为传入的offset大小是0x10,最后调用memcpy将源地址就是压缩数据0x1FF2FF00BC拷贝到目的地址是0xffff9b893fdc46f0(token+0x40)的后16字节将被覆盖,成功修改Token的值。

    0x05 提权

    而覆盖的值是两个相同的0x1FF2FF00BC,为什么用两个相同的值去覆盖token+0x40的偏移呢,这就是在windows内核中操作Token提升权限的方法之一了,一般是两种方法:

    第一种方法是直接覆盖Token,第二种方法是修改Token,这里采用的是修改Token。

    在windbg中可运行kd> dt _token的命令查看其结构体:

    所以修改_SEP_TOKEN_PRIVILEGES的值可以开启禁用, 同时修改PresentEnabledSYSTEM进程令牌具有的所有特权的值0x1FF2FF00BC,之后权限设置为:

    这里顺利在内核提升了权限,接下来通过注入常规的shellcode到windows进程winlogon.exe中执行任意代码:

    如下所示执行了弹计算器的动作:

    参考链接:

    1. https://github.com/eerykitty/CVE-2020-0796-PoC
    2. https://github.com/danigargu/CVE-2020-0796
    3. https://ired.team/miscellaneous-reversing-forensics/windows-kernel/how-kernel-exploits-abuse-tokens-for-privilege-escalation

    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1164/

    作者:江 | Categories:漏洞分析 | Tags:
  • CVE-2020-0796 Windows SMBv3 LPE Exploit POC Analysis

    2020-11-02

    Author:SungLin@Knownsec 404 Team 
    Time: April 2, 2020 
    Chinese version:https://paper.seebug.org/1164/

    0x00 Background

    On March 12, 2020, Microsoft confirmed that a critical vulnerability affecting the SMBv3 protocol exists in the latest version of Windows 10, and assigned it with CVE-2020-0796, which could allow an attacker to remotely execute the code on the SMB server or client. On March 13 they announced the poc that can cause BSOD, and on March 30, the poc that can promote local privileges was released . Here we analyze the poc that promotes local privileges.

    0x01 Exploit principle

    The vulnerability exists in the srv2.sys driver. Because SMB does not properly handle compressed data packets, the function Srv2DecompressData is called when processing the decompressed data packets. The compressed data size of the compressed data header, OriginalCompressedSegmentSize and Offset, is not checked for legality, which results in the addition of a small amount of memory. SmbCompressionDecompress can be used later for data processing. Using this smaller piece of memory can cause copy overflow or out-of-bounds access. When executing a local program, you can obtain the current offset address of the token + 0x40 of the local program that is sent to the SMB server by compressing the data. After that, the offset address is in the kernel memory that is copied when the data is decompressed, and the token is modified in the kernel through a carefully constructed memory layout to enhance the permissions.

    0x02 Get Token

    Let's analyze the code first. After the POC program establishes a connection with smb, it will first obtain the Token of this program by calling the function OpenProcessToken. The obtained Token offset address will be sent to the SMB server through compressed data to be modified in the kernel driver. Token is the offset address of the handle of the process in the kernel. TOKEN is a kernel memory structure used to describe the security context of the process, including process token privilege, login ID, session ID, token type, etc.

    Following is the Token offset address obtained by my test.

    0x03 Compressed Data

    Next, poc will call RtCompressBuffer to compress a piece of data. By sending this compressed data to the SMB server, the SMB server will use this token offset in the kernel, and this piece of data is 'A' * 0x1108 + (ktoken + 0x40).

    The length of the compressed data is 0x13. After this compressed data is removed except for the header of the compressed data segment, the compressed data will be connected with two identical values 0x1FF2FF00BC, and these two values will be the key to elevation.

    0x04 debugging

    Let's debug it first, because here is an integer overflow vulnerability. In the function srv2! Srv2DecompressData, an integer overflow will be caused by the multiplication 0xffff ffff * 0x10 = 0xf, and a smaller memory will be allocated in srvnet! SrvNetAllocateBuffer.

    After entering srvnet! SmbCompressionDecompress and nt! RtlDecompressBufferEx2 to continue decompression, then entering the function nt! PoSetHiberRange, and then starting the decompression operation, adding OriginalMemory = 0xffff ffff to the memory address of the UnCompressBuffer storage data allocated by the integer overflow just started Get an address far larger than the limit, it will cause copy overflow.

    But the size of the data we need to copy at the end is 0x1108, so there is still no overflow, because the real allocated data size is 0x1278, when entering the pool memory allocation through srvnet! SrvNetAllocateBuffer, finally enter srvnet! SrvNetAllocateBufferFromPool to call nt! ExAllocatePoolWithTag to allocate pool memory.

    Although the copy did not overflow, it did overwrite other variables in this memory, including the return value of srv2! Srv2DecompressDatade. The UnCompressBuffer_addressis fixed at 0x60, and the return value relative to the UnCompressBuffer_address offset is fixed at 0x1150, which means that the offset to store the address of the UnCompressBuffer relative to the return value is 0x10f0, and the address to store the offset data is 0x1168, relative to the storage decompression Data address offset is 0x1108.

    There is a question why it is a fixed value, because the OriginalSize = 0xffff ffff, offset = 0x10 passed in this time, the multiplication integer overflow is 0xf, and in srvnet! SrvNetAllocateBuffer, the size of the passed in 0xf is judged, which is less At 0x1100, a fixed value of 0x1100 will be passed in as the memory allocation value of the subsequent structure space for the corresponding operation, and when the value is greater than 0x1100, the size passed in will be used.

    Then return to the decompressed data. The size of the decompressed data is 0x13. The decompression will be performed normally. Copy 0x1108of "A", the offset address of the 8-byte token + 0x40 will be copied to the back of "A".

    After decompression and copying the decompressed data to the address that was initially allocated, exit the decompression function normally, and then call memcpy for the next data copy. The key point is that rcx now becomes the address of token + 0x40of the local program!!!

    After the decompression, the distribution of memory data is 0x1100 ('A') + Token = 0x1108, and then the function srvnet! SrvNetAllocateBuffer is called to return the memory address we need, and the address of v8 is just the initial memory offset 0x10f0, so v8 + 0x18 = 0x1108, the size of the copy is controllable, and the offset size passed in is 0x10. Finally, memcpy is called to copy the source address to the compressed data0x1FF2FF00BC to the destination address 0xffff9b893fdc46f0 (token + 0x40), the last 16 Bytes will be overwritten, the value of the token is successfully modified.

    0x05 Elevation

    The value that is overwritten is two identical 0x1FF2FF00BC. Why use two identical values to overwrite the offset of token + 0x40? This is one of the methods for operating the token in the windows kernel to enhance the authority. Generally, there are two methods.

    The first method is to directly overwrite the Token. The second method is to modify the Token. Here, the Token is modified.

    In windbg, you can run the kd> dt _token command to view its structure.

    So modify the value of _SEP_TOKEN_PRIVILEGES to enable or disable it, and change the values of Present and Enabled to all privileges of the SYSTEM process token 0x1FF2FF00BC, and then set the permission to:

    This successfully elevated the permissions in the kernel, and then execute any code by injecting regular shellcode into the windows process "winlogon.exe":

    Then it performed the action of the calculator as follows:

    Reference link:

    1. https://github.com/eerykitty/CVE-2020-0796-PoC
    2. https://github.com/danigargu/CVE-2020-0796
    3. https://ired.team/miscellaneous-reversing-forensics/windows-kernel/how-kernel-exploits-abuse-tokens-for-privilege-escalation

    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1165/

    作者:江 | Categories:漏洞分析 | Tags:
  • Nexus Repository Manager 3 Several Expression Parsing Vulnerabilities

    2020-11-02

    Author:Longofo@ Knownsec 404 Team 
    Time: April 8, 2020 
    Chinese version:https://paper.seebug.org/1166/

    Nexus Repository Manager 3 recently exposed two El expression parsing vulnerabilities, cve-2020-10199 and cve-2020-10204, both of which are found by GitHub security Lab team's @pwntester. I didn't track the vulnerability of nexus3 before, so diff had a headache at that time. In addition, the nexus3 bug and security fix are all mixed together, which makes it harder to guess the location of the vulnerability. Later, I reappeared cve-2020-10204 with @r00t4dm, cve-2020-10204 is a bypass of cve-2018-16621. After that, others reappeared cve-2020-10199. The root causes of these three vulnerabilities are the same. In fact, there are more than these three. The official may have fixed several such vulnerabilities. Since history is not easy to trace back, it is only a possibility. Through the following analysis, we can see it. There is also the previous CVE-2019-7238, this is a jexl expression parsing, I will analyze it together here, explain the repair problems to it. I have seen some analysis before, the article said that this vulnerability was fixed by adding a permission. Maybe it was really only a permission at that time, but the newer version I tested, adding the permission seems useless. In the high version of Nexus3, the sandbox of jexl whitelist has been used.

    Test Environment

    Three Nexus3 environments will be used in this article:

    • nexus-3.14.0-04
    • nexus-3.21.1-01
    • nexus-3.21.2-03

    nexus-3.14.0-04 is used to test jexl expression parsing, nexus-3.21.1-01 is used to test jexl expression parsing and el expression parsing and diff, nexus-3.21.2-03 is used to test el expression Analysis and diff.

    Vulnerability diff

    The repair limit of CVE-2020-10199 and CVE-2020-10204 vulnerabilities is 3.21.1 and 3.21.2, but the github open source code branch does not seem to correspond, so I have to download the compressed package for comparison. The official download of nexus-3.21.1-01 and nexus-3.21.2-03, but beyond comparison requires the same directory name, the same file name, and some files for different versions of the code are not the same. I first decompiled all the jar packages in the corresponding directory, and then used a script to replace all the files in nexus-3.21.1-01 directory and the file name with 3.21.1-01 to 3.21.2-03, and deleted the META folder, this folder is not useful for the vulnerability diff and affects the diff analysis, so it has been deleted. The following is the effect after processing:

    If you have not debugged and familiar with the previous Nexus 3 vulnerabilities, it may be headache to look at diff. There is no target diff.

    Routing and corresponding processing class

    General routing

    Grab the packet sent by nexus3, random, you can see that most requests are POST type, URI is /service/extdirect:

    The content of the post is as follows:

    We can look at other requests. In post json, there are two keys: action and method. Search for the keyword "coreui_Repository" in the code:

    We can see this, expand and look at the code:

    The action is injected through annotations, and the method "getBrowseableFormats" in the post above is also included, the corresponding method is injected through annotations:

    So after such a request,It is very easy to locate routing and corresponding processing class.

    API routing

    The Nexus3 API also has a vulnerability. Let's see how to locate the API route. In the admin web page, we can see all the APIs provided by Nexus3:

    look at the package, there are GET, POST, DELETE, PUT and other types of requests:

    Without the previous action and method, we use URI to locate it, but direct search of /service/rest/beta/security/content-selectors cannot be located, so shorten the keyword and use /beta/security/content-selectors to locate:

    Inject URI through @Path annotation, the corresponding processing method also uses the corresponding @GET, @POST to annotate.

    There are may be other types of routing, but you can also use a similar search method to locate. There is also the permission problem of Nexus. You can see that some of the above requests set the permissions through @RequiresPermissions, but the actual test permissions are still prevailing. Some permissions are also verified before arrival. Some operations are on the admin page, but it may not require admin permissions, may be no need permissions or only ordinary permissions.

    Several Java EL vulnerabilities caused by buildConstraintViolationWithTemplate

    After debugging CVE-2018-16621 and CVE-2020-10204, I feel that the keyword buildConstraintViolationWithTemplate can be used as the root cause of this vulnerability, because the call stack shows that the function call is on the boundary between the Nexus package and the hibernate-validator package, and the pop-up of the calculator is also after it enters the processing flow of hibernate-validator, that is, buildConstraintViolationWithTemplate (xxx) .addConstraintViolation (), and finally expressed in the ElTermResolver class in the hibernate-validator package through valueExpression.getValue (context) :

    So I decompile all jar packages of Nexus3, and then search for this keyword (use the repair version search, mainly to see if there are any missing areas that are not repaired; Nexue3 has some open source code, you can also search directly in the source code):

    Later I saw the Vulnerability Analysis published by the author, indeed used buildConstraintViolationWithTemplate as the source of the vulnerability , use this key point to do tracking analysis.

    As can be seen from the search results above, the three CVE key points caused by the el expression are also among them, and there are several other places, a few used this.getEscapeHelper().StripJavaEl , There are a few, it seems ok, a ecstasy in my heart? However, although several other places that have not been cleared and can be accessed by routing, they cannot be used. One of them will be selected for analysis later. So at the beginning, I said that the official may have fixed several similar places. I guess there are two possibilities:

    • Officials have noticed that there are also el parsing vulnerabilities in those places, so they did a cleanup.
    • There are other vulnerability discoverers who submitted the cleared vulnerability points, because those places can be used; but the uncleared places cannot be used, so the discoverers did not submit, and the official did not go do clear

    However, I feel that the latter possibility is more likely. After all, it is unlikely that the official will clear some places, and some places will not do it.

    CVE-2018-16621 analysis

    This vulnerability corresponds to the above search result is RolesExistValidator. Since the key point is searched, I will manually reverse the traceback to see if it can be traced back to the place where there is routing processing. Here is a simple search traceback.

    The key point is isValid in RolesExistValidator, which calls buildConstraintViolationWithTemplate. Search if there is a place to call RolesExistValidator:

    There is a call in RolesExist, this way of writing will generally use RolesExist as a comment, and will call RolesExistValidator.isValid () during verification. Continue to search for RolesExist:

    There are several places that directly use RolesExist to annotate the roles attribute. We can go back one by one, but according to the Role keyword, RoleXO is more likely, so look at this (UserXO is also), continue to search for RoleXO:

    There may be some other disturbances, such as the first red label RoleXOResponse, this can be ignored, we find the place to use RoleXO directly. In RoleComponent, if you see the second red annotation, you probably know that you can enter the route here. The third red annotation uses roleXO and has the roles keyword. RolesExist also annotates roles above, so the guess is attribute injection to roleXO. The decompiled code in some places is not easy to understand, you can look at the source code:

    It can be seen that the submitted parameters are injected into roleXO, and the route corresponding to RoleComponent is as follows:

    Through the above analysis, we probably know that we can enter the final RolesExistValidator, but there are may be many conditions to be met in the middle, we need to construct the payload and then measure it step by step. The location of the web page corresponding to this route is as follows:

    Test (the 3.21.1 version used here, CVE-2018-16621 is the previous vulnerability, which was fixed earlier in 3.21.1, but 3.21.1 was bypassed again, so the following is the bypass situation, will$Is replaced with$ \\ x to bypass):

    Repair method:

    Added getEscapeHelper (). StripJavaEL to clear the el expression, replacing$ {with{, the next two CVEs are bypassing this fix:

    CVE-2020-10204 analysis

    This is the bypass of the previous stripJavaEL repair mentioned above, and it will not be analyzed here. The use of the $\\x{ format will not be replaced (tested with version 3.21.1):

    CVE-2020-10199 analysis

    This vulnerability corresponds to ConstraintViolationFactory in the search results above:

    buildConstraintViolationWith (label 1) appears in the isValid of HelperValidator class (label 2), HelperValidator is annotated on HelperAnnotation (label 3, 4), HelperAnnotation is annotated on HelperBean (label 5), on ConstraintViolationFactory.createViolation HelperBean (labels 6, 7) is used in the method. Follow this idea to find the place where ConstraintViolationFactory.createViolation is called.

    Let's also go back to the manual reverse trace to see if we can trace back to where there is routing.

    Search ConstraintViolationFactory:

    There are several, here uses the first BowerGroupRepositoriesApiResource analysis, click to see that we can see that it is an API route:

    ConstraintViolationFactory was passed to super, and other functions of ConstraintViolationFactory were not called in BowerGroupRepositoriesApiResource, but its two methods also called super corresponding methods. Its super is AbstractGroupRepositoriesApiResource class:

    The super called in the BowerGroupRepositoriesApiResource constructor assigns ConstraintViolationFactory (label 1) in AbstractGroupRepositoriesApiResource, the use of ConstraintViolationFactory (label 2), and calls createViolation (we can see the memberNames parameter), which is needed to reach the vulnerability point. This call is in validateGroupMembers (label 3). The call to validateGroupMembers is called in both createRepository (label 4) and updateRepository (label 5), and these two methods can also be seen from the above annotations that they are routing methods.

    The route of BowerGroupRepositoriesApiResource is /beta/repositories/bower/group, find it in the admin page APIs to make a call (use 3.21.1 test):

    Several other subclasses of AbstractGroupRepositoriesApiResource are the same:

    CleanupPolicyAssetNamePatternValidator does not do cleanup point analysis

    Corresponding to the CleanupPolicyAssetNamePatternValidator in the search results above, we can see that there is no StripEL removal operation here:

    This variable is thrown into buildConstraintViolationWithTemplate through an error report. If the error message contains the value, then it can be used here.

    Search CleanupPolicyAssetNamePatternValidator:

    Used in CleanupPolicyAssetNamePattern class annotation, continue to search for CleanupPolicyAssetNamePattern:

    The attribute regex in CleanupPolicyCriteria is annotated by CleanupPolicyAssetNamePattern, and continue to search for CleanupPolicyCriteria:

    Called in the toCleanupPolicy method in CleanupPolicyComponent, where cleanupPolicyXO.getCriteria also happens to be CleanupPolicyCriteria object. toCleanupPolicy calls toCleanupPolicy in the createup and previewCleanup methods of the CleanupPolicyComponent that can be accessed through routing.

    Construct the payload test:

    However, it cannot be used here, and the value value will not be included in the error message. After reading RegexCriteriaValidator.validate, no matter how it is constructed, it will only throw a character in the value, so it cannot be used here.

    Similar to this is the CronExpressionValidator, which also throws an exception there, it can be used, but it has been fixed, and someone may have submitted it before. There are several other places that have not been cleared,but either skipped by if or else, or cannot be used.

    The way of manual backtracking search may be okay if there are not many places where the keyword is called, but if it is used a lot, it may not be so easy to deal with. However, for the above vulnerabilities, we can see that it is still feasible to search through manual backtracking.

    Vulnerabilities caused by JXEL (CVE-2019-7238)

    we can refer to @iswin's previous analysis https://www.anquanke.com/post/id/171116, here is no longer going Debugging screenshots. Here I want to write down the previous fix for this vulnerability, saying that it was added with permission to fix it. If only the permission is added, can it still be submitted? However, after testing version 3.21.1, even with admin permissions can not be used, I want to see if it can be bypassed. Tested in 3.14.0, it is indeed possible:

    But in 3.21.1, even if the authority is added, it will not work. Later, I debug and compare separately, and pass the following test:

    I learned that 3.14.0 and the above test used org.apache.commons.jexl3.internal.introspection.Uberspect processing, and its getMethod method is as follows:

    In 3.21.1, Nexus is set to org.apache.commons.jexl3.internal.introspection.SandboxJexlUberspect, this SandboxJexlUberspect, its getMethod method is as follows:

    It can be seen that only a limited number of methods of type String, Map, and Collection are allowed.

    Conclusion

    • After reading the above content, I believe that we have a general understanding of the Nexus3 loopholes, and you will no longer feel that you can't start. Try to look at other places, for example, there is an LDAP in the admin page, which can be used for jndi connect operation, but the context.getAttribute is called there. Although the class file will be requested remotely, the class will not be loaded, so there is no harm.
    • The root cause of some vulnerabilities may appear in a similar place in an application, just like the keyword buildConstraintViolationWithTemplate above, good luck maybe a simple search can encounter some similar vulnerabilities (but my luck looks bad Click, we can see the repair in some places through the above search, indicating that someone has already taken a step forward, directly called buildConstraintViolationWithTemplate and the available places seem to be gone)
    • Look closely at the payloads of the above vulnerabilities, it seems that the similarity is very high, so we can get a tool similar to fuzz parameters to collect the historical vulnerability payload of this application, each parameter can test the corresponding payload, good luck may be Hit some similar vulnerabilities.

    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1167/

    作者:江 | Categories:漏洞分析 | Tags:
  • Nexus Repository Manager 3 几次表达式解析漏洞

    2020-11-02

    作者:Longofo@知道创宇404实验室 
    时间:2020年4月8日

    Nexus Repository Manager 3最近曝出两个el表达式解析漏洞,编号为CVE-2020-10199CVE-2020-10204,都是由Github Secutiry Lab团队的@pwntester发现。由于之前Nexus3的漏洞没有去跟踪,所以当时diff得很头疼,并且Nexus3 bug与安全修复都是混在一起,更不容易猜到哪个可能是漏洞位置了。后面与@r00t4dm师傅一起复现出了CVE-2020-10204CVE-2020-10204CVE-2018-16621的绕过,之后又有师傅弄出了CVE-2020-10199,这三个漏洞的根源是一样的,其实并不止这三处,官方可能已经修复了好几处这样的漏洞,由于历史不太好追溯回去,所以加了可能,通过后面的分析,就能看到了。还有之前的CVE-2019-7238,这是一个jexl表达式解析,一并在这里分析下,以及对它的修复问题,之前看到有的分析文章说这个漏洞是加了个权限来修复,可能那时是真的只加了个权限吧,不过我测试用的较新的版本,加了权限貌似也没用,在Nexus3高版本已经使用了jexl白名单的沙箱。

    测试环境

    文中会用到三个Nexus3环境:

    • nexus-3.14.0-04
    • nexus-3.21.1-01
    • nexus-3.21.2-03

    nexus-3.14.0-04用于测试jexl表达式解析,nexus-3.21.1-01用于测试jexl表达式解析与el表达式解析以及diff,nexus-3.21.2-03用于测试el表达式解析以及diff

    漏洞diff

    CVE-2020-10199、CVE-2020-10204漏洞的修复界限是3.21.1与3.21.2,但是github开源的代码分支好像不对应,所以只得去下载压缩包来对比了。在官方下载了nexus-3.21.1-01nexus-3.21.2-03,但是beyond对比需要目录名一样,文件名一样,而不同版本的代码有的文件与文件名都不一样。我是先分别反编译了对应目录下的所有jar包,然后用脚本将nexus-3.21.1-01中所有的文件与文件名中含有3.21.1-01的替换为了3.21.2-03,同时删除了META文件夹,这个文件夹对漏洞diff没什么用并且影响diff分析,所以都删除了,下面是处理后的效果:

    如果没有调试和熟悉之前的Nexus3漏洞,直接去看diff可能会看得很头疼,没有目标的diff。

    路由以及对应的处理类

    一般路由

    抓下nexus3发的包,随意的点点点,可以看到大多数请求都是POST类型的,URI都是/service/extdirect

    post内容如下:

    可以看下其他请求,json中都有actionmethod这两个key,在代码中搜索下coreui_Repository这个关键字:

    可以看到这样的地方,展开看下代码:

    通过注解方式注入了action,上面post的method->getBrowseableFormats也在中,通过注解注入了对应的method:

    所以之后这样的请求,我们就很好定位路由与对应的处理类了

    API路由

    Nexus3的API也出现了漏洞,来看下怎么定位API的路由,在后台能看到Nexus3提供的所有API。

    点几个看下包,有GET、POST、DELETE、PUT等类型的请求:

    没有了之前的action与method,这里用URI来定位,直接搜索/service/rest/beta/security/content-selectors定位不到,所以缩短关键字,用/beta/security/content-selectors来定位:

    通过@Path注解来注入URI,对应的处理方式也使用了对应的@GET、@POST来注解

    可能还有其他类型的路由,不过也可以使用上面类似的方式进行搜索来定位。还有Nexus的权限问题,可以看到上面有的请求通过@RequiresPermissions来设置了权限,不过还是以实际的测试权限为准,有的在到达之前也进行了权限校验,有的操作虽然在web页面的admin页面,不过本不需要admin权限,可能无权限或者只需要普通权限。

    buildConstraintViolationWithTemplate造成的几次Java EL漏洞

    在跟踪调试了CVE-2018-16621CVE-2020-10204之后,感觉buildConstraintViolationWithTemplate这个keyword可以作为这个漏洞的根源,因为从调用栈可以看出这个函数的调用处于Nexus包与hibernate-validator包的分界,并且计算器的弹出也是在它之后进入hibernate-validator的处理流程,即buildConstraintViolationWithTemplate(xxx).addConstraintViolation(),最终在hibernate-validator包中的ElTermResolver中通过valueExpression.getValue(context)完成了表达式的执行,与@r00t4dm师傅也说到了这个:

    于是反编译了Nexus3所有jar包,然后搜索这个关键词(使用的修复版本搜索,主要是看有没有遗漏的地方没修复;Nexue3有开源部分代码,也可以直接在源码搜索):

    后面作者也发布了漏洞分析,确实用了buildConstraintViolationWithTemplate作为了漏洞的根源,利用这个关键点做的污点跟踪分析。

    从上面的搜索结果中可以看到,el表达式导致的那三个CVE关键点也在其中,同时还有其他几个地方,有几个使用了this.getEscapeHelper().stripJavaEl做了清除,还有几个,看起来似乎也可以,心里一阵狂喜?然而,其他几个没有做清除的地方虽然能通过路由进入,但是利用不了,后面会挑选其中的一个做下分析。所以在开始说了官方可能修复了几个类似的地方,猜想有两种可能:

    • 官方自己察觉到了那几个地方也会存在el解析漏洞,所以做了清除
    • 有其他漏洞发现者提交了那几个做了清除的漏洞点,因为那几个地方可以利用;但是没清除的那几个地方由于没法利用,所以发现者并没有提交,官方也没有去做清除

    不过感觉后一种可能性更大,毕竟官方不太可能有的地方做清除,有的地方不做清除,要做也是一起做清除工作。

    CVE-2018-16621分析

    这个漏洞对应上面的搜索结果是RolesExistValidator,既然搜索到了关键点,自己来手动逆向回溯下看能不能回溯到有路由处理的地方,这里用简单的搜索回溯下。

    关键点在RolesExistValidator的isValid,调用了buildConstraintViolationWithTemplate。搜索下有没有调用RolesExistValidator的地方:

    在RolesExist中有调用,这种写法一般会把RolesExist当作注解来使用,并且进行校验时会调用RolesExistValidator.isValid()。继续搜索RolesExist:

    有好几处直接使用了RolesExist对roles属性进行注解,可以一个一个去回溯,不过按照Role这个关键字RoleXO可能性更大,所以先看这个(UserXO也可以的),继续搜索RoleXO:

    会有很多其他干扰的,比如第一个红色标注RoleXOResponse,这种可以忽略,我们找直接使用RoleXO的地方。在RoleComponent中,看到第二个红色标注这种注解大概就知道,这里能进入路由了。第三个红色标注使用了roleXO,并且有roles关键字,上面RolesExist也是对roles进行注解的,所以这里猜测是对roleXO进行属性注入。有的地方反编译出来的代码不好理解,可以结合源码看:

    可以看到这里就是将提交的参数注入给了roleXO,RoleComponent对应的路由如下:

    通过上面的分析,我们大概知道了能进入到最终的RolesExistValidator,不过中间可能还有很多条件需要满足,需要构造payload然后一步一步测。这个路由对应的web页面位置如下:

    测试(这里使用的3.21.1版本,CVE-2018-16621是之前的漏洞,在3.21.1早修复了,不过3.21.1又被绕过了,所以下面使用的是绕过的情况,将$换成$\\x去绕过,绕过在后面两个CVE再说):

    修复方式:

    加上了getEscapeHelper().stripJavaEL对el表达式做了清除,将${替换为了{,之后的两个CVE就是对这个修复方式的绕过:

    CVE-2020-10204分析

    这就是上面说到的对之前stripJavaEL修复的绕过,这里就不细分析了,利用$\\x格式就不会被替换掉(使用3.21.1版本测试):

    CVE-2020-10199分析

    这个漏洞对应上面搜索结果是ConstraintViolationFactory

    buildConstraintViolationWith(标号1)出现在了HelperValidator(标号2)的isValid中,HelperValidator又被注解在HelperAnnotation(标号3、4)之上,HelperAnnotation注解在了HelperBean(标号5)之上,在ConstraintViolationFactory.createViolation方法中使用到了HelperBean(标号6、7)。按照这个思路要找调用了ConstraintViolationFactory.createViolation的地方。

    也来手动逆向回溯下看能不能回溯到有路由处理的地方。

    搜索ConstraintViolationFactory:

    有好几个,这里使用第一个BowerGroupRepositoriesApiResource分析,点进去看就能看出它是一个API路由:

    ConstraintViolationFactory被传递给了super,在BowerGroupRepositoriesApiResource并没有调用ConstraintViolationFactory的其他函数,不过它的两个方法,也是调用了super对应的方法。它的superAbstractGroupRepositoriesApiResource类:

    BowerGroupRepositoriesApiResource构造函数中调用的super,在AbstractGroupRepositoriesApiResource赋值了ConstraintViolationFactory(标号1),ConstraintViolationFactory的使用(标号2),调用了createViolation(在后面可以看到memberNames参数),这也是之前要到达漏洞点所需要的,这个调用处于validateGroupMembers中(标号3),validateGroupMembers的调用在createRepository(标号4)和updateRepository(标号5)中都进行了调用,而这两个方法通过上面的注解也可以看出,通过外部传递请求能到达。

    BowerGroupRepositoriesApiResource的路由为/beta/repositories/bower/group,在后台API找到它来进行调用(使用3.21.1测试):

    还有AbstractGroupRepositoriesApiResource的其他几个子类也是可以的:

    CleanupPolicyAssetNamePatternValidator未做清除点分析

    对应上面搜索结果的CleanupPolicyAssetNamePatternValidator,可以看到这里并没有做StripEL清除操作:

    这个变量是通过报错抛出放到buildConstraintViolationWithTemplate中的,要是报错信息中包含了value值,那么这里就是可以利用的。

    搜索CleanupPolicyAssetNamePatternValidator

    CleanupPolicyAssetNamePattern类注解中使用了,继续搜索CleanupPolicyAssetNamePattern

    CleanupPolicyCriteria中的属性regexCleanupPolicyAssetNamePattern注解了,继续搜索CleanupPolicyCriteria

    CleanupPolicyComponent中的to CleanupPolicy方法中有调用,其中的cleanupPolicyXO.getCriteria也正好是CleanupPolicyCriteria对象。toCleanupPolicy在CleanupPolicyComponent的可通过路由进入的create、previewCleanup方法又调用了toCleanupPolicy

    构造payload测试:

    然而这里并不能利用,value值不会被包含在报错信息中,去看了下RegexCriteriaValidator.validate,无论如何构造,最终也只会抛出value中的一个字符,所以这里并不能利用。

    与这个类似的是CronExpressionValidator,那里也是通过抛出异常,但是那里是可以利用的,不过被修复了,可能之前已经有人提交过了。还有其他几个没做清除的地方,要么被if、else跳过了,要么不能利用。

    人工去回溯查找的方式,如果关键字被调用的地方不多可能还好,不过要是被大量使用,可能就不是那么好处理了。不过上面几个漏洞,可以看到通过手动回溯查找还是可行的。

    JXEL造成的漏洞(CVE-2019-7238)

    可以参考下@iswin大佬之前的分析https://www.anquanke.com/post/id/171116,这里就不再去调试截图了。这里想写下之前对这个漏洞的修复,说是加了权限来修复,要是只加了权限,那不是还能提交一下?不过,测试了下3.21.1版本,就算用admin权限也无法利用了,想去看下是不是能绕过。在3.14.0中测试,确实是可以的:

    但是3.21.1中,就算加了权限,也是不行的。后面分别调试对比了下,以及通过下面这个测试:

    才知道3.14.0与上面这个测试使用的是org.apache.commons.jexl3.internal.introspection.Uberspect处理,它的getMethod方法如下:

    而在3.21.1中Nexus设置的是org.apache.commons.jexl3.internal.introspection.SandboxJexlUberspect,这个SandboxJexlUberspect,它的get Method方法如下:

    可以看出只允许调用String、Map、Collection类型的有限几个方法了。

    总结

    • 看完上面的内容,相信对Nexus3的漏洞大体有了解了,不会再无从下手的感觉。尝试看下下其他地方,例如后台有个LDAP,可进行jndi connect操作,不过那里调用的是context.getAttribute,虽然会远程请求class文件,不过并不会加载class,所以并没有危害。
    • 有的漏洞的根源点可能会在一个应用中出现相似的地方,就像上面那个buildConstraintViolationWithTemplate这个keyword一样,运气好说不定一个简单的搜索都能碰到一些相似漏洞(不过我运气貌似差了点,通过上面的搜索可以看到某些地方的修复,说明已经有人先行一步了,直接调用了buildConstraintViolationWithTemplate并且可用的地方似乎已经没有了)
    • 仔细看下上面几个漏洞的payload,好像相似度很高,所以可以弄个类似fuzz参数的工具,搜集这个应用的历史漏洞payload,每个参数都可以测试下对应的payload,运气好可能会撞到一些相似漏洞

    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1166/

    作者:江 | Categories:漏洞分析 | Tags: