RSS Feed
更好更安全的互联网
  • Typo3 CVE-2019-12747 反序列化漏洞分析

    2019-08-01

    作者:mengchen@知道创宇404实验室
    时间:2019年8月1日
    英文版本:https://paper.seebug.org/997/

    1. 前言

    TYPO3是一个以PHP编写、采用GNU通用公共许可证的自由、开源的内容管理系统。

    2019年7月16日,RIPS的研究团队公开了Typo3 CMS的一个关键漏洞详情CVE编号为CVE-2019-12747,它允许后台用户执行任意PHP代码。

    漏洞影响范围:Typo3 8.x-8.7.26 9.x-9.5.7

    2. 测试环境简述

     

    3. TCA

    在进行分析之前,我们需要了解下Typo3TCA(Table Configuration Array),在Typo3的代码中,它表示为$GLOBALS['TCA']

    Typo3中,TCA算是对于数据库表的定义的扩展,定义了哪些表可以在Typo3的后端可以被编辑,主要的功能有

    • 表示表与表之间的关系
    • 定义后端显示的字段和布局
    • 验证字段的方式

    这次漏洞的两个利用点分别出在了CoreEngineFormEngine这两大结构中,而TCA就是这两者之间的桥梁,告诉两个核心结构该如何表现表、字段和关系。

    TCA的第一层是表名:

     

    其中pagestt_content就是数据库中的表。

    接下来一层就是一个数组,它定义了如何处理表,

     

    在这次分析过程中,只需要了解这么多,更多详细的资料可以查询官方手册

    4. 漏洞分析

    整个漏洞的利用流程并不是特别复杂,主要需要两个步骤,第一步变量覆盖后导致反序列化的输入可控,第二步构造特殊的反序列化字符串来写shell。第二步这个就是老套路了,找个在魔术方法中能写文件的类就行。这个漏洞好玩的地方在于变量覆盖这一步,而且进入两个组件漏洞点的传入方式也有着些许不同,接下来让我们看一看这个漏洞吧。

    4.1 补丁分析

    从Typo3官方的通告中我们可以知道漏洞影响了两个组件——Backend & Core API (ext:backend, ext:core),在GitHub上我们可以找到修复记录

    很明显,补丁分别禁用了backendDatabaseLanguageRows.phpcore中的DataHandler.php中的的反序列化操作。

    4.2 Backend ext 漏洞点利用过程分析

    根据补丁的位置,看下Backend组件中的漏洞点。

    路径:typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseLanguageRows.php:37

     

    很多类都继承了FormDataProviderInterface接口,因此静态分析寻找谁调用的DatabaseLanguageRowsaddData方法根本不现实,但是根据文章中的演示视频,我们可以知道网站中修改page这个功能中进入了漏洞点。在addData方法加上断点,然后发出一个正常的修改page的请求。

    当程序断在DatabaseLanguageRowsaddData方法后,我们就可以得到调用链。

    DatabaseLanguageRows这个addData中,只传入了一个$result数组,而且进行反序列化操作的目标是$result['databaseRow']中的某个值。看命名有可能是从数据库中获得的值,往前分析一下。

    进入OrderedProviderListcompile方法。

    路径:typo3/sysext/backend/Classes/Form/FormDataGroup/OrderedProviderList.php:43

     

    我们可以看到,在foreach这个循环中,动态实例化$this->providerList中的类,然后调用它的addData方法,并将$result作为方法的参数。

    在调用DatabaseLanguageRows之前,调用了如图所示的类的addData方法。

    经过查询手册以及分析代码,可以知道在DatabaseEditRow类中,通过调用addData方法,将数据库表中数据读取出来,存储到了$result['databaseRow']中。

    路径:typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseEditRow.php:32

     

    再后面又调用了DatabaseRecordOverrideValues类的addData方法。

    路径:typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordOverrideValues.php:31

     

    在这里,将$result['overrideValues']中的键值对存储到了$result['databaseRow']中,如果$result['overrideValues']可控,那么通过这个类,我们就能控制$result['databaseRow']的值了。

    再往前,看看$result的值是怎么来的。

    路径:typo3/sysext/backend/Classes/Form/FormDataCompiler.php:58

     

    很明显,通过调用FormDataCompilercompile方法,将$initialData中的数据存储到了$result中。

    再往前走,来到了EditDocumentController类中的makeEditForm方法中。

    在这里,$formDataCompilerInput['overrideValues']获取了$this->overrideVals[$table]中的数据。

    $this->overrideVals的值是在方法preInit中设定的,获取的是通过POST传入的表单中的键值对。

    这样一来,在这个请求过程中,进行反序列化的字符串我们就可以控制了。

    在表单中提交任意符合数组格式的输入,在后端代码中都会被解析,然后后端根据TCA来进行判断并处理。 比如我们在提交表单中新增一个名为a[b][c][d],值为233的表单项。

    在编辑表单的控制器EditDocumentController.php中下一个断点,提交之后。

    可以看到我们传入的键值对在经过getParsedBody方法解析后,变成了嵌套的数组,并且没有任何限制。

    我们只需要在表单中传入overrideVals这一个数组即可。这个数组中的具体的键值对,则需要看进行反序列化时取的$result['databaseRow']中的哪一个键值。

     

    要想进入反序列化的点,还需要满足上面的if条件,动态调一下就可以知道,在if语句中调用的是

     

    后面反序列化中调用的是

    因此,我们只需要在传入的表单中增加三个参数即可。

     

    可以看到,我们的输入成功的到达了反序列化的点。

    4.3 Core ext 漏洞点利用过程分析

    看下Core中的那个漏洞点。

    路径:typo3/sysext/core/Classes/DataHandling/DataHandler.php:1453

    看代码,如果我们要进入反序列化的点,需要满足前面的if条件

     

    也就是说要满足以下条件

    • $currentRecord是个数组
    • TCA$table的表属性中存在transOrigDiffSourceFieldlanguageFieldtransOrigPointerField字段。
    • $table的属性languageFieldtransOrigPointerField$currentRecord中对应的值要大于0

    查一下TCA表,满足第二条条件的表有

     

    但是所有sys_*的字段的adminOnly属性的值都是1,只有管理员权限才可以更改。因此我们可以用的表只有pages

    它的属性值是

     

    再往上,有一个对传入的参数进行处理的if-else语句。

    从注释中,我们可以知道传入的各个参数的功能:

    • 数组 $fieldArray 是默认值,这种一般都是我们无法控制的
    • 数组 $incomingFieldArray 是你想要设置的字段值,如果可以,它会合并到$fieldArray中。

    而且如果满足if (strpos($id, 'NEW') !== false)条件的话,也就是$id是一个字符串且其中存在NEW字符串,会进入下面的合并操作。

    如果不满足上面的if条件,$currentRecord的值就会通过recordInfo方法从数据库中直接获取。这样后面我们就无法利用了。

    简单总结一下,我们需要

    • $tablepages
    • $id是个字符串,而且存在NEW字符串
    • $incomingFieldArray中要存在payload

    接下来我们看在哪里对该函数进行了调用。

    全局搜索一下,只找到一处,在typo3/sysext/core/Classes/DataHandling/DataHandler.php:954处的process_datamap方法中进行了调用。

    整个项目中,对process_datamap调用的地方就太多了,尝试使用xdebug动态调试来找一下调用链。从RIPS团队的那一篇分析文章结合上面的对表名的分析,我们可以知道,漏洞点在创建page的功能处。

    接下来就是找从EditDocumentController.phpmainAction方法到前面我们分析的fillInFieldArray方法的调用链。

    尝试在网站中新建一个page,然后在调用fillInFieldArray的位置下一个断点,发送请求后,我们就拿到了调用链。

    看一下mainAction的代码。

     

    当满足if条件是进入目标$response = $this->processData($request)

     

    这个在新建一个page时,正常的表单中就携带doSave == 1,而doSave的值就是在方法preInit中获取的。

    这样条件默认就是成立的,然后将$request传入了processData方法。

     

    代码很容易懂,从$request中解析出来的数据,首先存储在$this->data$this->cmd中,然后实例化一个名为$tce,调用$tce->start方法将传入的数据存储在其自身的成员datamapcmdmap中。

     

    而且if ($this->doSave === true)这个条件也是成立的,进入process_datamap方法。

    代码有注释还是容易阅读的,在第985行,获取了datamap中所有的键名,然后存储在$orderOfTables,然后进入foreach循环,而这个$table,在后面传入fillInFieldArray方法中,因此,我们只需要分析$table == pages时的循环即可。

     

    大致浏览下代码,再结合前面的分析,我们需要满足以下条件:

    • $recordAccess的值要为true
    • $incomingFieldArray中的payload不会被删除
    • $table的值为pages
    • $id中存在NEW字符串

    既然正常请求可以直接断在调用fillInFieldArray处,正常请求中,第一条、第三条和第四条都是成立的。

    根据前面对fillInFieldArray方法的分析,构造payload,向提交的表单中添加三个键值对。

     

    其中NEW*字符串要根据表单生成的值进行对应的修改。

    发送请求后,依旧能够进入fillInFieldArray,而在传入的$incomingFieldArray参数中,可以看到我们添加的三个键值对。

    进入fillInFieldArray之后,其中l10n_diffsource将会进行反序列化操作。此时我们在请求中将其l10n_diffsource改为构造好的序列化字符串,重新发送请求即可成功getshell

    5. 写在最后

    其实单看这个漏洞的利用条件,还是有点鸡肋的,需要你获取到typo3的一个有效的后台账户,并且拥有编辑page的权限。

    而且这次分析Typo3给我的感觉与其他网站完全不同,我在分析创建&修改page这个功能的参数过程中,并没有发现什么过滤操作,在后台的所有参数都是根据TCA的定义来进行相应的操作,只有传入不符合TCA定义的才会抛出异常。而TCA的验证又不严格导致了变量覆盖这个问题。

    官方的修补方式也是不太懂,直接禁止了反序列化操作,但是个人认为这次漏洞的重点还是在于前面变量覆盖的问题上,尤其是Backend的利用过程中,可以直接覆盖从数据库中取出的数据,这样只能算是治标不治本,后面还是有可能产生新的问题。

    当然了,以上只是个人拙见,如有错误,还请诸位斧正。

    6. 参考链接

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • CVE-2019-11229详细分析 –git config可控-RCE

    2019-07-24

    作者:LoRexxar'@知道创宇404实验室
    时间:2019年7月23日
    英文版本:https://paper.seebug.org/990/

    2019年4月15号,gitea曾爆出过一个漏洞,恰逢当时对这个漏洞比较好奇就着手去研究了一下,漏洞的描述是这样的:

    models/repo_mirror.go in Gitea before 1.7.6 and 1.8.x before 1.8-RC3 mishandles mirror repo URL settings, leading to remote code execution.

    在和朋友@hammer的一同研究下,成功控制了git config的内容,但是在从git config到RCE的过程遇到了困难,就暂时搁置了,在过了几个月之后,偶然得到@Lz1y和@x1nGuang两位大佬的启发,成功复现了这个漏洞,下面我们就来仔细研究下这个问题。

    分析补丁

    首先根据cve的信息,确定漏洞1.7.6和1.8.0-rc3上修复

    根据漏洞文件为repo_mirror.go这个信息锁定更新的commit,commit主要为 #6593和#6595

    根据patch可以大致锁定问题的关键点

    /models/repo_mirror.go

    当仓库为mirror仓库时,settings页面会显示关于mirror的配置

    patch中将原来的修改配置文件中的url选项修改为NewCommand。很容易理解,将写入文件更改为执行命令,这种修复方式一定是因为写入文件存在无法修复这个问题的窘境,那么这也就说明url这里可以通过传入%0d%0a来换行,导致修改config中的其他配置。

    控制 gitconfig

    跟随前面的逻辑,首先我们新建一个mirror仓库。

    抓包并修改mirror_address为相应的属性。

    可以传入各种配置,可以控制config文件的内容。

    比较有趣的是,如果你更新同步设置时,服务端还会格式化配置。

    进一步利用

    而重要的是如何从config文件可控到下一步利用。

    首先,git服务端只会保留.git里的内容,并不是完整的类似我们客户端使用的git仓库。所以很难引入外部文件。否则就可以通过设置hook目录来实现RCE,这种思路的关键点在于找到一个可控的文件写入或者文件上传。

    其次,另外一种思路就是寻找一个能够执行命令的配置,并寻找一个能够触发相关配置的远程配置。

    通过写文件配合 githook path RCE

    在git中,存在一个叫做Git Hook的东西,是用于在处理一些操作的时,相应的hook就会执行相应的脚本。

    在web界面,只有gitea的管理员才能管理git hook,所以对于普通用户来说,我们就不能直接通过编辑git hook来修改脚本。

    但我们却可以通过控制git config来修改hook存放的目录。

    当我们构造发送

    服务端的config文件变为

    这样我们只要能在服务端的任意位置能够写入文件或者创建文件,我们就可以设置hookspath到那里,并触发git hook来执行命令。

    在经过我们的仔细研究之后,我们发现,在漏洞存在的版本1.7.5版本以下,如果编辑服务端的文件,那么服务端的文件就会保存在gitea的运行目录下生成。

    而这个文件在不重启gitea的情况下不会清除,而这个repo_id可以从其他的api处挖掘到。

    具体详细利用链可以看

    值得注意的是,这种方式需要知道服务端运行的位置,虽然我们可以认为go的路径都是比较形似的,也有部分人会在当前编译目录下执行。但可以说这种方式还是不算靠谱。

    通过控制 git config 配置来 RCE

    在@x1nGuang大佬的帮助下,我重新审视了和git config相关的一些配置。

    gitProxy

    gitProxy是用来针对git协议需要fetch等操作时,需要执行的命令。是一个用来应对特殊场景的配置选项。一般是应用于,在git请求时,可能的需要使用代理应用的场景。

    这里我们设置服务端

    然后需要注意,同步的url必须为git开头

    但问题在于,由于gitProxy在git设计中,就是执行一个代理应用,所以无论输入什么,都会被当作一个应用执行,也就没办法带参数。

    这样一来,在实际的利用场景中就又受到了很大的局限,这里可以尝试用普通项目中的上传文件功能来上传一个bin,然后抓包获取文件路径,最后通过gitProxy来执行后门。

    但同样的是,这种思路仍旧受限于gitea的运行目录,不过比起之前的利用方式来说,1.8.0版本也可以利用这种方式来RCE。

    sshCommand

    在git的文档中,还有一个配置是sshCommand。

    这是一个在git中允许通过特殊的配置,使git fetch/git push 通过ssh来连接远端的系统。在@Lz1y大佬的博客中也提到了这种利用方式。

    我们设置sshCommand为指定的命令

    然后设置协议为ssh保存,并点击同步。

    而与gitProxy不同的是,这里可以跟参数

    写在最后

    这是一个很特别的关于git类平台的漏洞例子,由于我在研究git config利用方式的时候遭遇了很多困难,导致这篇文章断断续续的复现了很久。整个漏洞利用链和git的特性都有强依赖,还算是挺有趣的体验,有机会再仔细分析一下gitea、gogs和gitlab的代码,希望也能挖一个有趣的洞...


    Paper

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

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • Redis 基于主从复制的 RCE 利用方式

    2019-07-15

    作者:LoRexxar'@知道创宇404实验室
    时间:2019年7月9日

    在2019年7月7日结束的WCTF2019 Final上,LC/BC的成员Pavel Toporkov在分享会上介绍了一种关于redis新版本的RCE利用方式,比起以前的利用方式来说,这种利用方式更为通用,危害也更大,下面就让我们从以前的redis RCE利用方式出发,一起聊聊关于redis的利用问题。

    https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf

    通过写入文件 GetShell

    未授权的redis会导致GetShell,可以说已经是众所周知的了。

    而这种方式是通过写文件来完成GetShell的,这种方式的主要问题在于,redis保存的数据并不是简单的json或者是csv,所以写入的文件都会有大量的无用数据,形似

    这种主要利用了crontab、ssh key、webshell这样的文件都有一定容错性,再加上crontab和ssh服务可以说是服务器的标准的服务,所以在以前,这种通过写入文件的getshell方式基本就可以说是很通杀了。

    但随着现代的服务部署方式的不断发展,组件化成了不可逃避的大趋势,docker就是这股风潮下的产物之一,而在这种部署模式下,一个单一的容器中不会有除redis以外的任何服务存在,包括ssh和crontab,再加上权限的严格控制,只靠写文件就很难再getshell了,在这种情况下,我们就需要其他的利用手段了。

    通过主从复制 GetShell

    在介绍这种利用方式之前,首先我们需要介绍一下什么是主从复制和redis的模块。

    Redis主从复制

    Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。但如果当把数据存储在单个Redis的实例中,当读写体量比较大的时候,服务端就很难承受。为了应对这种情况,Redis就提供了主从模式,主从模式就是指使用一个redis实例作为主机,其他实例都作为备份机,其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。

    这里我们开两台docker来做测试

    然后通过slaveof可以设置主从状态

    这样一来数据就会自动同步了

    Redis模块

    在了解了主从同步之后,我们还需要对redis的模块有所了解。

    在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在redis中实现一个新的Redis命令,通过写c语言并编译出.so文件。

    编写恶意so文件的代码

    https://github.com/RicterZ/RedisModules-ExecuteCommand

    利用原理

    Pavel Toporkov在2018年的zeronights会议上,分享了关于这个漏洞的详细原理。

    https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf

    在ppt中提到,在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。

    然后在从机上加载so文件,我们就可以执行拓展的新命令了。

    复现过程

    这里我们选择使用模拟的恶意服务端来作为主机,并模拟fullresync请求。

    https://github.com/LoRexxar/redis-rogue-server

    然后启用redis 5.0的docker

    为了能够更清晰的看到效果,这里我们把从服务端执行完成后删除的部分暂时注释掉。

    然后直接通过脚本来攻击服务端

    然后我们链接上去就可以执行命令

     

    Paper

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

    作者:吴烦恼 | Categories:安全研究技术分享 | Tags:
  • Linux 内核 TCP MSS 机制详细分析

    2019-07-15

    作者:Hcamael@知道创宇 404 实验室
    时间:2019 年 6 月 26 日
    英文版本:https://paper.seebug.org/967/

    前言

    上周Linux内核修复了4个CVE漏洞[1],其中的CVE-2019-11477感觉是一个很厉害的Dos漏洞,不过因为有其他事打断,所以进展的速度比较慢,这期间网上已经有相关的分析文章了。[2][3]

    而我在尝试复现CVE-2019-11477漏洞的过程中,在第一步设置MSS的问题上就遇到问题了,无法达到预期效果,但是目前公开的分析文章却没对该部分内容进行详细分析。所以本文将通过Linux内核源码对TCP的MSS机制进行详细分析。

    测试环境

    1. 存在漏洞的靶机

    操作系统版本:Ubuntu 18.04

    内核版本:4.15.0-20-generic

    地址:192.168.11.112

    内核源码:

    带符号的内核:

    关闭内核地址随机化(KALSR):

    装一个nginx,供测试:

    2. 宿主机

    操作系统:MacOS

    Wireshark:抓流量

    虚拟机:VMware Fusion 11

    调试Linux虚拟机:

    编译gdb:

    gdb进行远程调试:

    3. 攻击机器

    自己日常使用的Linux设备就好了

    地址:192.168.11.111

    日常习惯使用Python的,需要装个scapy构造自定义TCP包

    自定义SYN的MSS选项

    有三种方法可以设置TCP SYN包的MSS值

    1. iptable

    2. route

    3. 直接发包设置

    PS:使用scapy发送自定义TCP包需要ROOT权限

     

    flags选项S表示SYN,A表示ACK,SA表示SYN, ACK

    scapy中TCP可设置选项表:

    但是这个会有一个问题,在使用Python发送了一个SYN包以后,内核会自动带上一个RST包,查过资料后,发现在新版系统中,对于用户发送的未完成的TCP握手包,内核会发送RST包终止该连接,应该是为了防止进行SYN Floor攻击。解决办法是使用iptable过滤RST包:

    对于MSS的深入研究

    关于该漏洞的细节,别的文章中已经分析过了,这里简单的提一下,该漏洞为uint16溢出:

    所以在mss_now小于等于8时,才能发生整型溢出。

    深入研究的原因是因为进行了如下的测试:

    攻击机器通过iptables/iproute命令将MSS值为48后,使用curl请求靶机的http服务,然后使用wireshark抓流量,发现服务器返回的http数据包的确被分割成小块,但是只小到36,离预想的8有很大的差距

    这个时候我选择通过审计源码和调试来深入研究为啥MSS无法达到我的预期值,SYN包中设置的MSS值到代码中的mss_now的过程中发生了啥?

    随机进行源码审计,对发生溢出的函数tcp_set_skb_tso_segs进行回溯:

    随后对tcp_current_mss函数进行分析,关键代码如下:

     

    看完这部分源码后,我们对MSS的含义就有一个深刻的理解,首先说一说TCP协议:

    TCP协议包括了协议头和数据,协议头包括了固定长度的20字节和40字节的可选参数,也就是说TCP头部的最大长度为60字节,最小长度为20字节。

    __tcp_mtu_to_mss函数中的mss_now为我们SYN包中设置的MSS,从这里我们能看出MSS最小值是48,通过对TCP协议的理解和对代码的理解,可以知道SYN包中MSS的最小值48字节表示的是:TCP头可选参数最大长度40字节 + 数据最小长度8字节。

    但是在代码中的mss_now表示的是数据的长度,接下来我们再看该值的计算公式。

    tcphdr结构:

     

     

    该结构体为TCP头固定结构的结构体,大小为20bytes

    变量tcp_sk(sk)->tcp_header_len表示的是本机发出的TCP包头部的长度。

    因此我们得到的计算mss_now的公式为:SYN包设置的MSS值 - (本机发出的TCP包头部长度 - TCP头部固定的20字节长度)

    所以,如果tcp_header_len的值能达到最大值60,那么mss_now就能被设置为8。那么内核代码中,有办法让tcp_header_len达到最大值长度吗?随后我们回溯该变量:

     

     

    所以在Linux 4.15内核中,在用户不干预的情况下,内核是不会发出头部大小为60字节的TCP包。这就导致了MSS无法被设置为最小值8,最终导致该漏洞无法利用。

    总结

    我们来总结一下整个流程:

    1. 攻击者构造SYN包,自定义TCP头部可选参数MSS的值为48
    2. 靶机(受到攻击的机器)接收到SYN请求后,把SYN包中的数据保存在内存中,返回SYN,ACK包。
    3. 攻击者返回ACK包

    三次握手完成

    随后根据不同的服务,靶机主动向攻击者发送数据或者接收到攻击者的请求后向攻击者发送数据,这里就假设是一个nginx http服务。

    1. 攻击者向靶机发送请求:GET / HTTP/1.1

    2. 靶机接收到请求后,首先计算出tcp_header_len,默认等于20字节,在内核配置sysctl_tcp_timestamps开启的情况下,增加12字节,如果编译内核的时候选择了CONFIG_TCP_MD5SIG,会再增加18字节,也就是说tcp_header_len的最大长度为50字节。

    3. 随后需要计算出mss_now = 48 - 50 + 20 = 18

    这里假设一下该漏洞可能利用成功的场景:有一个TCP服务,自己设定了TCP可选参数,并且设置满了40字节,那么攻击者才有可能通过构造SYN包中的MSS值来对该服务进行Dos攻击。

    随后我对Linux 2.6.29至今的内核进行审计,mss_now的计算公式都一样,tcp_header_len长度也只会加上时间戳的12字节和md5值的18字节。

    ----- 2019/07/03 UPDATE -----

    经过@riatre大佬的指正,我发现上述我对tcp_current_mss函数的分析中漏了一段重要的代码:

     

    tcp_established_options函数的代码中,除了12字节的时间戳,20字节的md5,还有对SACK长度的计算,在长度不超过tcp可选项40字节限制的前提下,公式为:size = 4 + 8 * opts->num_sack_blocks

     

    所以凑齐40字节的方法是:12字节的时间戳 + 8 * 3(opts->num_sack_blocks)

    变量opts->num_sack_blocks表示从对端接受的数据包中丢失的数据包数目

    所以在这里修改一下总结中后三步的过程:

    1. 攻击者向靶机发送一段正常的HTTP请求
    2. 靶机接收到请求后,会发送HTTP响应包,如上面的wireshark截图所示,响应包会按照36字节的长度分割成多分
    3. 攻击者构造序列号带有缺漏的ACK包(ACK包需要带一些数据)
    4. 服务器接收到无序的ACK包后,发现产生了丢包的情况,所以在后续发送的数据包中,都会带上SACK选项,告诉客户端,那些数据包丢失,直到TCP链接断开或者接收到响应序列的数据包。

    效果如下图所示:

    因为算上时间戳,TCP SACK选项里最多只能包含3段序列编号,所以只要发送4次ACK包,就能把MSS设置为8。

    部分scapy代码如下:

    因为现在已经能满足mss_now=8的前提,后续将会对该漏洞进行进一步的分析。

    参考

    1. https://github.com/Netflix/security-bulletins/blob/master/advisories/third-party/2019-001.md
    2. https://paper.seebug.org/959/
    3. https://paper.seebug.org/960/

     

    Paper

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

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • Vim/Neovim 基于 modeline 的多个任意代码执行漏洞分析(CVE-2002-1377、CVE-2016-1248、CVE-2019-12735)

    2019-06-27

    作者:fenix@知道创宇 404 实验室
    日期:2019 年 6 月 11 日
    英文版本:https://paper.seebug.org/956/

    前言

    Vim 是从 vi 发展出来的一个文本编辑器。代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用,和 Emacs 并列成为类 Unix 系统用戶最喜欢的文本编辑器。Neovim 是一个基于 vim 源代码的重构项目。

    2019 年 06 月 04 日,Vim & neovim 被曝出任意代码执行漏洞。攻击者通过诱使受害者使用 vim 或者 neovim 打开一个精心制作的文件,可以在目标机器上执行任意命令。

    该漏洞是由于启用了 modeline 模式导致的,Vim & neovim 历史上也多次曝出和 modeline 相关的漏洞。

    原作者已经分析的很清楚了,本文权当总结一下,顺便对历史曝出的多个漏洞做一次完整的分析。(在 vim 环境下,neovim 类似)

    modeline 详解

    既然都是和 modeline 相关的漏洞,那就有必要知道 modeline 是什么。

    vim 一共有 4 种模式:正常模式、插入模式、命令模式、可视模式。

    在正常模式中,按下 : 键,就可以进入命令模式。在命令模式中可以执行一些输入并执行一些 vim 或插件提供的指令,就像在 shell 里一样。这些指令包括设置环境、文件操作、调用某个功能、执行命令等等。例如设置不显示行号:

    如果有很多偏好设置,每次打开文件都手动设置就会显得很繁琐,这时候 .vimrc 就派上用场了,在启动 vim 时,当前用户根目录下的 .vimrc 文件会被自动加载。

    .vimrc 中的设置会对打开的所有文件生效,不便于对单个文件作个性化设置,modeline 应运而生。

    vim 的 modeline 可以让你针对每个文件进行文件级别的设置,这些设置是覆盖当前用户的 .vimrc 中的设置的。vim 默认关闭了 modeline,在 .vimrc 末尾追加 set modeline 即可打开。

    如果 modeline 打开,vim 在打开文件时会解析文件开头及末尾符合一定格式的设置行。

    格式一:

    格式二:

    为了安全考虑,在 modeline 的设置中只支持 set 命令。

    特殊的,foldexpr,formatexpr,includeexpr,indentexpr,statusline,foldtext 等选项的值可以是一个表达式,如果选项是在 modeline 中设置,表达式在沙箱中执行。沙箱实质上就是对表达式所能实现的功能做了限制,如在沙箱中不能执行 shell 命令、不能读写文件、不能修改缓冲区等等,如下:

    vim 对于沙箱的实现也很简单。

    沙箱检查函数 check_secure():

    在 libcall、luaeval 等危险指令的开头进行沙箱检查,如果发现在沙箱中调用,直接 return 掉。

    历史曝出的几个 rce 漏洞中,CVE-2002-1377 和 CVE-2019-12735 都是由于存在部分指令没有检查沙箱,导致在 modeline 模式中被滥用从而任意命令执行。下面将一一分析。

    CVE-2002-1377

    2002 年曝出的 vim 任意代码执行漏洞,影响 6.0、6.1 版本。太过古老,环境难以重现,简单说下原理。PoC 如下:

    利用 libcall 指令调用 libc 库中的 system 函数实现任意命令执行。

    现在添加了沙箱检查,modeline 下已经用不了 libcall 了:

    CVE-2016-1248

    8.0.0056 之前的 vim 未正确验证 filetype、syntax 、keymap 选项的值,受害者在 modeline 开启下打开特制的文件,则可能导致执行任意代码。

    从 github 克隆代码,checkout 到 v8.0.0055 分支,编译安装。.vimrc 的配置如下:

    验证 PoC :

    set verbose=20开启所有日志,看下调用链:

    autocommand 即“自动命令”,在发生某些事件时自动执行,类似于钩子函数。

    比如我们在命令模式中输入 :set syntax=python, vim 就会在相应目录中寻找和 python syntax 相关的 vmscript 并加载。

    如果我们在 modeline 中设置了 filetype 或者 syntax,会执行 au! FileType * exe "set syntax=" . expand("<amatch>") 自动完成上述过程。首先删除所有和 FileType 相关联的自动命令,然后调用 exe (即 execute) 执行 set syntax=filetype。execute 用于执行一个表达式字符串,由于未对 filetype 过滤,造成了命令注入。

    相关代码在 /usr/local/share/vim/vim80/syntax/syntax.vim:

    patch 8.0.0056 增加了对名称的校验。

    CVE-2019-12735

    最近刚曝出来,影响 Vim < 8.1.1365,Neovim < 0.3.6。和 CVE-2002-1377 原理类似,找到了一个新的绕过沙箱执行命令的点。source 指定的定义如下:

    :so! filepath 可以从一个文件加载 vim 命令。

    构造 PoC,将待执行的命令放在 text 部分,so! % 加载当前文件。

    [text]{white}{vi:|vim:|ex:}[white]{options}

    补丁对 source 指令添加了沙箱检查。

    总结

    Windows 记事本都任意代码执行了,Vim 怎么能被比下去 … 漏洞无处不在,谨慎打开任何来历不明文件。

    参考链接

    https://github.com/numirias/security/blob/master/doc/2019-06-04_ace-vim-neovim.md

    https://github.com/vim/vim/commit/d0b5138ba4bccff8a744c99836041ef6322ed39a


    Paper

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

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • Mybb 18.20 From Stored XSS to RCE 分析

    2019-06-27

    作者:LoRexxar'@知道创宇404实验室
    日期:2019年6月12日
    英文版本:https://paper.seebug.org/954/

    2019年6月11日,RIPS团队在团队博客中分享了一篇MyBB <= 1.8.20: From Stored XSS to RCE,文章中主要提到了一个Mybb18.20中存在的存储型xss以及一个后台的文件上传绕过。

    其实漏洞本身来说,毕竟是需要通过XSS来触发的,哪怕是储存型XSS可以通过私信等方式隐藏,但漏洞的影响再怎么严重也有限,但漏洞点却意外的精巧,下面就让我们一起来详细聊聊看...

    漏洞要求

    储存型xss

    • 拥有可以发布信息的账号权限
    • 服务端开启视频解析
    • <=18.20

    管理员后台文件创建漏洞

    • 拥有后台管理员权限(换言之就是需要有管理员权限的账号触发xss)
    • <=18.20

    漏洞分析

    在原文的描述中,把多个漏洞构建成一个利用链来解释,但从漏洞分析的角度来看,我们没必要这么强行,我们分别聊聊这两个单独的漏洞:储存型xss、后台任意文件创建。

    储存型xss

    在Mybb乃至大部分的论坛类CMS中,一般无论是文章还是评论又或是的什么东西,都会需要在内容中插入图片、链接、视频等等等,而其中大部分都是选择使用一套所谓的“伪”标签的解析方式。

    也就是说用户们通过在内容中加入[url][img]等“伪”标签,后台就会在保存文章或者解析文章的时候,把这类“伪”标签转化为相应的<a><img>,然后输出到文章内容中,而这种方式会以事先规定好的方式解析和处理内容以及标签,也就是所谓的白名单防御,而这种语法被称之为bbcode

    这样一来攻击者就很难构造储存型xss了,因为除了这些标签以外,其他的标签都不会被解析(所有的左右尖括号以及双引号都会被转义)。

    正所谓,有人的地方就会有漏洞。

    在这看似很绝对的防御方式下,我们不如重新梳理下Mybb中的处理过程。

    /inc/class_parse.php line 435 的 parse_mycode函数中就是主要负责处理这个问题的地方。

    当服务端接收到你发送的内容时,首先会处理解析[ img ]相关的标签语法,然后如果开启了$this->options['allow_videocode'](默认开启),那么开始解析[ video ]相关的语法,然后是[list]标签。在488行开始,会对[url]等标签做相应的处理。

    我们把上面的流程简单的具象化,假设我们在内容中输入了

    后台会首先处理[ video ],然后内容就变成了

    然后会处理[url]标签,最后内容变成

    乍一看好像没什么问题,每个标签内容都会被拼接到标签相应的属性内,还会被htmlspecialchars_uni处理,也没办法逃逸双引号的包裹。

    但假如我们输入这样的内容呢?

    首先跟入到函数/inc/class_parse.php line 1385行 mycode_parse_video

    链接经过parse_url处理被分解为

    然后在1420行,各个参数会被做相应的处理,由于我们必须保留=号以及/ 号,所以这里我们选择把内容放在fragment中。

    在1501行case youtube中,被拼接到id上

    最后id会经过一次htmlspecialchars_uni,然后生成模板。

    当然这并不影响到我们上面的内容。

    到此为止我们的内容变成了

    紧接着再经过对[url]的处理,上面的内容变为

    我们再把前面的内容简化看看,链接由

    变成了

    由于我们插入在iframe标签中的href被转变成了<a href="http://onload=alert();//">, 由于双引号没有转义,所以iframe的href在a标签的href中被闭合,而原本的a标签中的href内容被直接暴露在了标签中,onload就变成了有效的属性!

    最后浏览器会做简单的解析分割处理,最后生成了相应的标签,当url中的链接加载完毕,标签的动作属性就可以被触发了。

    管理员后台文件创建漏洞

    在Mybb的管理员后台中,管理员可以自定义论坛的模板和主题,除了普通的导入主题以外,他们允许管理员直接创建新的css文件,当然,服务端限制了管理员的这种行为,它要求管理员只能创建文件结尾为.css的文件。

    看上去好像并没有什么办法绕过,但值得注意的是,代码中先将文件名先写入了数据库中。

    紧接着我们看看数据库结构

    我们可以很明显的看到name的类型为varchar且长度只有30位。

    如果我们在上传的xml文件中构造name为tttttttttttttttttttttttttt.php.css时,name在存入数据库时会被截断,并只保留前30位,也就是tttttttttttttttttttttttttt.php.

    紧接着我们需要寻找一个获取name并创建文件的地方。

    在/admin/modules/style/themes.php 的1252行,这个变量被从数据库中提取出来。

    theme_stylesheet 的name作为字典的键被写入相关的数据。

    $mybb->input['do'] == "save_orders"时,当前主题会被修改。

    在保存了当前主题之后,后台会检查每个文件是否存在,如果不存在,则会获取name并写入相应的内容。

    可以看到我们成功的写入了php文件

    完成的漏洞复现过程

    储存型xss

    找到任意一个发送信息的地方,如发表文章、发送私信等....

    发送下面这些信息

    然后阅读就可以触发

    管理员后台文件创建漏洞

    找到后台加载theme的地方

    构造上传文件test.xml

    需要注意要勾选 Ignore Version Compatibility。

    然后查看Theme列表,找到新添加的theme

    然后保存并访问相应tid地址的文件即可

    补丁

    储存型xss

    这里的iframe标签的链接被encode_url重新处理,一旦被转义,那么[url]就不会被继续解析,则不会存在问题。

    管理员后台文件创建漏洞

    在判断文件名后缀之前,加入了字符数的截断,这样一来就无法通过数据库字符截断来构造特殊的name了。

    写在最后

    整个漏洞其实说到实际利用来说,其实不算太苛刻,基本上来说只要能注册这个论坛的账号就可以构造xss,由于是储存型xss,所以无论是发送私信还是广而告之都有很大的概率被管理员点击,当管理员触发之后,之后的js构造exp就只是代码复杂度的问题了。

    抛开实际的利用不谈,这个漏洞的普适性才更加的特殊,bbcode是现在主流的论坛复杂环境的解决方案,事实上,可能会有不少cms会忽略和mybb一样的问题,毕竟人才是最大的安全问题,当人自以为是理解了机器的一切想法时,就会理所当然得忽略那些还没被发掘的问题,安全问题,也就在这种情况下悄然诞生了...

     

    Paper

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

     

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • 如何打造自己的PoC框架-Pocsuite3-框架篇

    2019-05-15

    作者:w7ay@知道创宇404实验室
    English version: https://paper.seebug.org/914/
    相关阅读:如何打造自己的PoC框架-Pocsuite3-使用篇

    本节笔者将按照Pocsuite框架结构以及工程化实践,来实现一款自己的PoC框架。为了开一个好头,我们先取一个好听的名字,想威武霸气一些可以取上古神器之类的,诸如轩辕夏禹赤霄干将,若怀着对游戏的热爱也可以有山丘之王(Mountain King)剑圣(BladeMaster)月之女神(Priess Of the moon)。由于笔者比较懒,我们就取一个朴素的名字:AirPoc,中文名叫它"空气炮"吧。

    名称取好了,我们还要幻想一下大饼。这里请充分发挥想象力,幻想它的功能,你要记住,没有我们实现不了的功能,如果有,打死产品manager即可。

    这里不妨开下脑洞,为了组建兔子安全联盟,我们计划开发一款基于区块链的PoC验证框架AirPoc,限定只对"兔子安全联盟”范围内的网站进行安全检查,由一个AirPoc节点检查出了存在漏洞的地址,将URL和PoC共享到区块中,再由随机的其他节点验证,验证成功则获得"空气币",而被检测到的网站所有者则需要支付"空气币"作为报酬。

    虽然只是暂时的幻想,但是产品小哥哥也略带激动整理出了我们需要的功能。

    1. 使用简单,不要有太多的命令,可以跨平台使用
    2. 人多力量大,能让更多人参与进来的
    3. 能简单操作就能内置到其他产品上
    4. 验证速度与验证准确率极高!
    5. 我也不知道什么好,总之你跑起来能出东西就行!

    当然,这位产品小哥哥可能怕被打,没有将分布式,区块链的概念加入进来。

    具体细节

    下面就由笔者来具体实现由笔者兼职的产品manager随便一想(挖坑)的东西。我们逐一分析问题,并给出最后的解决方案。

    说到使用简单,我们就任性的选择使用Python了,不信你看看Python之父的头发。在安装了Python之后,也可以一份代码多处使用,但为了足够的简单与原生,我们决定尽量少使用Python的第三方包。而目前Python最新版为3.7,我们就以此为例。

    国外的众多开源安全项目都有不少人参与,像Metasploit

    image-20190425142853330

    Sqlmap

    image-20190425142829862

    Routersploit

    image-20190425142732368

    能贡献一份代码到上面可能是安全研究人员最想做的事情吧。

    所以笔者有个想法是AirPoc的PoC仓库可以开源到GitHub,并且能够在线调用上面的PoC,这样也不会为了PoC的更新而烦恼了。

    内置到其他产品也更是容易,如果是Python类的软件,可以直接把AirPoc当做包来调用,如果其他软件,AirPoc可以开放一个RPC接口提供使用,如果不想要Python的环境,也可以通过pyinstaller之类的工具打包,我们的设计原则是尽量不依赖其他第三方库,所以也会避免很多奇奇怪怪的问题。

    想要实现验证速度与验证准确率极高,我们要做好多线程或协程的并发模型,这里我们会在后面在详细叙述。

    最后,"我也不知道什么好,总之你跑起来能出东西就行!",如果上面的事情我们都做好了,这个应该就是水到渠成的了~

    AirPoc的框架

    在完成这个"宏伟计划"之前,我们也需要设计一下整体的代码框架。作为一名代码洁癖患者,一个良好的代码结构,是万里长征的第一步。我们建立如下的目录结构,env是虚拟环境,建立两个目录libpocslib用于存储之后的相关核心文件,pocs用于存储poc文件,和一个文件main.py用作初始入口。

    就像盖大楼需要打好地基,接下来完成基础框架,我们可以先不用写具体的功能,但是了解作为"地基"的函数的意义。如下,在main.py文件中如下代码,一个初始的框架就完成了。

     

    image-20190429110505553

    但是,正如你所见,版本号和我的比特币钱包的数字竟然差不多,我们还要给它加些料。

    单例模式

    在我们软件的初始化的工程中,我们需要得到很多环境相关的信息。比如当前执行的路径是哪?poc目录在哪?我们输出结果文件输出到哪个路径等等。

    它们有一个共同的特定是,它们只需要加载一次,在后面使用中直接拿来用就行了。这种模式在软件设计模式中有一个单独的名词,"单例模式"。

    幸运的是python的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。

    我们在lib目录里面新建一个data.py用于存储这些信息。同时将版本信息也放到这里来。

     

    为了更好的来表示这些常量,我们用PEP8标准里的规范,统一约定用大写和下划线来表示常量。为了说明与之前的区别,我们象征性的将VERSION减一个0,来表达我们的比特币又增长了10倍。

    动态加载

    在解决完我们相关的环境问题后,我们在看看如何动态加载模块。在具体细节里我们说过,我们期望PoC能够从本地或者远程网站(如GitHub)上加载。

    这里又得分成两种情况,如果是通过文件路径加载动态加载的模块,可以直接用__import__()来加载,但是如果要远程加载,可能就又会复杂一点,根据python的相关文档,我们要自己实现"查找器"与"加载器" https://docs.python.org/zh-cn/3/reference/import.html

    当然,你也可以从远程保存到本地后,按照本地加载模式进行加载。但是Pocsuite已经有完整的加载器代码了,我们可以直接拿来用。

    新建lib/loader.py文件

     

    具体如何实现的我们可以不用关心,我们只需要知道,其中我们可以用load_string_to_module来从源码中加载模块了。如果你有兴趣了解具体的实现,可以参考上面的python官方文档。

    规则的制定

    从文件或者远程加载好模块后,就可以准备运行的相关事宜了。我们需要对PoC做一个规则的统一约定,让程序更好的调用它们。

    你可以将规则定义的详细,也可以一切从简,主要是看使用场景。而前面也提到,为了保护"安全联盟"的安全问题,所以我们需要PoC更够比较简单的快速编写。

    同时我们还需要考虑如果PoC需要多个参数如何处理?笔者的规则是这样定义的。

     

    在PoC文件中定义一个verify函数用作验证使用,arg作为普通的参数传递,当需要传递较多的参数时,从kwargs中接收。在PoC验证成功后,也只需要返回一个字典即可,如果验证失败,返回FalseNone即可。字典内容由PoC编写者制定,给予编写者最大的灵活空间。

    但是注意!PoC的质量就需要依靠编写者的维护。

    V0.01

    我们最终要实现的目标是,设置好目标,程序自动加载指定的一个或多个PoC或全部的PoC,逐个检测目标。剩下的部分就是怎样将这些功能串联在一起了。

    前面我们已经实现了AirPoc的基础框架,现在只需要在其基础上具体实现功能即可。

    为了测试的方便,我们先在pocs目录下按照之前定义的规则建立两个简陋的PoC。

    image-20190429164121695

    image-20190429164143909

    现在,main.py中的代码如下

     

    我们的版本也来到了0.01,它已经是一个"成熟的”能自己跑PoC的框架了。

    image-20190429172118745

    多线程模型

    为了让我们的框架运行得更快一点,我们使用多线程来处理每个PoC,因为我们处理的任务大多是I/O密集型任务,所以我们也不用太纠结python是不是伪线程这个问题。

    多线程模型中最简单的一种是生产者/消费者的模型,启动多个线程来共同消费一个队列就行了。新建lib/threads.py

     

    值得注意的一点是,我们并没有使用Python线程中推荐的join()来阻塞线程,因为使用join()的话,python将无法响应用户输入的消息了,会导致Ctrl+C退出时没有任何响应,所以以while循环的方式来阻塞线程。

    接着将主程序改造成多线程的模式,将原start()中的"消费者"提取出来,单独用作一个函数,用队列接收数据即可。如下

     

    另外,线程数量是我们可配置的,我们将它改成从配置中读取。

     

    再次运行,会发现比以前快很多!

    image-20190430102952036

    统一网络请求

    这是我们整个框架的最后一个部分,如何来统一网络请求。有时我们需要让我们的PoC框架发出的网络请求中统一一下代理,UA头等等的设置,这需要我们框架进行统一的处理。在实现我们的目的之前,我们还需要在框架里做一个约定,约定我们的网络请求都需要统一使用requests来进行发包。开始时我们说到,我们会尽量不使用第三方模块,但是requests模块实在太好用了,我们将它排除在外...

    Python语言动态的机制,我们可以很容易在使用一个函数之前Hook它,将它原始的方法重定向到我们自定义的方法中,这是我们能够统一网络请求的一个前提。

     

    image-20190430112557108

    通过hook一个函数来达到我们自己的目的。

    像sqlmap这类工具,基于python内置的urllib模块,但是有大量的代码都在处理在了网络请求方面,甚至为了处理chunked发包的问题,hook重写了更底层的httplib库。

    pocsuite为了统一调度网络请求,hook了requests模块的相关方法。我们可以具体参考其中的代码。

    pocsuite3/lib/request/patch/__init__.py代码很清晰的说明了hook的函数

     

    如果你看过requests的源码,会知道这里面的重点是看它如何hook seesion函数的。

    pocsuite3/lib/request/patch/hook_request.py

     

    它重写了session_request函数的方法,让其中可以自定义我们自定义的文件头等信息。上述代码可能需要你看过requests才会对他有所理解,不过没关系,我们还是以拿来主义的精神直接用即可。

    为了达到此目的以及更好的优化框架结构,我们还需要做一些小调整。

    新建lib/requests.py

     

    同时在config中预留requests的接口

    image-20190430115617047

    以及init的时候执行我们的hook。

    image-20190430115652364

    我们新编写一个PoC,用这个网站测试一下 最后的效果 http://www.httpbin.org/get

    pocs/poc.py

     

    image-20190430130417913

    效果很好,但是如果加上https的网站,就有一个警告信息。

    image-20190430130730241

    同样参考Pocsuite的方法禁用掉warning信息

     

    最后有仪式感的将版本号变更为0.1,AirPoc的框架部分大体完成了。

    最后

    AirPoc的很多结构思想都来源于Pocsuite,如果直接阅读Pocsuite,也许能收获很多东西。目前AirPoc v0.1基础框架已经差不多完成了,已经可以从本地加载一个或多个PoC,进行批量测试。后面我们再尝试些更好玩的,如何验证无回显的情况,如何生成shellcode,以及如何操作回连的shell,敬请期待下节《功能篇》~。

    AirPoc下载:https://images.seebug.org/archive/airpoc.zip


    Paper

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

    作者:吴烦恼 | Categories:安全研究技术分享 | Tags:
  • WebLogic RCE(CVE-2019-2725)漏洞之旅

    2019-05-05

    作者:Badcode@知道创宇404实验室
    时间:2019年4月30日
    English version: https://paper.seebug.org/910/

    417

    2019年4月17日,CNVD 发布《关于Oracle WebLogic wls9-async组件存在反序列化远程命令执行漏洞的安全公告》,公告指出部分版本WebLogic中默认包含的wls9_async_response包,为WebLogic Server提供异步通讯服务。由于该WAR包在反序列化处理输入信息时存在缺陷,攻击者可以发送精心构造的恶意 HTTP 请求,获得目标服务器的权限,在未授权的情况下远程执行命令。

    418

    2019年4月18日,开始应急。因为这个漏洞当时属于0day,也没有补丁可以参考,只能参考公告内容一步一步来看了。首先看到公告里提到的wls9_async_response.war包,看下web.xml里的url。

    看到/AsyncResponseService,尝试访问一下,404。之后看到weblogic.xmlweblogic-webservices.xml

    访问下_async/AsyncResponseService

    可以正常访问,再结合公告中的漏洞处置建议,禁止 /_async/* 路径的URL访问,可以大概率猜测,漏洞入口在这里。

    weblogic-webservices.xml中有一个类,weblogic.wsee.async.AsyncResponseBean,跟进去这个类,发现在wseeclient.jar里面

    而后我在这个类里面的方法下断点,然后构造一个普通的SOAP消息,发送。

    断点没有debug到。最后我把wsee/async所有类的所有方法都下了断点,重新发送消息,成功在AsyncResponseHandler类中的handleRequest拦截到了。

    继续流程,String var2 = (String)var1.getProperty("weblogic.wsee.addressing.RelatesTo");这个步骤一直取不到值,导致流程结束。为了解决这个问题,翻了不少资料,最后找到一个类似的例子,可以使用<ads:RelatesTo>test</ads:RelatesTo>weblogic.wsee.addressing.RelatesTo赋值。

     

    之后流程就能够继续下去了,我一直以为漏洞的关键点在这里,因为这个wsee.async下面的几个类中有readObject方法,我一直尝试着通过AsyncResponseHandler跳到readObject方法,而后就卡在这里,后面的流程就不写了,对这个漏洞来说是错的,上面写的这些猜测和流程都是正确的。

    419

    2019年4月19日,和我一起应急的师傅给我发了一张截图。

    看到这截图里面的RelatesTo,我还以为之前的推测没有错,只是没有构造好。

    全局搜索UnitOfWorkChangeSet这个类,之后在这个类中下断点。

    根据截图,构造一个类似的,然后发送

    在这个类中debug到了。

    看到了日思夜想的readObject,有了反序列的点,自然要找利用链了,目前 WebLogic 下面 commoncollections 相关的利用链已经是无法使用了,WebLoigc 依赖的common-collections版本已经升级了,先找个Jdk7u21测试一下,将生成的 payload 转换成 byte,发送。

    可以看到,成功地执行了命令。但是这个利用链限制太大了,基本没啥用。我想起去年应急过的一个WebLogic 反序列漏洞,CVE-2018-3191,既然jdk7u21都不受黑名单限制,想来CVE-2018-3191也是一样可以利用的。

    猜测没有错误,CVE-2018-3191也是能够利用的,这个漏洞也终于有点"危害"了。和 pyn3rd 师傅讨论一下有没有其他利用链,仔细翻下黑名单,除了CVE-2018-3191,就只有新的jython利用链(CVE-2019-2645)了,由 Matthias Kaiser大佬提交的,但是目前这个还有没有公开,所以这个利用链也没法使用。

    有了正确答案,就可以看下之前的猜测哪里出了问题。

    回到AsyncResponseHandler类中的handleRequesthandleRequest的上一步,HandlerIterator类中的handleRequest方法

    会遍历this.handlers,然后调用每个handlerhandleRequest去处理用户传入的SOAP Message。

    可以看到,AsyncResponseHandler仅仅只是21个handler之中的一个,而weblogic.wsee.addressing.RelatesTo的赋值就是在ServerAddressingHandler中完成的,有兴趣的可以去跟一下。这里面有一个非常重要的handler--WorkAreaServerHandler,看名字可能觉得眼熟,看到里面的handleRequest方法可能就不淡定了。

    之后的流程就和CVE-2017-10271是一样的了,关于这个漏洞的分析可以参考廖师傅的文章

    跟到这里就可以看出来了,这个url只是CVE-2017-10271漏洞的另外一个入口而已。这也是后期导致假PoC泛滥的一个原因。整个流程大概如下:

    那么问题来了,这个PoC是如何绕过CVE-2017-10271的黑名单的呢?

    首先来看一下CVE-2017-10271的补丁,会将传入的数据先调用validate校验,通过之后才交给XMLDecoder

     

    可以看到,objectnewmethod这些标签都被拦截了,遇到直接抛出错误。void标签后面只能跟indexarray标签后面可以跟class属性,但是类型只能是byte类型的。其中,过滤object标签是CVE-2017-3506的补丁,剩下的过滤是针对CVE-2017-10271的补丁。

    如果仔细看了黑名单的,就不难发现,外面流传的很多PoC都是假的,就是新url入口+老的payload,这样的组合是没有办法绕过这个黑名单的。

    绕过这个黑名单的关键是class标签,可以从官方的文档来了解一下这个标签。

    class标签可以表示一个类的实例,也就是说可以使用class标签来创建任意类的实例。而class标签又不在WebLogic 的黑名单之内,这才是这个漏洞最根本的原因。4月26日,Oracle 发布这个漏洞的补丁,过滤了class标签也证实了这点。

    既然漏洞的原因是绕过了CVE-2017-10271的黑名单,那么wls-wsat.war也是应该受影响的。

    测试一下,没有问题。

    这说明,CNVD的公告写的影响组件不全,漏洞处置建议也写的不全面,要通过访问策略控制禁止 /_async/* 及 /wls-wsat/* 路径的URL访问才行,之后我们也同步给了CNVD,CNVD发了第二次通告

    421

    2019年4月21日,准备构造出这个漏洞的检测PoC,能够使用class标签来创建类的实例,我首先考虑的是构造java.net.Socket,这也引出了一个JDK版本的坑。我测试的是jdk6,参考之前的PoC,可以这么构造

    ceye成功接收到请求,也说明Socket实例创建成功了。

    我把上面的检测PoC在 jdk 7上测试,竟然失败了,一直爆找不到java.net.Socket这个类错误,让我一度以为这个漏洞只能在 jdk 6 下面触发,后来仔细对比,发现是换行符的问题,也就是这样写才对。

    不带换行符的在6和7下面都能生成实例。其实这个问题在最早测试 CVE-2018-3191 payload的时候就已经发生过,pyn3rd师傅问我xml payload是怎么生成的,我说用的拼接,直接System.out.println输出的,都带了换行符,我因为当时跑weblogic的jdk是jdk6,所以没有问题,但是 pyn3rd 师傅的环境是 jdk7 的,没测试成功,只觉得是PoC写法不同造成的问题,后来师傅自己解决了,这里也没沟通,埋下了一个大坑,导致我后面踩进去了。

    422

    2019年4月22日,pyn3rd 师傅测试 WebLogic 12.1.3没成功,发现是12的版本没有oracle.toplink.internal.sessions.UnitOfWorkChangeSet这个类,所以没办法利用。尝试着构造新的exp,目前的情况是,能够创建类的实例,但是调用不了方法。自然想起com.sun.rowset.JdbcRowSetImpl这个类。

    这个是CVE-2017-10271的一种触发方法。之前的黑名单提过,void标签后面只能跟index,所以上面这个payload肯定会被黑名单拦截。尝试使用class标签重写上面的payload。

    构造的过程中,在跟底层代码的时候,发现 jdk 6和 jdk 7处理标签的方式不同。

    jdk 6使用的是com.sun.beans.ObjectHandler

    能用的有stringclassnullvoidarrayjavaobject和一些基本类型标签(如int)。

    jdk7 使用的是com.sun.beans.decoder.DocumentHandler

    可以看到,和jdk6差异不小,例如,jdk 6不支持newproperty等标签。

    我在用jdk 6 的标签构造的时候,一直没构造成功,直到我看到jdk 7 的源码里面的property,这不就是我想要的么,而且这个标签还不在 WebLogic 的黑名单内。所以重写上面的payload如下

    可以看到,没有触发黑名单,成功的执行了命令,而且没有依赖 WebLogic 内部的包,10.3.6和12.1.3都可以通用。遗憾的是,这个payload的打不了 jdk 6的,因为 jdk 6 不支持 property标签。期望有大佬能写出6也能用的。

    423

    2019年4月23日,在CNVD发出通告,各大安全公司发出漏洞预警之后,之前提过的新url+老payload的这种模式的PoC和exp纷纷出炉。不仅是国内,国外也很热闹,很多人表示测试成功,但是都是在无补丁的情况下测试的。Oracle 官网下载的 WebLogic 都是没有安装补丁的,Oracle的补丁是单独收费的,如果安装了 CVE-2017-10271 的补丁,这些PoC和exp都是没有办法触发的,绕过不了黑名单。

    426

    2019年4月26日,Oracle 官方发布紧急补丁,并为该漏洞分配编号CVE-2019-2725。

    427

    2019年4月27日,pyn3rd 师傅说12.1.3版本的exp也有人弄出来了,用的是org.slf4j.ext.EventData

     

    看下这个类的构造方法,直接将传入的xml交给XMLdecoder处理,太粗暴了...

    相当于经过了两次XMLdecode,所以外层用<class>绕过,内层直接标记为纯文本,绕过第一次过滤,第二次 XMLdecode不经过WebLogic 黑名单,直接被JDK解析反序列化执行。

    这种exp也是最完美的,没有jdk版本限制,不需要外连,可惜的是只能打12.1.3版本。

    430

    2019年4月30日,在其他大佬手中看到了这个漏洞的其他利用方式,没有 weblogic和 jdk的版本限制,比上面的几种利用方式都更完善。这种利用方式我之前也看到过,就是Tenable 发的演示视频,当时没想明白,看了大佬的利用方式之后,才明白自己忽略了什么。构造方式可以参考CVE-2017-17485,我之前构造exp的时候也没有往这方面想,这或许就是黑哥说的积累不够吧。

    总结

    • 针对这次漏洞,Oracle 也是打破了常规更新,在漏洞预警后不久就发布了补丁,仍然是使用黑名单的方式修复。(吐槽一下,这么修复,这个功能还能用么?)
    • 此次的漏洞事件中,也看到了安全圈的乱象,漏洞都没有经过完全的验证,就直接发错误的分析文章和假PoC,误导大众。
    • 在这个漏洞应急的过程中,从无到有,从缺到圆,踩了很多坑,也学习到了很多姿势,也看到了自己和大佬的差距。最后感谢漏洞应急过程中几位师傅的交流和指点。

    参考链接

     

    Paper

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

    作者:吴烦恼 | Categories:安全研究技术分享 | Tags:
  • WebLogic CVE-2019-2647、CVE-2019-2648、CVE-2019-2649、CVE-2019-2650 XXE漏洞分析

    2019-05-05

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

    Oracle发布了4月份的补丁,详情见链接(https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html#AppendixFMW)

    @xxlegend 在《Weblogic CVE-2019-2647等相关XXE漏洞分析》分析了其中的一个XXE漏洞点,并给出了PoC。刚入手java不久,本着学习的目的,自己尝试分析了其他几个点的XXE并构造了PoC。下面的分析我尽量描述自己思考以及PoC构造过程,新手真的会踩很多莫名其妙的坑。感谢在复现与分析过程中为我提供帮助的小伙伴@Badcode,没有他的帮助我可能环境搭起来都会花费一大半时间。

    补丁分析,找到漏洞点

    根据JAVA常见XXE写法与防御方式(参考https://blog.spoock.com/2018/10/23/java-xxe/),通过对比补丁,发现新补丁以下四处进行了setFeature操作:

    应该就是对应的四个CVE了,其中ForeignRecoveryContext@xxlegend大佬已经分析过了,这里就不再分析了,下面主要是分析下其他三个点

    分析环境

    • Windows 10
    • WebLogic 10.3.6.0
    • Jdk160_29(WebLogic 10.3.6.0自带的JDK)

    WsrmServerPayloadContext 漏洞点分析

    WsrmServerPayloadContext修复后的代码如下:

    可以看到进行了setFeature操作防止xxe攻击,而未打补丁之前是没有进行setFeature操作的

    readExternal在反序列化对象时会被调用,与之对应的writeExternal在序列化对象时会被调用,看下writeExternal的逻辑:

    var1就是this.formENdpt,注意var5.serialize可以传入三种类型的对象,var1.getEndptElement()返回的是Element对象,先尝试新建一个项目构造一下PoC:

    结构如下

    test.xml内容如下,my.dtd暂时为空就行,先测试能否接收到请求:

    运行PoC,生成的反序列化数据xxe,使用十六进制查看器打开:

    发现DOCTYPE无法被引入

    我尝试了下面几种方法:

    • 在上面说到var5.serialize可以传入Document对象,测试了下,的确可以,但是如何使getEndptElement返回一个Document对象呢?
      • 尝试了自己创建一个EndpointReference类,修改getEndptElement返回对象,内容和原始内容一样,但是在反序列化时找不到我创建的类,原因是自己建的类package与原来的不同,所以失败了
      • 尝试像Python那样动态替换一个类的方法,貌似Java好像做不到...
    • 尝试了一个暴力的方法,替换Jar包中的类。首先复制出Weblogic的modules文件夹与wlserver_10.3\server\lib文件夹到另一个目录,将wlserver_10.3\server\lib\weblogic.jar解压,将WsrmServerPayloadContext.class类删除,重新压缩为weblogic.Jar,然后新建一个项目,引入需要的Jar文件(moduleswlserver_10.3\server\lib中所有的Jar包),然后新建一个与WsrmServerPayloadContext.class同样的包名,在其中新建WsrmServerPayloadContext.class类,复制原来的内容进行修改(修改只是为了生成能触发xml解析的数据,对readExternal反序列化没有影响)。WsrmServerPayloadContext.class修改的内容如下: