RSS Feed
更好更安全的互联网
  • 专项行动的意外收获—— 2020 年 9 月墨子(Mozi)僵尸网络分析报告

    2020-11-03

    作者:answerboy@知道创宇404积极防御实验室
    时间:2020年9月18日

    1.概述

    专项行动期间,某天各大蓝队群内都在交流最近是否收到很多来自印度的攻击流量,最初部分认为是红队在使用印度IP进行攻击。但很快发现事情好像并不是这么简单,通过对攻击Payload特征的分析,发现该攻击不是专项行动红队所发起,而是来自一个正在迅速扩张的僵尸网络——Mozi(墨子)僵尸网络。

    Mozi僵尸网络是于2019年底首次出现在针对路由器和DVR 的攻击场景上的一种P2P僵尸网络。主要攻击物联网(IoT)设备,包括网件、D-Link和华为等路由设备。它本质上是Mirai的变种,但也包含Gafgyt和IoT Reaper的部分代码,用于进行DDoS攻击、数据窃取、垃圾邮件发送以及恶意命令执行和传播。目前其规模已经迅速扩大,据统计目前已占到所有物联网(IoT)僵尸网络流量的90% 。

    近日知道创宇404积极防御实验室通过知道创宇云防御安全大数据平台监测到大量来自印度IP的攻击。经分析,其中大量的攻击来自Mozi僵尸网络,可能和近期印度Mozi僵尸网络大范围感染并传播有关。

    2.追溯分析

    2.1发现攻击

    近日,知道创宇404积极防御实验室监测到大量来自印度IP的Web攻击,试图通过远程命令执行下载Mozi.m、Mozi.a等恶意文件到被攻击设备上,且使用的User-Agent均为:“Hello, world”。使用的部分攻击Payload如下:

    图1-攻击日志

    通过对样本的分析确定该样本属于Mozi僵尸网络家族。

    2.2详细分析

    2.2.1 Mozi.m样本分析

    捕获到的样本信息:

    SHA256:bba18438991935a5fb91c8f315d08792c2326b2ce19f2be117f7dab984c47bdf

    ELF 头:

    Magic7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
    类别ELF32
    数据2 补码,大端序 (big endian)
    Version1 (current)
    OS/ABIUNIX - System V
    ABI 版本0
    类型EXEC (可执行文件)
    系统架构MIPS R3000
    版本0x1
    入口点地址0x41fb58
    程序头起点52 (bytes into file)

    样本通过UPX进行了加壳操作,且将p_info结构中的p_file_size和p_blocksize值擦除为了0,来增强自身的“安全性”。

    在成功感染目标设备之后,Mozi为进行自我保护,会通过防火墙阻断SSH、Telnet端口,以防止被其他僵尸网络入侵:

    图2-阻断22、2323端口通信

    根据感染的设备,修改防火墙策略放行不同的端口来保证自身的通信:

    图3-放行自身使用端口

    同时读取/proc/net/tcp和/proc/net/raw来查找并KILL掉使用1536和5888端口的进程:

    图4-Kill相关进程

    检查被感染的设备上是否存在Watchdog来避免重启:

    图5-检测Watchdog

    检查被感染的设备上是否存在/usr/bin/python,如果存在,则将进程名称更改为sshd,不存在则更改为dropbear,以此来迷惑被攻击者。

    图6-更改进程名

    分析过程中发现Mozi僵尸网络复用了部分Gafgyt家族僵尸网络的代码,其中内嵌了8个硬编码的公共节点信息,用于加入P2P网络,如下:

    图7-内置的节点

    在样本中还硬编码了一个使用XOR加密的配置文件及密钥:

    图8-配置文件

    使用硬编码的秘钥解密后得到如下配置数据: [ss]bot[/ss][hp]88888888[/hp][count]http://ia.51.la/go1?id = 19894027&pu =http%3a%2f%2fbaidu.com/[idp][/count]。

    新的Mozi节点向http://ia.51.la/发送HTTP请求,来注册自身。

    在通信流量中通过1:v4:JBls来标记是否为Mozi节点发起的通信。

    图9-通信标识

    所攻击的设备类型包括:GPON光纤设备、NetGear路由设备、华为HG532交换机系列、D-Link路由设备、使用Realtek SDK的设备、Vacron监控摄像机、斐讯路由器、 USR-G806 4G工业无线路由器等:

    图10-攻击的设备类型

    同时还在样本中发现硬编码的部分用户名和弱口令,用来对Telnet进行暴力破解攻击,以扩大感染和传播范围,硬编码的部分用户名和密码如下:

    图11-部分弱口令密码

    2.3攻击分布

    自9月以来知道创宇云防御共拦截了来自Mozi僵尸网络的151,952个IP的攻击,总计拦截攻击14,228,252次。与8月份相比,来自印度的攻击显著增加,拦截到的来自印度的攻击IP同比上涨了近30%,所拦截到的总攻击次数上涨了近42%。下图为知道创宇404积极防御实验室自9月以来监测到的来自印度的攻击IP TOP10:

    图12-攻击IP TOP10

    通过对捕获到的日志分析,对所有被攻击的行业进行分类统计,其中被攻击占比最高的为政府事业单位,以及部分部委机关系统及网站。这部分系统在所有被攻击的行业中占比达到45%。如下:

    图13-被攻击行业分布

    目前Mozi僵尸网络仍在快速扩张,且呈上升趋势,临近十一重保,各单位仍需提高警惕,做好安全防护措施,尤其是各级政府事业单位以及部委机关单位,应提前做好相关设备的安全检查,避免被僵尸网络入侵感染。

    3.防护建议

    • 1.设备关闭不必要的端口,对使用的端口号进行更改;
    • 2.定期更新系统补丁,及时修复相关漏洞;
    • 3.服务器切勿使用弱口令,避免被暴力破解;
    • 4.根据附件中的Payload阻断存在以下特征的通信;
    • 5.关注设备上的可疑进程和外连访问,尤其是近期来自印度的IP。

    4附:IoCs

    公共节点

    dht.transmissionbt.com:6881
    router.bittorrent.com:6881
    router.utorrent.com:6881
    ttracker.debian.org:6881
    212.129.33.59:6881(ZoomEye搜索结果
    82.221.103.244:6881(ZoomEye搜索结果
    130.239.18.159:6881(ZoomEye搜索结果
    87.98.162.88:6881(ZoomEye搜索结果

    部分Payload

    暴力破解使用的用户名及弱口令

    UsernamePassword
    admin00000000
    telnetadmin1111
    !!Huawei1111111
    admin1234
    root12345
    root123456
    keomeo2010vesta
    support2011vesta
    CMCCAdmin25802580
    e8telnet54321
    e8ehome1666666
    e8ehome7ujMko0admin
    user7ujMko0vizxv
    mother888888
    root88888888
    Administrator@HuaweiHgw
    serviceBrAhMoS@15
    supervisorCMCCAdmin
    guestCUAdmin
    admin1Fireitup
    administratorGM8182
    ubntPhrQjGzk
    techPon521
    adminZte521
    adminadmin
    telnetadmin1234
    adminHW
    adminpass
    anko
    cat1029
    chzhdpl
    conexant
    default
    dreambox
    e2008jl
    e8ehome
    e8ehome1
    e8telnet
    epicrouter
    fucker
    gpon
    guest
    gw1admin
    h@32LuyD
    hg2x0
    hi3518
    ikwb
    juantech
    jvbzd
    keomeo
    klv123
    klv1234
    meinsm
    pass
    password
    plumeria0077
    r@p8p0r+
    realtek
    root
    service
    smcadmin
    supervisor
    support
    system
    tech
    telnet
    telnetadmin
    ubnt
    user
    v2mprt
    vizxv
    xJ4pCYeW
    xc3511
    xmhdipc
    zlxx
    zte

    SHA256

    5.参考链接:

    https://kb.cert.org/vuls/id/582384/https://www.cebnet.com.cn/20180302/102469557.htmlhttps://cloud.tencent.com/developer/article/1366157


    Paper

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

    作者:江 | Categories:技术分享漏洞分析 | Tags:
  • CVE-2019-0808 从空指针解引用到权限提升

    2020-11-03

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

    前言

    选择这个漏洞的原因是和之前那个cve-2019-5786是在野组合利用的,而且互联网上这个漏洞的资料也比较多,可以避免在踩坑的时候浪费过多的时间。

    首先跟据 Google 的博客,我们可以了解到这个漏洞在野外被用作在windows7 32位系统上的浏览器沙盒逃逸,并且可以定位到漏洞函数 win32k!MNGetpItemFromIndex 。

    在复现漏洞之前有几个问题浮现出来了,首先这个漏洞被用作沙盒逃逸,那么浏览器沙盒逃逸有哪几种方式?这个漏洞除了沙盒逃逸还可以用来做什么?其次空指针解引用的漏洞如何利用?这些可以通过查阅相关资料来自行探索。

    从poc到寻找漏洞成因

    在我分析这个漏洞的时候已经有人公布了完整的利用链,包括该漏洞的 poc 、 exp 和浏览器利用的组合拳。但是本着学习的目的,我们先测试一下这个 poc ,看下漏洞是如何触发的。搭建双机调试环境之后,运行 poc 导致系统 crash ,通过调试器我们可以看到

    加载符号之后查看一下栈回溯.

    可以看到大概是在 NtUserMNDragOver 之后的调用流程出现了问题,可能是符号问题我在查看了 Google 的博客之后没有搜索到 MNGetpItemFromIndex 这个函数,从栈回溯可以看到最近的这个函数是 MNGetpItem ,大概就是在这个函数里面。

    大概看了下函数触发顺序之后,我们看下poc的代码是如何触发crash的。首先看下poc的代码流程。

    首先获取了两个函数的地址 NtUserMNDragOver 和 NtAllocateVirtualMemory ,获取这两个函数的地址是因为参考栈回溯中是由 win32k!NtUserMNDragOver 函数中开始调用后续函数的,但是这个函数没有被导出,所以要通过其他函数的地址来导出。NtAllocateVirtualMemory函数是用来后续分配零页内存使用的。

    然后设置Hook EVENT_SYSTEM_MENUPOPUPSTART事件和WH_CALLWNDPROC消息。

    之后设置了两个无模式拖放弹出菜单(之前创建的,但是不影响poc的逻辑顺序),即hMenuRoot和hMenuSub。hMenuRoot会被设置为主下拉菜单,并将hMenuSub设置为其子菜单。

    创建了一个类名为#32768的窗口

    根据msdn我们可以查询到这个#32768为系统窗口,查的资料,因为CreateWindowA()并不知道如何去填充这些数据,所以直接调用多个属性被置为0或者NULL,包括创建的菜单窗口对象属性 tagPOPUPMENU->spmenu = NULL 。

    然后设置wndclass的参数,再使用CreateWindowsA来创建窗口。参数可以确保只能从其他窗口、系统或应用程序来接收窗口消息。

    接着,使用 TrackPopupMenuEx() 来弹出 hMenuRoot ,然后再通过 GetMessageW 来获取消息,然后在 WindowHookProc 函数中由于bOnDraging被初始化为FALSE,所以直接会执行 CallNextHookEx 。由于触发了EVENT_SYSTEM_MENUPOPUPSTART事件,然后传递给 DisplayEventProc ,由于 iMenuCreated 被初始化为0,所以进入0的分支。通过 SendMessageW() 将 WM_LMOUSEBUTTON 窗口消息发送给 hWndMain 来选择 hMenuRoot 菜单项(0x5, 0x5)。这样就会触发 EVENT_SYSTEM_MENUPOPUPSTART 事件,再次执行 DisplayEventProc ,由于刚刚 iMenuCreated 自增了,所以进入分支1,导致发送消息使鼠标挪到了坐标(0x6,0x6),然后 iMenuCreated 再次进行自增。然后在主函数的消息循环中iMenuCreated大于等于1进入分支,bOnDraging被置为TRUE,然后调用被我们导出的pfnNtUserMNDragOver函数。

    poc的流程已经分析完了,但是还是有部分的代码没有进入,比如 WindowHookProc 的 cwp->message == WM_MN_FINDMENUWINDOWFROMPOINT 分支,该分支通过 SetWindowLongPtrA 来改变窗口的属性。把默认的过程函数替换为SubMenuProc,SubMenuProc函数在收到 WM_MN_FINDMENUWINDOWFROMPOINT 消息后把过程函数替换为默认的过程函数,然后返回我们自定义的FakeMenu的句柄。

    接下来还要我们从漏洞的代码本身来分析。我们来看下调用pfnNtUserMNDragOver之后发生了什么,以及什么时候能收到 WM_MN_FINDMENUWINDOWFROMPOINT 这个消息。通过我们之前看到 windbg 的栈回溯中,我们在IDA中逐渐回溯函数,在 xxxMNMouseMove 函数中发现了 xxxMNFindWindowFromPoint 就在 xxxMNUpdateDraggingInfo 之前,xxxMNUpdateDraggingInfo 函数也是我们栈回溯中的函数。

    在函数 FindWindowFromPoint 函数中通过 xxxSendMessage 发送消息 235 也是 poc 中定义的 WM_MN_FINDMENUWINDOWFROMPOINT ,然后返回 v6 也就是获取的窗口句柄。然后在函数MNGetpItem中导致了空指针解引用得问题。

    从空指针解引用到任意代码执行

    触发了漏洞之后我们如何利用是个问题,首先的问题是把空指针解引用异常解决掉,在 windows7 版本上可以使用 ntdll!NtAllocateVirtualMemory 来分配零页内存。可以看到在申请零页内存之后不会产生异常导致crash了。

    为了进入到 MNGetpItem 的 if 分支中,我们需要对零页内存中的数据进行设置。并且通过查询资料得知,MNGetpItem 中的参数为 tagPOPUPMENU 结构,uDraggingIndex又可以从tagMSG的wParam取到,所以这个函数的返回值是在用户态可控的。

    进入 if 分支之后我们继续看程序流程,继续跟进 xxxMNSetGapState 函数。

    进入 xxxMNSetGapState 可以看到再次出现了我们之前的漏洞函数 MNGetpItem ,其中 v5 是 MNGetpItem 的返回值,v6 = v5,后续中有 v6 或的操作,MNGetpItem 的返回值又是用户态可控,利用这一点我们可以实现任意地址或0x40000000u的操作。

    如何把这个能力转化为任意地址读写呢?公开的exp中采用了窗口喷射的方法,类似于堆喷射创建大量的 tagWND 再通过 HMValidateHandle 函数来泄露内核地址来进行进一步的利用。HMValidateHandle 允许用户获得具有对象的任何对象的用户级副本。通过滥用此功能,将包含指向其在内核内存中位置的指针的对象(例如 tagWND(窗口对象))”复制“到用户模式内存中,攻击者只需获取它们的句柄即可泄漏各种对象的地址。这里又需要导出 HMValidateHandle 函数来进一步利用。再导出了 HMValidateHandle 之后可以泄露对象的地址了,然后我们利用窗口对象喷射的方法,寻找两个内存位置相邻的对象,通过修改窗口附加长度 tagWND+0x90->cbwndExtra 为0x40000000u来,再次修改第二个窗口对象的 strName.Buffer 指针,再通过设置 strName 的方式来达到任意地址写。

    有了任意代码写,如果使 shellcode 在内核模式中执行呢?可以利用 tagWND. bServerSideWindowProc 字段,如果被置位那话窗口的过程函数就实在内核模式的上下文中执行,最后可以实现用户态提权。

    后记

    通过这个漏洞的分析和复现也学到了不少在内核模式下的操作。分析到这里已经算结束了,但是如何达到在野外实现的浏览器沙盒逃逸的功能,还有之前提出的问题都是还需要思考的。那我们通过这个漏洞的复现及利用过程,还要思考这个漏洞是如何被发现的,是否可以通过poc中的一些功能来 fuzz 到同样的空指针解引用,以及我们如何去寻找这类漏洞。

    参考链接

    https://security.googleblog.com/2019/03/disclosing-vulnerabilities-to-protect.html

    https://github.com/ze0r/cve-2019-0808-poc/


    Paper

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

    作者:江 | Categories:安全研究漏洞分析 | Tags:
  • .Net 反序列化之 ViewState 利用

    2020-11-03

    作者:HuanGMz@知道创宇404实验室
    时间:2020年10月30日

    .NET 相关漏洞中,ViewState也算是一个常客了。Exchange CVE-2020-0688,SharePoint CVE-2020-16952 中都出现过ViewState的身影。其实ViewState 并不算漏洞,只是ASP.NET 在生成和解析ViewState时使用ObjectStateFormatter 进行序列化和反序列化,虽然在序列化后又进行了加密和签名,但是一旦泄露了加密和签名所使用的算法和密钥,我们就可以将ObjectStateFormatter 的反序列化payload 伪装成正常的ViewState,并触发ObjectStateFormatter 的反序列化漏洞。

    加密和签名序列化数据所用的算法和密钥存放在web.confg 中,Exchange 0688 是由于所有安装采用相同的默认密钥,而Sharepoitn 16952 则是因为泄露web.confg 。

    .NET 反序列化神器 ysoserial.net 中有关于ViewState 的插件,其主要作用就是利用泄露的算法和密钥伪造ViewState的加密和签名,触发ObjectStateFormatter 反序列化漏洞。但是我们不应该仅仅满足于工具的使用,所以特意分析了ViewState 的加密和签名过程作成此文,把工具用的明明白白的。

    初接触.NET,文中谬误纰漏之处在所难免,如蒙指教不胜感激。

    1. 调试.Net FrameWork

    1.1 .Net 源码

    对于刚接触.Net反序列化,甚至刚接触C#的朋友来说,有一个舒适方便的调试环境实在是太重要了。这里就简单介绍一下如何进行.net framework 的底层调试。

    .Net Framework 已经被微软开源了,你可以在官方网站上下载源码或者直接在线浏览。目前开源的版本包括 .Net 4.5.1 到 4.8。但是要注意,虽然微软开源了.Net 的源码,以及相应的VS项目文件,但是只能用于代码浏览,而无法进行编译。因为缺少重要组件(包括xaml文件和资源文件)。

    1.2 调试

    微软官文档有说明如何使用VS进行.Net源码的调试。其原理大概是通过pdb+源码的方式来进行单步调试。但经过实际尝试,发现并不是所有.net 程序集文件都有完整的pdb文件,其中一部分程序集的pdb是没有源码信息的。也就是说,只有一部分的程序集可以通过vs进行单步调试。

    细节参考以下连接:https://referencesource.microsoft.com/setup.html

    支持源码调试的程序集列表为:https://referencesource.microsoft.com/indexedpdbs.txt

    在放弃使用vs进行调试后,我发现还可以使用dnspy 进行.net底层调试。dnspy 是一个开源的.Net反编译工具,与经典工具Reflector相比,它不仅可以用于反编译,还可以借助反编译直接进行调试。dnspy 的github链接在这里。可以下载源码进行编译,也可以直接下载编译好的版本,不过要注意满足它要求的.net framework 版本。

    设置环境变量 COMPLUS_ZapDisable=1

    为什么要设置这个环境变量,为了禁用所有NGEN映像(* .ni.dll)的使用。

    假如你的windows服务器上安装有IIS服务,并且上面运行一个网站。使用浏览器打开该网站,这会使IIS在后台创建一个工作进程,用于运行该网站。这时我们用 process explore去查看 w3wp.exe 进程加载的dll,你会发现为什么程序集后面都有一个.ni的后缀。System.Web.dll 变为了 System.Web.ni.dll ,并且该dll的描述中还特意写了 "System.Web.dll"。其实这就是在使用.Net的优化版代码。

    设置环境变量 COMPLUS_ZapDisable=1 ,重启windows(一定要重启,因为重启IIS服务才能应用到我们设置的新环境变量)。仍然用ie打开网站,然后使用Process explore去查看w3wp.exe,这时你就会发现:网站工作进程加载的程序集变回了我们所熟知的System.Web.dll。

    注意1:设置环境变量后要重启

    注意2:如果找不到w3wp.exe,使用管理员运行process explore。

    使用dnspy 进行调试

    首先我们用process explore检查w3wp.exe加载的程序集所在的位置。因为你的系统上可能安装有多个版本的.Net或者是不同位数的.Net。如果你在dnsPy 中打开了错误的程序集,你在上面下断点的时候会提示你:无法中断到该断点,因为没有加载该模块。

    选择32位或者64位的 dnspy(与被调试进程匹配),以管理员权限启动。随便找一个程序集,比如System.Web.dll,点开后我们看他第一行中所写的路径是否与目标进程加载的程序集相同:

    如果不相同,左上方 文件->全部关闭,然后 文件->打开列表,从中选择一个版本合适的 .Net 。

    然后上方 调试->附加到进程,选择w3wp.exe,如果有多个进程,我们可以通过进程号来确定。那么如何判断哪一个进程是我们需要的呢?方法有很多种,你可以通过 process explore 查看w3wp.exe的启动命令,看哪个是运行目标网站的工作进程。又或者,以管理员权限启动cmd,进入 C:\Windows\System32\inetsrv,然后运行appcmd list wp。

    我们可以看到进程号和对应的网站集名称。

    然后就是给目标函数下断点,刷新页面,会中断到断点。

    2. ViewState基础知识

    在我们尝试利用ViewState反序列化之前,我们需要一些了解相关的知识。

    ASP.NET是由微软在.NET Framework框架中所提供,开发Web应用程序的类别库,封装在System.Web.dll文件中,显露出System.Web名字空间,并提供ASP.NET网页处理、扩展以及HTTP通道的应用程序与通信处理等工作,以及Web Service的基础架构。

    也就是说,ASP.NET 是.NET Framework 框架提供的一个Web库,而ViewState则是ASP.NET所提供的一个极具特点的功能。

    出现ViewState的原因

    HTTP模型是无状态的,这意味着,每当客户端向服务端发起一个获取页面的请求时,都会导致服务端创建一个新的page类的实例,并且一个往返之后,这个page实例会被立刻销毁。假如服务端在处理第n+1次请求时,想使用第n次传给服务器的值进行计算,而这时第n次请求所对应的page实例早已被销毁,要去哪里找上一次传给服务器的值呢?为了满足这种需求,就出现了多种状态管理技术,而VewState正是ASP.NET 所采用的状态管理技术之一。

    什么是视图状态及其在ASP.NET中的工作方式

    ViewState是什么样的?

    要了解ViewState,我们要先知道什么叫做服务器控件。

    ASP.NET 网页在微软的官方名称中,称为 Web Form,除了是要和Windows Forms作分别以外,同时也明白的刻划出了它的主要功能:“让开发人员能够像开发 Windows Forms 一样的方法来发展 Web 网页”。因此 ASP.NET Page 所要提供的功能就需要类似 Windows Forms 的窗体,每个 Web Form 都要有一个< form runat="server" >< /form >区块,所有的 ASP.NET 服务器控件都要放在这个区域中,这样才可以让 ViewState 等服务器控制能够顺畅的运作。

    无论是HTML服务器控件、Web服务器控件 还是 Validation服务器控件,只要是ASP.NET 的服务器控件,都要放在< form runat="server" >< /form >的区块中,其中的属性 runat="server" 表明了该表单应该在服务端进行处理。

    ViewState原始状态是一个 字典类型。在响应一个页面时,ASP.NET 会把所有控件的状态序列化为一个字符串,然后作为 hidden input 的值 插入到页面中返还给客户端。当客户端再次请求时,该hidden input 就会将ViewState传给服务端,服务端对ViewState进行反序列化,获得属性,并赋给控件对应的值。

    ViewState的安全性:

    在2010年的时候,微软曾在《MSDN杂志》上发过一篇文章,讨论ViewState的安全性以及一些防御措施。文章中认为ViewState主要面临两个威胁:信息泄露和篡改。

    信息泄露威胁:

    原始的ViewState仅仅是用base64编码了序列化后的binary数据,未使用任何类型的密码学算法进行加密,可以使用LosFormatter(现在已经被ObjectStateFormatter替代)轻松解码和反序列化。

    反序列化的结果实际上是一组System.Web.UI.Pair对象。

    为了保证ViewState不会发生信息泄露,ASP.NEt 2.0 使用 ViewStateEncryptionMode属性 来启用ViewState的加密,该属性可以通过页面指令或在应用程序的web.config 文件中启用。

    ViewStateEncryptionMode 可选值有三个:Always、Never、Auto

    篡改威胁:

    加密不能防止篡改 ,即使使用加密数据,攻击者仍然有可能翻转加密书中的位。所以要使用数据完整性技术来减轻篡改威胁,即使用哈希算法来为消息创建身份验证代码(MAC)。可以在web.config 中通过EvableViewStateMac来启用数据校验功能。

    注意:从.NET 4.5.2 开始,强制启用ViewStateMac 功能,也就是说即使你将 EnableViewStateMac设置为false,也不能禁止ViewState的校验。安全公告KB2905247(于2014年9月星期二通过补丁程序发送到所有Windows计算机)将ASP.NET 设置为忽略EbableViewStateMac设置。

    启用ViewStateMac后的大致步骤:

    (1)页面和所有参与控件的状态被收集到状态图对象中。

    (2)状态图被序列化为二进制格式

    a. 密钥值将附加到序列化的字节数组中。
    b. 为新的序列化字节数组计算一个密码哈希。
    c. 哈希将附加到序列化字节数组的末尾。

    (3) 序列化的字节数组被编码为base-64字符串。

    (4)base-64字符串将写入页面中的__VIEWSTATE表单值。

    利用ViewState 进行反序列化利用

    其实ViewState 真正的问题在与其潜在的反序列化漏洞风险。ViewState 使用ObjectStateFormatter 进行反序列化,虽然ViewState 采取了加密和签名的安全措施。但是一旦泄露web.config,获取其加密和签名所用的密钥和算法,我们就可以将ObjectStateFormatte 的反序列化payload 进行同样的加密与签名,然后再发给服务器。这样ASP.NET在进行反序列化时,正常解密和校验,然后把payload交给ObjectStateFormatter 进行反序列化,触发其反序列化漏洞,实现RCE。

    3. web.config 中关于ViewState 的配置

    ASP.NET 通过web.config 来完成对网站的配置。

    在web.config 可以使用以下的参数来开启或关闭ViewState的一些功能:

    enableViewState: 用于设置是否开启viewState,但是请注意,根据 安全通告KB2905247 中所说,即使在web.config中将enableViewState 设置为false,ASP.NET服务器也始终被动解析 ViewState。也就是说,该选项可以影响ViewState的生成,但是不影响ViewState的被动解析。实际上,viewStateEncryptionMode也有类似的特点。

    enableViewStateMac: 用于设置是否开启ViewState Mac (校验)功能。在 安全通告KB2905247 之前,也就是4.5.2之前,该选项为false,可以禁止Mac校验功能。但是在4.5.2之后,强制开启ViewState Mac 校验功能,因为禁用该选项会带来严重的安全问题。不过我们仍然可以通过配置注册表或者在web.config 里添加危险设置的方式来禁用Mac校验,详情见后面分析。

    viewStateEncryptionMode: 用于设置是否开启ViewState Encrypt (加密)功能。该选项的值有三种选择:Always、Auto、Never。

    • Always表示ViewState始终加密;
    • Auto表示 如果控件通过调用 RegisterRequiresViewStateEncryption() 方法请求加密,则视图状态信息将被加密,这是默认值;
    • Never表示 即使控件请求了视图状态信息,也永远不会对其进行加密。

    在实际调试中发现,viewStateEncryptionMode 影响的是ViewState的生成,但是在解析从客户端提交的ViewState时,并不是依据此配置来判断是否要解密。详情见后面分析。

    在web.config 中通过machineKey节 来对校验功能和加密功能进行进一步配置:

    例子:

    其中的validationKey 和 decryptionKey 分别是校验和加密所用的密钥,validationdecryption则是校验和加密所使用的算法(可以省略,采用默认算法)。校验算法包括: SHA1、 MD5、 3DES、 AE、 HMACSHA256、 HMACSHA384、 HMACSHA512。加密算法包括:DES、3DES、AES。 由于web.config 保存在服务端上,在不泄露machineKey的情况下,保证了ViewState的安全性。

    了解了一些关于ViewState的配置后,我们再来看一下.NET Framework 到底是如何处理ViewState的生成与解析的。

    4. ViewState的生成和解析流程

    根据一些先验知识,我们知道ViewState 是通过ObjectStateFormatter的SerializeDeserialize 来完成ViewState的序列化和反序列化工作。(LosFormatter 也用于ViewState的序列化,但是目前其已被ObjectStateFormatter替代。LosFormatter的Serialize 是直接调用的ObjectStateFormatter 的Serialize)

    ObjectStateFormatter 位于System.Web.UI 空间,我们给他的 Serialize函数下个断点(重载有多个Serialize函数,注意区分)。使用dnspy 调试,中断后查看栈回溯信息:

    通过栈回溯,我们可以清晰的看到Page类通过调用 SaveAllState 进入到ObjectStateFormatter的 Seralize 函数。

    4.1 Serialize 流程

    查看Serialize 函数的代码(这里我使用.Net 4.8 的源码,有注释,更清晰):

    在函数开头处,调用了另一个重载的Serialzie函数,作用是将stateGraph 序列化为binary数据:

    之后进入else分支:

    这里有两个重要标志位, _page.RequiresViewStateEncryptionInternal 和 _page.EnableViewStateMac。这两个标志位决定了序列化的Binary数据 是进入 MachineKeySection.EncryptOrDecryptData()函数还是 MachineKeySection.GetEncodedData()函数。

    其中EncryptOrDecryptData() 函数用于加密以及可选择的进行签名(校验),而GetEncodedData() 则只用于签名(校验)。稍后我们再具体分析这两个函数,我们先来研究一下这两个标志位。

    这两个标志位决定了服务端产生的ViewState采取了什么安全措施。这与之前所描述的web.config 中的EnableViewStateMac 和 viewStateEncryptionMode的作用一致。

    _page.RequiresViewStateEncryptionInternal 来自这里:

    其中的ViewStateEncryptionMode 应当是直接来自web.config。所以是否进入 MachineKeySection.EncryptOrDecryptData 取决于web.config 里的配置。(注意,进入该函数不仅会进行加密,也会进行签名)。

    _page.EnableViewStateMac 来自这里:

    对应字段 _enableViewStateMac 在Page类的初始化函数中被设置为默认值 true:

    于是 _enableViewStateMac 是否被修改就取决于 EnableViewStateMacRegistryHelper.EnforceViewStateMac。

    查看 EnableViewStateMacRegistryHelper 类,其为EnforceViewStateMac 做了如下注释:

    也就是说:在启用EnableViewStateMac补丁的情况下,EnforceViewStateMac 返回true,这表示 前面的EnableViewStateMac 标志位会始终保持其默认值true。

    在EnableViewStateMacRegistryHelper 类的初始化函数中,进一步表明了是依据什么修改 EnforceViewStateMac的:

    可以看到EnforceViewStateMac 在两种情况下被修改:

    • 依据 IsMacEnforcementEnabledViaRegistry() 函数

    该函数是从注册表里取值,如果该表项为0,则表示禁用EnableViewStateMac 补丁。

    • 依据 AppSettings.AllowInsecureDeserialization.HasValue

    该值应当是来自于web.config 中的危险设置:

    总结来说,ViewStateMac 默认强制开启,要想关闭该功能,必须通过注册表或者在web.config 里进行危险设置的方式禁用 EnableViewStateMac 补丁才能实现。

    4.2 Deserialize 流程

    查看 Deserialize 函数的代码:

    重点仍然是里面的else分支:

    这里出现了一个新的标志位 _page.ContainsEncryptedViewState 用于决定是否进入MachineKeySection.EncryptOrDecryptData() 函数进行解密,查看ContainsEncryptedViewState 的来历:

    注释表明,该标志确实用于判断接收到的viewstate 是否被加密。查看dnspy逆向的结果,你会更清晰:

    这 "__VIEWSTATEENCRYPTED" 很像是request 里提交的字段啊,查找一下,确实如此。

    查看开启加密后的 request 请求,确实有这样一个无值的字段:

    所以,ASP.NET在解析ViewState时,并不是根据web.config来判断 ViewState 是否加密,而是通过request里是否有__VIEWSTATEENCRYPTED 字段进行判断。换句话说,即使我们在web.config 里设置 Always 解密,服务端仍然会被动解析只有签名的ViewState。( 我在 YsoSerial.NET 工具 ViewState插件作者的博客里看到,.net 4.5 之后需要加密算法和密钥。但是我不明白为什么,在实际测试中似乎也不需要。)

    5. GetEncodedData 签名函数

    GetEncodedData() 函数用于对序列化后的Binary数据进行签名,用于完整性校验。查看其代码(.NET 4.8):

    大致流程:

    • HashData()函数计算出hash值。
    • 判断原buffer长度是否够,如果够,则直接在原buffer中data后添加hash值;否则申请新的buf,并将data和hash值拷贝过去。
    • 判断hash算法是否是3DES 或者 AES,如果是,则调用EncryptOrDecryptData() 函数。

    我们首先来看一下HashData函数:

    这里有几个特殊的标志位:s_config.Validation、_UseHMACSHA、_CustomValidationTypeIsKeyed,用来决定进入哪个函数生成hash。

    s_config.Validation 应当是web.config 中设置的签名算法。

    而另外两个标志则源自于 InitValidationAndEncyptionSizes() 函数里根据签名算法进行的初始化设置:

    可以看到,只有MD5签名算法将 _UseHMASHA设置为false,其他算法都将其设置为true。除此之外,还根据签名算法设置_HashSize 为相应hash长度。所以计算MD5 hahs时进入 HashDataUsingNonKeyedAlgorithm()函数,计算其他算法hash时进入 GetHMACSHA1Hash() 函数。

    我们先看使用MD5签名算法时进入的 HashDataUsingNonKeyedAlgorithm() 函数:

    这里的modifier 的来源我们稍后再议,其长度一般为4个字节。HashDataUsingNonKeyedAlgorithm() 函数流程如下:

    • 申请一块新的内存,其长度为data length + validationkey.length + modifier.length
    • 将data,modifier,validationkey 拷贝到新分配的内存里。特殊的是,modifier 和 vavlidationkey 都是从紧挨着data的地方开始拷贝,这就导致了validationkey 会 覆盖掉modifier。所以真正的内存分配为: data + validationkey + '\x00'*modifier.length
    • 根据MD5算法设置hash长度,即newHash。关于这一点,代码中有各种算法产生hash值的长度设定:

    各种算法对应的Hash长度分别为 MD5:16 SHA1:20 MACSHA256:32 HMACSHA384:48 HMACSHA512:64, 全都不同。

    • 调用UnsafeNativeMethods.GetSHA1Hash() 函数进行hash计算。该函数是从webengine4.dll 里导入的一个函数。第一次看到这里,我有一些疑问,为什么MD5算法要调用GetSHA1Hash函数呢?这个疑问先保留。我们先看其他算法是如何生成hash的。

    计算其他算法的hash时调用了一个自己写的GetHMACSHA1Hash() 函数,其实现如下:

    可以看到,其内部直接调用的UnsafeNativeMethods.GetHMACSHA1Hash() 函数,该函数也是从webengine4.dll里导入的一个函数。和之前看生成MD5 hash值时有一样的疑问,为什么是GetHMACSHA1HAsh?为什么多种算法都进入这一个函数?根据他们参数的特点,而且之前看到各个算法生成hash的长度不同,我们可以猜测,或许是该函数内部根据hash长度来选择使用什么算法。

    把 webengine4.dll 拖进ida里。查看GetSHA1Hash() 函数和 GetHMACSHA1Hash() 函数,特点如下:

    GetHMACSHA1Hash:

    二者都进入了GetAlgorithmBasedOnHashSize() 函数,看来我们的猜测没错,确实是通过hash长度来选择算法。

    6. EncryptOrDecryptData 加密解密函数

    我们之前看到,无论是开启加密的情况下,还是采用AES\3DES签名算法的情况下,都会进入 MachineKeySection.EncryptOrDecryptData() 函数,那么该函数内部是怎么样的流程呢?

    先来看一下该函数的声明和注释:

    注释开头说明:该函数用于加密/解密,可选择的进行签名/校验。一共有4中情况:加密+签名、只加密、解密+校验、只解密。重点是其中的加密+签名、解密+校验。

    • 加密+签名:fEncrypt = true, signData = true输入:待加密的原始数据,modifier输出:E(iv + buf + modifier) + HMAC(E(iv + buf + modifier))

    (上述公式中E表示加密,HMAC表示签名)

    • 解密+校验:fEncrypt = false, signData = true输入:带解密的加密数据,modifier,buf 即为上面的 E(iv + m + modifier) + HMAC(E(iv + m + modifier))输出:m

    老实说,只看注释,我们似乎已经可以明白该函数是如何进行加密和签名的了,操起python 就可以学习伪造加密的viewstate了(开玩笑)。不过我们还是看一下他的代码:

    该函数有9个参数:

    • 第1个参数 fEncrypt 表示是加密还是解密,true为加密,false 为解密;
    • 第2~5个参数 buf、modifier、start、length 为与原始数据相关;
    • 第6个参数 useValidationSymAlgo 表示加密是否使用与签名相同的算法;
    • 第7个参数useLegacyMode 与自定义算法有关,一般为false;
    • 第8个参数 ivType与加密中使用的初始向量iv 有关,根据注释,旧的 IPType.Hash 已经被去除,现在默认使用IPType.Random;
    • 第9个参数 signData 表示是否签名/校验。

    关于第6个参数 useValidationSymAlgo 有一些细节要说:

    我们知道,在Serialize 函数下有两种情况会进入 EncryptOrDecryptData 函数:

    (1)由于web.config 配置中开启加密功能,直接进入 EncryptOrDecryptData() 函数:

    image-20201029150303455

    此时EncryptOrDecryptData () 参数有5个。

    (2)在进入GetEncodeData() 函数后,由于使用了AES/3DES 签名算法,导致再次进入 EncryptOrDecryptData() 函数:

    此时EncryptOrDecryptData () 参数有6个。

    二者参数个数不同,说明是进入了不同的重载函数。

    细细观察会发现,由于使用了AES/3DES签名算法导致进入 EncryptOrDecryptData () 时,第6个参数 useValidationSymAlgo 为true。意义何在呢?因为先进入GetEncodedData() 函数,说明没有开启加密功能,此时由于使用的是AES/3DES签名算法,导致需要在签名后再次EncryptOrDecryptData () 函数。进入EncryptOrDecryptData() 就需要决定使用什么加密算法。所以第6个参数为true,表示加密使用和签名同样的算法。另外多说一句,这种情况下会有两次签名,在GetEncodedData() 里一次,进入EncryptOrDecryptData() 后又一次(后面会看到)。

    下面代码将有关解密和校验的操作隐去,只介绍加密与签名的部分。

    这一段是先调用GetCryptoTransform 获取加密工具,而后通过CryptoStream 将数据流链接到加密转换流。不了解这一过程的可以查看微软相关文档

    关键在于GetCryptoTransform() 是如何选择加密工具的?该函数的3个参数中似乎并无算法相关。观其代码:

    algo 表示相应的算法类,那么关键便是 s_oSymAlgoValidation 和 s_oSymAlgoDecryption,察其来历:

    ConfigureEncryptionObject() 函数:

    看来在网站初始化时就已将相应的加密类分配好了。

    继续观察 EncryptOrDecryptData() 的代码:

    这一段开头是在生成IV。IV是加密时使用的初始向量,应保证其随机性,防止重复IV导致密文被破解。

    • ivLength为64。这里随机生成64个字节作为iv。
    • 三次调用 cs.Write(),分别写入iv、buf、modifier。cs即为前面生成的CryptoStream类实例,用于将数据流转接到加密流。这里与我们前面所说的公式 E(iv + buf + modifier) 对应上了。
    • 调用ms.ToArray() ,即返回加密完成后的生成的字节序列。

    继续观察 EncryptOrDecryptData() 的代码:

    这里是最后一部,将加密后生成的字节序列传给HashData,让其生成hash值,并缀在字节序列后面。

    这就与前面的公式 E(iv + buf + modifier) + HMAC(E(iv + buf + modifier)) 对应上了。

    看完 EncryptOrDecryptData() 函数的代码,我么也明白了其流程,总结下来其实就一个公式,没错就是 E(iv + buf + modifier) + HMAC(E(iv + buf + modifier)) 。

    7. modifier 的来历

    在前面进行签名和加密的过程中,都使用了一个关键变量叫做modifier,该变量同密钥一起用于签名和加密。该变量来自于 GetMacKeyModifier() 函数:

    函数流程:

    1. 函数开头先通过 _page.GetClientStateIdentifier 计算出一个 pageHashCode;
    2. 如果有viewStateUserKey,则modifier = pageHashCode + ViewStateUsereKey;
    3. 如果没有viewStateUserKey,则modifier = pageHashCode

    先看pageHashCode 来历:

    从注释中也可以看出,计算出directory 和 class name 的hash值,相加并返回。这样pageHashCode 就有4个字节了。所以我们可以手动计算一个页面的 pageHashCode,directory 和 class name 应当分别是网站集路径和网站集合名称。除此之外也可以从页面中的隐藏字段"__VIEWSTATEGENERATOR" 中提取。便如下图:

    "__VIEWSTATEGENERATOR" 与 pageHashCode 的关系在这里:

    再看ViewStateUserKey 的来历:

    按照官方说法:ViewStateUserKey 即 :在与当前页面关联的ViewState 变量中为单个用户分配标识符。

    可见,ViewStateUserKey 是一个随机字符串值,且要保证与用户关联。如果网站使用了ViewStateUserKey,我们应当在SessionID 或 cookie 中去猜。在CVE-20202-0688 中,便是取 SessionID 作为ViewStateUserKey。

    8. 伪造ViewState

    经过上面长篇大论的贴代码、分析。我们已经大致明白了ASP.NET 生成和解析ViewState 的流程。这有助帮助我们理解如何伪造 ViewState。当然了伪造 ViewState 仍然需要 泄露web.config,知晓其 密钥与算法。

    1. 如果签名算法不是AES/3DES,无论是否开启加密功能,我们只需要根据其签名算法和密钥,生成一个签名的ViewState。由于发送该ViewState的时候没有使用"__VIEWSTATEENCRYPTED" 字段,导致ASP.NET 在解析时直接进入GetDecodedData() 进行签名校验,而不再执行解密步骤。
    2. 如果签名算法是 AES/3DES,无论是否开启加密功能,我们只需按照先前所讲,对数据先签名一次,再加密一次,再签名一次。 然后发送给服务端,ASP.NET 进入 GetDecodedData(),然后先进 EncryptOrDecryptData() 进行一次校验和解密,出来后再进行一次校验。

    换种表达方式,无论使用什么签名算法,无论是否开启加密功能,我们伪造ViewState时,就按照没有开启加密功能情况下的正常步骤,去伪造ViewState。

    9.附录:

    [1] ysoserial.net

    https://github.com/pwntester/ysoserial.net

    [2] viwgen (python 写的viewstate生成工具,不依赖.NET,方便自动化脚本使用)

    https://github.com/0xacb/viewgen

    [3] 什么是View State 及其在ASP.NET中的工作方式

    https://www.c-sharpcorner.com/UploadFile/225740/what-is-view-state-and-how-it-works-in-Asp-Net53/

    [4] 微软官方文档:ASP.NET服务器控件概述

    https://docs.microsoft.com/zh-cn/troubleshoot/aspnet/server-controls

    [5]《MSDN杂志》文章:ViewState 安全

    https://docs.microsoft.com/en-us/archive/msdn-magazine/2010/july/security-briefs-view-state-security

    [6] 安全通告KB2905247

    https://docs.microsoft.com/en-us/security-updates/SecurityAdvisories/2013/2905247?redirectedfrom=MSDN

    [7] 使用ViewState

    http://appetere.com/post/working-with-viewstate

    [8] Exhange CVE-2020-0688

    https://www.thezdi.com/blog/2020/2/24/cve-2020-0688-remote-code-execution-on-microsoft-exchange-server-through-fixed-cryptographic-keys


    Paper

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

    作者:江 | Categories:技术分享漏洞分析 | Tags:
  • DeFi 项目 bZx-iToken 盗币事件分析

    2020-11-03

    作者:昏鸦@知道创宇404区块链安全研究团队
    时间:2020年9月14日

    发生了什么

    iToken是bZx推出的一种代币,今天早些时候,bZx官方发推表示发现了一些iTokens的安全事件,随后有研究员对比iToken合约源码改动,指出其中存在安全问题,可被攻击用于薅羊毛。

    什么是iToken

    iToken是bZx推出的类似iDAI、iUSDC的累积利息的代币,当持有时,其价值会不断上升。iToken代表了借贷池中的份额,该池会随借贷人支付利息而扩大。iToken同样能用于交易、用作抵押、或由开发人员组成结构化产品,又或者用于安全价值存储。

    分析

    根据推文指出的代码,问题存在于_internalTransferFrom函数中,未校验fromto地址是否不同。

    若传入的fromto地址相同,在前后两次更改余额时balances[_to] = _balancesToNew将覆盖balances[_from] = _balancesFromNew的结果,导致传入地址余额无代价增加。

    漏洞复现

    截取transferFrom_internalTransferFrom函数作演示,测试合约代码如下:

    remix部署调试,0x1e9c2524Fd3976d8264D89E6918755939d738Ed5部署合约,拥有代币总量,授权0x28deb6CA32C274f7DabF2572116863f39b4E65D9500代币额度

    通过0x28deb6CA32C274f7DabF2572116863f39b4E65D9账户,调用transferFrom函数,_from_to传入地址0x1e9c2524Fd3976d8264D89E6918755939d738Ed5_value传入授权的500

    最后查看0x1e9c2524Fd3976d8264D89E6918755939d738Ed5地址余额,已增加500额度,超出代币发行总量。

    综上,恶意用户可创建小号,通过不断授权给小号一定额度,使用小号频繁为大号刷代币,增发大量代币薅羊毛。

    总结

    针对本次事件,根本原因,还是没做好上线前的代码审计工作。由于区块链智能合约的特殊性,智能合约上线前务必做好完善的代码审计、风险分析的工作。

    另外通过github搜索到其他项目也同样存在这个问题,务必提高警惕。


    智能合约审计服务

    针对目前主流的以太坊应用,知道创宇提供专业权威的智能合约审计服务,规避因合约安全问题导致的财产损失,为各类以太坊应用安全保驾护航。

    知道创宇404智能合约安全审计团队: https://www.scanv.com/lca/index.html
    联系电话:(086) 136 8133 5016(沈经理,工作日:10:00-18:00)欢迎扫码咨询:

    区块链行业安全解决方案

    黑客通过DDoS攻击、CC攻击、系统漏洞、代码漏洞、业务流程漏洞、API-Key漏洞等进行攻击和入侵,给区块链项目的管理运营团队及用户造成巨大的经济损失。知道创宇十余年安全经验,凭借多重防护+云端大数据技术,为区块链应用提供专属安全解决方案。欢迎扫码咨询:

    参考

    https://bzx.network/blog/incident
    https://twitter.com/k06a/status/1305223411615117322

    Paper

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

    作者:江 | Categories:安全研究漏洞分析 | Tags:
  • WebSphere XXE 漏洞分析(CVE-2020-4643)

    2020-11-03

    作者:Longofo@知道创宇404实验室 & r00t4dm@奇安信A-TEAM
    时间:2020年9月21日

    2020年9月17日,IBM发布了一个WebSphere XXE漏洞公告。 当时看到这个消息心想我们挖的那个XXE很可能与这个重了。然后看了下补丁,果不其然,当时心里就很遗憾,本来是打算一起找到一个RCE漏洞在一起提交XXE漏洞的,因为害怕提交了XXE官方把反序列化入口也封了,例如CVE-2020-4450,直接封掉了反序列化入口。奈何WebSphere找了一两周也没什么发现,后来正打算把XXE提交了,就看到官方发布了公告,看了下作者,是绿盟的一位大佬,也是CVE-2020-4450的发现者之一,这些默默挖洞的大佬,只可远观眺望啊。WebSphere的分析似乎挺少,聊聊几篇分析,不像Weblogic那样量产漏洞,单是一个高版本sdk就拦截了很多链或者说连接可用链的点,心想与其烂在手里,还不如分享出来,下面写下我们发现过程,其实重要的不是这个XXE,而是到达XXE这个点的前半部分。

    补丁

    先来看看补丁,只能看出是修复了一个XXE,不知道是哪儿的XXE:

    可以看出这里是修复了一个XXE漏洞,但是这只是一个Utils,我们找到的那个XXE刚好也用了这个Utils。

    漏洞分析

    最开始研究WebSphere就是前不久的CVE-2020-4450,这个漏洞外面已经有分析了。为了更熟悉一点WebSphere,我们也去研究了历史补丁,例如印象比较深的就是前不久的CVE-2020-4276,这个漏洞算是历史漏洞CVE-2015-7450的认证方式绕过,RCE的过程与CVE-2015-7450没区别。后面意外的找到另一个反序列化入口,在确认了已经无法在历史漏洞上做文章的时,只好从readObject、readExternal、toString、compare等函数去尝试找下了,后来在一个readObject找到一个能JNDI注入的地方,但是由于sdk高版本的原因,能利用的方式就只能是本地factory或利用jndi本地反序列化了,但是WebSphere公开的利用链都被堵上了,本地反序列化其实没什么作用在这里,所以只剩下看本地Factory了。反序列化入口暂时先不给出,可能这样的反序列化入口还有很多,我们碰巧遇到了其中一个,如果后面有幸找到了RCE漏洞,就把我们找到的入口写出来,下面从那个readObject中的JNDI开始吧。

    com.ibm.ws.ejb.portable.EJBMetaDataImpl#readObject中:

    com.ibm.ws.ejb.portable.HomeHandleImpl#getEJBHome如下:

    如果是sdk低版本,直接就是外部加载factory rce利用了,但是天不随人愿,如果这么容易就不会有CVE-2020-4450那种复杂的利用了。

    接下来就只能一个一个看本地的factory了,也不多大概几十个,一个一个看吧。在com.ibm.ws.webservices.engine.client.ServiceFactory#getObjectInstance中,找到了那个XXE:

    com.ibm.ws.webservices.engine.client.Service#Service(java.net.URL, javax.xml.namespace.QName),在构造函数中:

    com.ibm.wsdl.xml.WSDLReaderImpl#readWSDL(java.lang.String, org.w3c.dom.Document)之后,会调用到一个com.ibm.wsdl.xml.WSDLReaderImpl#parseDefinitions

    com.ibm.wsdl.xml.WSDLReaderImpl#parseImport:

    xml payload:

    最后

    我们只看了浮在表面上的一些地方,人工最多只看了两层调用,也许RCE隐藏在更深的地方或者知识盲点现在没找到呢,还是得有个属于自己的能查找链的工具,工具不会累,人会。


    Paper

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

    作者:江 | Categories:漏洞分析 | Tags:
  • 代码审计从0到1 —— Centreon One-click To RCE

    2020-11-03

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

    前言

    代码审计的思路往往是多种多样的,可以通过历史漏洞获取思路、黑盒审计快速确定可疑点,本文则侧重于白盒审计思路,对Centreon V20.04[1]的审计过程进行一次复盘记录,文中提及的漏洞均已提交官方并修复。

    概述

    Centreon(Merethis Centreon)是法国Centreon公司的一套开源的系统监控工具 。该产品主要提供对网络、系统和应用程序等资源的监控功能。

    网站基本结构

    源代码目录组成

    centreon/www/网站根目录

    centreon/www/include/核心目录结构

    概述一下

    • centreon/www/index.php是网站的入口文件,会先进行登录认证,未登录的话跳转进入登录页,登录成功后进入后台
    • centreon/www/main.phpcentreon/www/main.get.php,对应PC端与移动端的路由功能,根据不同的参数,可以加载到后台不同的功能页面,在实际调试的过程,发现使用main.php加载对应的功能页时,最终会调用main.get.php,所以路由部分直接看main.get.php即可
    • entreon/www/include/目录包含核心功能代码、公共类。其中有些功能代码可以直接通过路径访问,有些则需要通过main.get.php页面进行路由访问
    • centreon/www/api/目录下的index.php是另一处路由功能,可以实例化centreon/www/api/class/*.class.phpcentreon/www/modules/centreon/www/widgets/*/webServices/rest/*.class.phpcentreon/src/中的类并调用指定方法

    在审计代码的时候,有两个要关注点:

    • 重点审查centreon/www/include/centreon/www/api/class/两个目录,因为这些目录下的功能点可以通过centreon/www/main.phpcentreon/www/api/index.php路由访问
    • 重点寻找绕过登录认证或者越权的方式,否则后台漏洞难以利用

    代码分析

    如下简要分析centreon/www/目录下的部分脚本

    index.php

    index.php会进行登录认证,检查是否定义$_SESSION["centreon"]变量,这个值在管理员登录后设置。程序登录有两种方式,使用账密或者token,相关逻辑在/centreon/www/include/core/login/processLogin.php中。不止index.php,centreon/www/include/下大部分功能页都会检查session,没有登录就无法访问

    main.get.php

    这是主要的路由功能,程序开头对数据进行过滤。$_GET数组使用fiter_var()过滤处理,编码特殊字符,有效地防御了一些XSS,比如可控变量在引号中的情况,无法进行标签闭合,无法逃逸单引号

    对_POST中的指定参数,进行过滤处理,对数据类型进行限制,对特殊字符进行编码

    最终_POST数组赋值到$inputs数组中

    全局过滤数据后,程序引入公共类文件和功能代码

    99行session取出,认证是否登录

    通过登录认证后,程序会查询数据库,获取page与url的映射关系,程序通过p参数找到对应的url,进行路由,映射关系如下

    接着248行include_once $url,引入centreon/www/include/下对应的脚本

    这里将page与url映射关系存储到本地,方便后续查询

    api/index.php

    这是另外一个路由功能

    同样需要验证登录,104行$_SERVER['HTTP_CENTREON_AUTH_TOKEN']可以在请求头中伪造,但是并不能绕过登录,可以跟进查看CentreonWebService::router方法

    \api\class\webService.class.php,其中action参数可控

    311行判断isService是否为true,如果是,dependencyInjector['centreon.webservice']->get(object)

    313行centreon.webservice属性值如下,对应的是centreon/src目录下的类

    $webServicePaths变量包含以下类路径

    接着346行检查类中是否存在对应方法,在374行处调用,但是在350~369进行了第二次登录认证,所以之前$_SERVER['HTTP_CENTREON_AUTH_TOKEN']伪造并没能绕过登录

    过滤处理

    除了main.get.php开头的全局过滤操作,程序的其他过滤都是相对较分散的,对于SQL注入的话,程序的很多查询都使用了PDO进行参数化查询,对于PDO中一些直接拼接的参数,则单独调用某些函数进行过滤处理。比如下边这里进行数据库更新操作时,updateOption()会进行query操作,$ret["nagios_path_img"]可控,但是这里调用escape()函数进行转义

    路径限制

    不通过路由功能,直接访问对应路径的功能代码,大部分是不被允许的,比如直接访问generateFiles.php页面

    可以看到39行检查oreon参数,这就是为什么要通过main.get.php去访问某些功能点。当然有一些漏网之鱼,比如rename.php页面,这里只是检查session是否存在,在登录状态下,可以通过路径直接访问该页面。

    One-click To RCE

    XSS

    在上一节的最后,为什么要纠结通过路径访问还是路由访问呢?因为通过main.get.php中的路由访问的话,会经过全局过滤处理,直接通过路径访问则没有,这样就有了产生漏洞的可能,通过这个思路可以找到一个XSS漏洞,在rename.php中程序将攻击者可控的内容直接打印输出,并且没有进行编码处理,缺乏Httponly与CSP等的攻击缓存机制,当管理员点击精心构造的链接时,将触发XSS执行任意js代码,导致cookie泄露。

    漏洞分析

    漏洞入口

    centreon/include/home/customViews/rename.php

    前边也提到,46行验证session是否存在,所以受害者只要处于登录状态即可,59行echo直接打印_REQUEST)返回的值,rename函数中对params['newName'],因为直接通过路径访问,没有经过任何过滤处理

    所以elementId控制为title_1(任意数字),设置newName为script标签即可

    授权RCE

    程序在使用perl脚本处理mib文件时,没有对反引号的内容进行正确的过滤处理,攻击者利用XSS窃取的凭证登录后,可上传恶意文件导致远程代码执行,即One_click to RCE

    漏洞分析

    可以顺着CVE-2020-12688[2]的思路,全局搜索"shell_exec("关键字符串, formMibs.php调用了该函数

    查看源码,38行执行了shell_exec(command从form),打印$form方便调试

    之前记录的page与url的映射关系现在就可以派上用场了,设置page为61703,通过main.php或main.get.php可以路由到formMibs.php,也就是下边的文件上传功能

    调试发现formMibs.php中31行的manufacturerId可以通过上传数据包中mnftr字段修改,但是被filter_var()处理,只能为整数

    虽然缓存文件名是不可控的,但是上传的mib文件内容可控,shell_exec()中执行的命令实际为("xxx.mib"代表缓存文件名)

    centFillTrapDB是一个perl脚本,代码在/bin/centFillTrapDB中,用use引入centFillTrapDB模块

    use命令寻找的路径默认在@INC下,但不知道具体在哪里,可以全局搜索一下

    最后在usr/share/perl5/vendor_perl/centreon下找到script目录,有我们想要的文件

    把centFillTrapDB模块拉出来静态看一下,发现存在命令执行且内容可控的位置,实际调试发现最终分支是进入541行,540行和543行是我添加的调试代码

    在perl中反引号内可以执行系统命令,534行trap_lookup可控,对于mib文件来说,{IFS}代替

    为了方便构造mib文件,打印出反引号中的命令,并在服务器shell中进行测试

    构造/tmp/1.mib文件

    命令行执行

    可以清晰的看到command,并且执行了curl命令

    修改mib文件中的命令,在浏览器上传进行测试,成功执行whoami并回显

    审计总结

    文本主要分享了一些白盒审计思路,但就像之前所说的,审计的思路往往是多种多样的,以下是个人的小小总结:

    • 分析历史漏洞,在复现和调试的过程中,可以比较快的了解这个框架的结构,也可以从历史漏洞中获取思路,举一反三
    • 黑盒审计,开启抓包工具,测试可疑的功能点并观察数据包,这样可以加快对网站路由的熟悉,也可以快速的验证一些思路,排除一些可能性,仍然存疑的功能点可以在白盒审计时进一步确认;
    • 白盒审计,入口脚本,路由方式,核心配置,常用功能模块和数据验证过滤操作,这些都是要留意的,当然最主要还是看入口,路由和数据过滤验证的部分;其他的如核心配置,常用功能模块,可以按需查看,大概了解了网站架构就可以开始看对应的功能代码了,看的时候可以分两个角度:一个就是从刚才黑盒测试遗留的可疑点入手,断点功能代码,审查是否存在漏洞;另一个就是从敏感关键字入手,全局搜索,溯源追踪。
    • 注重不同漏洞的组合攻击,无论是这次的Centreon One_click to RCE漏洞,还是通达OA任意删除认证文件导致的未授权RCE、PHPCMS V9 authkey泄露导致的未授权RCE,打的都是一套组合拳,在漏洞挖掘的过程可以多加关注

    参考链接

    [1] Centreon V20.04

    https://github.com/centreon/centreon/releases/tag/20.04.0

    [2] CVE-2020-12688漏洞公开细节

    https://github.com/TheCyberGeek/Centreon-20.04


    Paper

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

    作者:江 | Categories:安全研究漏洞分析 | Tags:
  • Defi?Uniswap 项目漏洞教程新骗局

    2020-11-03

    作者:极光 @ 知道创宇404区块链安全研究团队
    时间:2020年8月31日

    前言

    昨晚突然看到群里的一个消息,揭秘uniswap-defi项目漏洞-割韭菜新手法,心想还有这事?而且还是中英文介绍。

    aa

    到底什么是DeFi?,网络上有很多关于 DeFi的定义,目前通用的定义是这样的:DeFi是自己掌握私钥,以数字货币为主体的金融业务这个定义包含三个层面的意思:

    • 自己掌握私钥
    • 以数字货币为主体
    • 金融业务

    DeFi是Decentralized Finance(去中心化金融)的缩写,也被称做Open Finance。它实际是指用来构建开放式金融系统的去中心化协议,旨在让世界上任何一个人都可以随时随地进行金融活动。

    在现有的金融系统中,金融服务主要由中央系统控制和调节,无论是最基本的存取转账、还是贷款或衍生品交易。DeFi则希望通过分布式开源协议建立一套具有透明度、可访问性和包容性的点对点金融系统,将信任风险最小化,让参与者更轻松便捷地获得融资。

    几年前区块链行业还没有DeFi这个概念,从默默无闻,一跃成为区块链行业的热门话题,DeFi只用了短短几年时间。Uniswap作为完全部署在以太坊链上的DEX平台,促进ETH和ERC20 代币数字资产之间的自动兑换交易,为DeFi发展提供了良好的支持。

    作者抓住当下区块链热门话题DeFi作为文章主题介绍如何利用 uniswap-defi项目漏洞割韭菜。很显然经过精心思考。

    分析

    打开教程链接,原文教程提醒

    aa

    作者特别提醒:完全开放源码----仅用于研究和测试,不要使用这种方法作弊。

    教程中提到合约代码可以在如下链接下载

    aa

    根据教程提供的链接,下载代码查看

    aa

    首先看到onlyOwner函数,而且条件判断中的address是硬编码的,这里说一下以太坊中的地址

    • 以太坊地址

    以太坊中的地址的长度为20字节,一字节等于8位,一共160位,所以address其实亦可以用uint160来声明。以太坊钱包地址是以16进制的形式呈现,我们知道一个十六进制的数字占4位,160 / 4 = 40,所以钱包地址ca35b7d915458ef540ade6068dfe2f44e8fa733c的长度为40。

    很明显,攻击者特意使用uint160来编码地址,起到了障眼法作用。如果不认真看,不会注意到这个address函数转换后的地址。

    通过对地址进行转换

    aa

    即:address(724621317456347144876435459248886471299600550182) 对应地址:0x7eed24C6E36AD2c4fef31EC010fc384809050926,这个地址即位合约实际控制账户地址。

    继续往下看原文教程

    首先部署合约

    aa

    然后添加到 Uniswap v1 资金池

    aa

    这里介绍下 Uniswap

    • Uniswap V1

    Uniswap V1基于以太坊区块链为人们提供去中心化的代币兑换服务。Uniswap V1提供了ETH以及ERC20代币兑换的流动性池,它具有当前DeFi项目中最引人注目的去中心化、无须许可、不可停止等特性。

    Uniswap V1实现了一种不需要考虑以上特点的去中心化交易所。它不需要用户进行挂单(没有订单),不需要存在需求重叠,可以随买随卖。得益于 ERC20 代币的特性,它也不需要用户将资产存入特定的账户。Uniswap V1模型的优点在于根据公式自动定价,通过供需关系实现自动调价。

    Uniswap V1的运行机制的关键在于建立了供给池,这个供给池中存储了 A 和 B 两种货币资产。用户在用 A 兑换 B 的过程中,用户的 A 会发送到供给池,使供给池中的 A 增多,同时,供给池的 B 会发送给用户。这里的关键的问题在于如何给 A 和 B 的兑换提供一个汇率(定价)。 Uniswap V1定价模型非常简洁,它的核心思想是一个简单的公式 x * y = k 。其中 x 和 y 分别代表两种资产的数量,k 是两种资产数量的乘积。

    假设乘积 k 是一个固定不变的常量,可以确定当变量 x 的值越大,那么 y 的值就越小;相反 x 的值越小,y 的值就越大。据此可以得出当 x 被增大 p 时,需要将 y 减少 q 才能保持等式的恒定。 为了做一些更实用的工作,将 x 和 y 替换为货币储备金的储备量,这些储备金将被存储在智能合约中。

    即用户可以把部署的合约可以添加到Uniswap V1中,通过充入资产提供流动性,获得该资金池(交易对)产生的交易手续费分红,过程完全去中心化、无审核上币。

    接着

    aa

    这是为什么?看看代码

    aa

    合约代币101行,require(allow[_from] == true),即转账地址from需要在allow这个mapping中为布尔值true

    aa

    而修改allowaddAllow函数中,且需要合约Owner权限。

    aa

    通过合约Ownable代码第13行可知,onlyOwner属性中,只有地址为724621317456347144876435459248886471299600550182即前面提到的0x7eed24C6E36AD2c4fef31EC010fc384809050926用户可以通过校验,而且是硬编码。这也是原文攻击者为什么使用了以太坊地址的uint160格式来编码地址,而不是直观的十六进制地址。

    aa

    最终部署的合约SoloToken直接继承了Ownable合约

    aa

    即只要用户部署该合约,合约Owner权限都在攻击者0x7eed24C6E36AD2c4fef31EC010fc384809050926手中。攻击者可以随时转移合约权限。

    在教程中攻击者还提到

    aa

    如果你想吸引买家,资金池必须足够大,如果只投入1-2个ETH,其他人将无法购买它,因为基金池太小。即希望部署合约的用户在资金池中添加更多的eth数量。攻击者为什么要单独Notice呢?

    aa

    合约代码第124行,mint函数,Owner权限用户可以直接增发代币。这是合约最关键部分。即攻击者可以直接在合约中给指定地址增发代币,然后利用增发得来的代币去Uniswap V1直接兑换合约部署用户存放在 Uniswap V1 资金池中的 eth。这也是为啥教程作者着重提示多添加 eth 数量的根本原因。

    截止目前,攻击者地址0x7eed24C6E36AD2c4fef31EC010fc384809050926中已经获利大约36eth

    aa

    总结

    Uniswap 因无需订单薄即可交易的模型创新引来赞誉,也因投机者和诈骗者的涌入遭到非议,在业内人士看来,Uniswap 的自动做市商机制有着特别的价值,作恶的不是Uniswap,但恶意与贪婪正在这个去中心化协议中一览无余。

    流动性挖矿点燃DeFi烈火,火势烧到去中心化交易所Uniswap。它凭借支持一键兑币、做市可获手续费分红,迅速成为最炙手可热的DeFi应用之一。

    财富故事在这里上演,某个新币种可能在一天之内制造出数十倍的涨幅,让参与者加快实现「小目标」;泡沫和罪恶也在此滋生,完全去中心化、无审核上币,让Uniswap成了人人可发币割韭菜的温床。

    DeFi作为当下区块链热门话题,很容易吸引人们的注意。攻击者利用人们贪图便宜的好奇心理。使用所谓的 uniswap-defi项目漏洞 教程一步一步带用户入坑。以当下区块链中最火的DeFi类为主题,分享了 揭秘uniswap-defi项目漏洞-割韭菜新手法 教程。如果用户不注意看合约代码,很容易掉入攻击者精心构造的陷阱中去。成为真正的韭菜

    REF

    [1] UNISWAP issuing tokens-enhancing tokens (consumers can only buy but can not sell)

    https://note.youdao.com/ynoteshare1/index.html?id=a41d926f5bcbe3f69ddef765ced5e27b&type=note?auto

    [2] 代币合约

    https://wwr.lanzous.com/i4MJOg6f2rg


    Paper

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

    作者:江 | Categories:技术分享漏洞分析 | Tags:
  • WebLogic coherence UniversalExtractor 反序列化 (CVE-2020-14645) 漏洞分析

    2020-11-03

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

    前言

    Oracle七月发布的安全更新中,包含了一个Weblogic的反序列化RCE漏洞,编号CVE-2020-14645,CVS评分9.8。

    image-20200801182009244

    该漏洞是针对于CVE-2020-2883的补丁绕过,CVE-2020-2883补丁将MvelExtractorReflectionExtractor列入黑名单,因此需要另外寻找一个存在extract且方法内存在恶意操作的类,这里用到的类为com.tangosol.util.extractor.UniversalExtractor,存在于Coherence组件。

    CVE-2020-2883

    先来回顾一下CVE-2020-2883的两个poc调用链

    其本质上,都是通过ReflectionExtractor调用任意方法,从而实现调用Runtime对象的exec方法执行任意命令,但补丁现在已经将ReflectionExtractor列入黑名单,那么只能使用UniversalExtractor重新构造一条利用链,这里使用poc2的入口即CommonsCollections4链的入口进行构造。

    CVE-2020-14645

    为了方便一些纯萌新看懂,此处将会从0开始分析反序列化链(啰嗦模式警告),并且穿插一些poc构造时需要注意的点,先来看看调用栈。

    image-20200801234349005

    从头开始跟进分析整个利用链,先来看看PriorityQueue.readObject()方法。

    image-20200801234303677

    第792会执行for循环,将s.readObject()方法赋给queue对象数组,跟进heapify()方法。

    image-20200801234406139

    这里会取一半的queue数组分别执行siftDown(i, (E) queue[i]);,实质上PriorityQueue是一个最小堆,这里通过siftDown()方法进行排序实现堆化,那么跟进siftDown()方法。

    image-20200801234425033

    这里有个对于comparator的判定,我们暂时不考虑comparator的值是什么,接下来会使用到,我们先跟进siftDownUsingComparator()方法。

    image-20200801234433087

    重点关注comparator.compare()方法,那么我们先来看看comparator是怎么来的。

    image-20200801234441593

    是在PriorityQueue的构造函数中被赋值的,并且这里可以看到,queue对象数组也是在这里被初始化的。那么结合上述所分析的点,我们需要构造一个长度为2的queue对象数组,才能触发排序,进入siftDown()方法。同时还要选择一个comparator,这里选用ExtractorComparator。继续跟进ExtractorComparator.compare()方法。

    image-20200801234503470

    这里将会调用this.m_extractor.extract()方法,让我们看看this.m_extractor是怎么来的。

    image-20200801221007825

    可以看到,this.m_extractor的值是与传入的extractor有关的。这里需要构造this.m_extractorChainedExtractor,才可以调用ChainedExtractorextract()方法实现串接extract()调用。因此,首先需要构造这样一个PriorityQueue对象:

    继续跟进ChainedExtractor.extract()方法,可以发现会遍历aExtractor数组,并调用其extract()方法。

    image-20200801234521152

    此处aExtractor数组是通过ChainedExtractor的父类AbstractCompositeExtractorgetExtractors()方法获取到父类的m_aExtractor属性值。

    image-20200801234540446

    所以,poc中需要这样构造m_aExtractor

    m_aExtractor具体的值需要怎么构造,需要我们继续往下分析。先回到我们所要利用到的UniversalExtractor,跟进其extract()方法。

    image-20200801234615723

    此处由于m_cacheTarget使用了transient修饰,无法被反序列化,因此只能执行else部分,跟进extractComplex()方法。

    image-20200801234732346

    这里看到最后有method.invoke()方法,oTargetaoParam都是我们可控的,因此我们需要看看method的处理,跟进findMethod方法。

    image-20200801234829234

    可以看到第477行可以获取任意方法,但是要进入if语句,得先使fExactMatchtruefStaticfalse。可以看到fStatic是我们可控的,而fExactMatch默认为true,只要没进入for循环即可保持true不变,使cParams为空即aclzParam为空的Class数组即可,此处aclzParamgetClassArray()方法获取。

    image-20200801235221510

    显而易见,传入一个空的Object[]即可。回到extractComplex()方法,此时我们只要我们进入第192行的else语句中,即可调用任意类的任意方法。但此时还需要fProperty的值为false,跟进isPropertyExtractor()方法。

    image-20200801235942238

    可惜m_fMethod依旧是使用transient修饰,溯源m_fMethod的赋值过程。

    image-20200802000213645
    image-20200802000409329
    image-20200802000425863

    可以看到,由于this对象的原因,getValueExtractorCanonicalName()方法始终返回的是null,那么跟进computeValuExtractorCanonicalName()方法。

    image-20200802000543790

    此处不难理解,如果aoParam不为null且数组长度大于0就会返回null,因此我们调用的方法必须是无参的(因为aoParam必须为null)。接着如果方法名sName不以 () 结尾,则会直接返回方法名。否则会判断方法名是否以 VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES数组中的前缀开头,是的话就会截取掉并返回。

    image-20200802002321123

    回到extractComplex方法中,在if条件里会对上述返回的方法名做首字母大写处理,然后拼接BEAN_ACCESSOR_PREFIXES数组中的前缀,判断clzTarget类中是否含有拼接后的方法。这时发现无论如何我们都只能调用任意类中getis开头的方法,并且还要是无参的。

    image-20200802005549067

    整理下我们可以利用的思路:

    • 调用init()方法,对this.method进行赋值,从而使fProperty的值为false,从而进入else分支语句,实现调用任意类的任意方法。然而这个思路马上就被终结了,因为我们根本调用不了非getis开头的方法!!!
    • transient修饰的m_cacheTargetextractComplex方法中被赋值
    image-20200802011653243

    ExtractorComparator.compare()方法中,我们知道extract方法能被执行两次,因此在第二次执行时,能够在UniversalExtractor.extract方法中调用targetPrev.getMethod().invoke(oTarget, this.m_aoParam)方法。但是这种方法也是行不通的,因为getMethod()获取的就是图上红框的中的method,很显然method依旧受到限制,当我们调用非 get 和 is 开头的方法时,findMethod 会返回 null

    • 只能走方法被限制的路线了,寻找所有类中以 get 和 is 开头并且可利用的无参方法

    复现过Fastjson反序列化漏洞的小伙伴,应该清楚Fastjson的利用链寻找主要针对getset方法,这时候就与我们的需求有重合处,不难想到JdbcRowSetImpl的JNDI注入,接下来一起回顾一下。

    image-20200802012325020

    connect方法中调用了lookup方法,并且DataSourceName是可控的,因此存在JNDI注入漏洞,看看有哪些地方调用了connect方法。

    image-20200802012358277

    有三个方法调用了connect方法,分别为preparegetDatabaseMetaDatasetAutoCommit方法,逐一分析。

    • prepare()
    image-20200802012523723

    一开始就调用了connect方法,继续回溯哪里调用了prepare方法。

    image-20200802012552220

    execute方法,应该是用于执行sql查询的

    image-20200802012907664

    这个应该是用于获取参数元数据的方法,prepare()方法应该都是用于一些与sql语句有关的操作方法中。

    • getDatabaseMetaData()
    image-20200802012946090
    • setAutoCommit()
    image-20200802012958970

    必须让this.conn为空,对象初始化时默认为null,因此直接进入else语句。其实this.conn就是connect方法,用于保持数据库连接状态。

    回到connect方法,我们需要进入else语句才能执行lookup方法。有两个前提条件,this.conn为空,也就是执行connect方法时是第一次执行。第二个条件是必须设置DataSourceName的值,跟进去该参数,发现为父类BaseRowSetprivate属性,可被反序列化。

    那么,对于WebLogic这个反序列化利用链,我们只要利用getDatabaseMetaData()方法就行,接下来看看该怎么一步步构造poc。先从JdbcRowSetImpl的JNDI注入回溯构造:

    接着构造UniversalExtract对象,用于调用JdbcRowSetImpl对象的方法

    紧接着将UniversalExtract对象装载进文章开头构造的chainedExtractor对象中

    此处,还有一个小点需注意,一个在文章开头部分构造的PriorityQueue对象,需要构造一个临时Extractor对象,用于创建时的comparator,此处以ReflectionExtractor为例。其次,PriorityQueue对象需要执行两次add方法。

    回到PriorityQueue对象的readObject方法

    image-20200802022513752

    首先需要能进入for循环,for循环就得有size的值,size值默认为0,private属性,可以通过反射直接设置,但是不想通过反射怎么办,回溯赋值过程。

    image-20200802022659383

    offer方法处获得赋值,而offer方法又是由add方法调用。(注意此处会执行siftUp方法,其中会触发comparator的compare方法,从而执行extract方法)。

    image-20200802023215593

    不难理解,每add一次,size加1,根据上述heapify方法,只会从开头开始取一半的queue数组执行siftDown方法。所以size至少为2,需要执行两次add方法,而不是add(2)一次。

    至此,poc的主体就构造完成,其余部分就不在此阐述了,当然构造方式有很多,此处为方便萌新,分析得比较啰嗦,poc也比较杂乱,大家可以自行构造属于自己的poc。如果想要了解简洁高效的poc,可以参考一下Y4er师傅的poc[3]。

    体会

    初次接触完整的反序列化漏洞分析,在整个分析过程中收获到很多东西。笔者得到的不仅仅只是知识上的收获,在调试过程中也学到了很多调试技巧。另外本文看起来可能会比较啰嗦冗余,但其初衷是想要站在读者的角度去思考,去为了方便一些同样刚入门的人阅读起来,能够更加浅显易懂。学安全,我们经常会碰壁,对于一些知识会比较难啃。有些人遇到就选择了放弃,然后却因此原地踏步。不妨就这样迎难而上,咬着牙啃下去,到最后,你会发现,你得到的,远远比你付出的要多。可能对部分人不太有效、毕竟因人而异,但这是自己在学习过程中所体会到的,也因此想要分享给大家这么一个建议。相信在未来,自己对于反序列化漏洞的理解以及挖掘思路,能够有更深刻的认知,同时激发出自己不一样的思维碰撞。

    References

    [1] Oracle 7月安全更新

    https://www.oracle.com/security-alerts/cpujul2020.html

    [2] T3反序列化 Weblogic12.2.1.4.0 JNDI注入

    https://mp.weixin.qq.com/s/8678EM15rZSeFBHGDfPvPQ

    [3] Y4er的poc

    https://github.com/Y4er/CVE-2020-14645

    [4] Java反序列化:基于CommonsCollections4的Gadget分析

    https://www.freebuf.com/articles/others-articles/193445.html

    [5] Oracle WebLogic 最新补丁的绕过漏洞分析(CVE-2020-2883)

    https://blog.csdn.net/systemino/article/details/106117659


    Paper

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

    作者:江 | Categories:漏洞分析 | Tags:
  • 从反序列化到类型混淆漏洞——记一次 ecshop 实例利用

    2020-11-03

    作者:LoRexxar'@知道创宇404实验室
    时间:2020年3月31日
    English Version: https://paper.seebug.org/1268

    本文初完成于2020年3月31日,由于涉及到0day利用,所以于2020年3月31日报告厂商、CNVD漏洞平台,满足90天漏洞披露期,遂公开。


    前几天偶然看到了一篇在Hackerone上提交的漏洞报告,在这个漏洞中,漏洞发现者提出了很有趣的利用,作者利用GMP的一个类型混淆漏洞,配合相应的利用链可以构造mybb的一次代码执行,这里我们就一起来看看这个漏洞。

    https://hackerone.com/reports/198734

    以下文章部分细节,感谢漏洞发现者@taoguangchen的帮助。

    GMP类型混淆漏洞

    漏洞利用条件

    • php 5.6.x
    • 反序列化入口点
    • 可以触发__wakeup的触发点(在php < 5.6.11以下,可以使用内置类)

    漏洞详情

    gmp.c

    zend_object_handlers.c

    从gmp.c中的片段中我们可以大致理解漏洞发现者taoguangchen的原话。

    __wakeup等魔术方法可以导致ZVAL在内存中被修改。因此,攻击者可以将**object转化为整数型或者bool型的ZVAL,那么我们就可以通过Z_OBJ_P访问存储在对象储存中的任何对象,这也就意味着可以通过zend_hash_copy覆盖任何对象中的属性,这可能导致很多问题,在一定场景下也可以导致安全问题。

    或许仅凭借代码片段没办法理解上述的话,但我们可以用实际测试来看看。

    首先我们来看一段测试代码

    在代码中我展示了多种不同情况下的环境。

    让我们来看看结果是什么?

    我成功修改了第一个声明的对象。

    但如果我将反序列化的类改成b会发生什么呢?

    很显然的是,并不会影响到其他的类变量

    如果我们给class b加一个__Wakeup函数,那么又会产生一样的效果。

    但如果我们把wakeup魔术方法中的变量设置为2

    返回的结果可以看出来,我们成功修改了第二个声明的对象。

    但如果我们把ryat改为4,那么页面会直接返回500,因为我们修改了没有分配的对象空间。

    在完成前面的试验后,我们可以把漏洞的利用条件简化一下。

    如果我们有一个可控的反序列化入口,目标后端PHP安装了GMP插件(这个插件在原版php中不是默认安装的,但部分打包环境中会自带),如果我们找到一个可控的__wakeup魔术方法,我们就可以修改反序列化前声明的对象属性,并配合场景产生实际的安全问题。

    如果目标的php版本在5.6 <= 5.6.11中,我们可以直接使用内置的魔术方法来触发这个漏洞。

    真实世界案例

    在讨论完GMP类型混淆漏洞之后,我们必须要讨论一下这个漏洞在真实场景下的利用方式。

    漏洞的发现者Taoguang Chen提交了一个在mybb中的相关利用。

    https://hackerone.com/reports/198734

    这里我们不继续讨论这个漏洞,而是从头讨论一下在ecshop中的利用方式。

    漏洞环境

    • ecshop 4.0.7
    • php 5.6.9

    反序列化漏洞

    首先我们需要找到一个反序列化入口点,这里我们可以全局搜索unserialize,挨个看一下我们可以找到两个可控的反序列化入口。

    其中一个是search.php line 45

    这是一个前台的入口,但可惜的是引入初始化文件在反序列化之后,这也就导致我们没办法找到可以覆盖类变量属性的目标,也就没办法进一步利用。

    还有一个是admin/order.php line 229

    后台的表单页的这个功能就满足我们的要求了,不但可控,还可以用urlencode来绕过ecshop对全局变量的过滤。

    这样一来我们就找到了一个可控并且合适的反序列化入口点。

    寻找合适的类属性利用链

    在寻找利用链之前,我们可以用

    来确定在反序列化时,已经声明定义过的类。

    在我本地环境下,除了PHP内置类以外我一共找到13个类

    从代码中也可以看到在文件头引入了多个库文件

    这里我们主要关注init.php,因为在这个文件中声明了ecshop的大部分通用类。

    在逐个看这里面的类变量时,我们可以敏锐的看到一个特殊的变量,由于ecshop的后台结构特殊,页面内容大多都是由模板编译而成,而这个模板类恰好也在init.php中声明

    回到order.php中我们寻找与$smarty相关的方法,不难发现,主要集中在两个方法中

    而这里我们主要把视角集中在display方法上。

    粗略的浏览下display方法的逻辑大致是

    比较重要的代码会在make_compiled这个函数中被定义

    当流程走到这一步的时候,我们需要先找到我们的目标是什么?

    重新审视cls_template.php的代码,我们可以发现涉及到代码执行的只有几个函数。

    get_para只在select中调用,但是没找到能触发select的地方。

    然后是pop_vars

    恰好配合GMP我们可以控制$this->_temp_key变量,所以我们只要能在上面的流程中找到任意地方调用这个方法,我们就可以配合变量覆盖构造一个代码执行。

    在回看刚才的代码流程时,我们从编译后的PHP文件中找到了这样的代码

    order_info.htm.php

    在遍历完表单之后,正好会触发pop_vars

    这样一来,只要我们控制覆盖cls_template变量的_temp_key属性,我们就可以完成一次getshell

    最终利用效果

    Timeline

    • 2020.03.31 发现漏洞。
    • 2020.03.31 将漏洞报送厂商、CVE、CNVD等。
    • 2020.07.08 符合90天漏洞披露期,公开细节。

    Paper

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

    作者:江 | Categories:漏洞分析 | Tags:
  • F5 BIG-IP hsqldb(CVE-2020-5902)漏洞踩坑分析

    2020-11-03

    作者:Longofo@知道创宇404实验室
    时间:2020年7月10日
    English Version: https://paper.seebug.org/1272/

    F5 BIG-IP最近发生了一次比较严重的RCE漏洞,其中主要公开出来的入口就是tmsh与hsqldb方式,tmsh的利用与分析分析比较多了,如果复现过tmsh的利用,就应该知道这个地方利用有些鸡肋,后面不对tmsh进行分析,主要看下hsqldb的利用。hsqldb的利用poc已经公开,但是java hsqldb的https导致一直无法复现,尝试了各种方式也没办法了,只好换其他思路,下面记录下复现与踩坑的过程。

    利用源码搭建一个hsqldb http servlet

    如果调试过hsqldb,就应该知道hsqldb.jar的代码是无法下断点调试的,这是因为hsqldb中类的linenumber table信息没有了,linenumber table只是用于调式用的,对于代码的正常运行没有任何影响。看下正常编译的类与hqldb类的lineumber table区别:

    使用javap -verbose hsqlServlet.class命令看下hsqldb中hsqlServlet.class类的详细信息:

    使用javap -verbose Test.class看下自己编译的类信息:

    可以看到自己编译的类中,每个method中都有一个 LineNumberTable,这个信息就是用于调试的信息,但是hsqldb中没有这个信息,所以是无法调试下断点的,hsqldb应该在编译时添加了某些参数或者使用了其他手段来去除这些信息。

    没办法调试是一件很难受的事情,我现在想到的有两种:

    1. 反编译hsqldb的代码,自己再重新编译,这样就有linenumber信息了,但是反编译再重新编译可能会遇到一些错误问题,这部分得自己手动把代码修改正确,这样确实是可行的,在后面f5的hsqldb分析中可以看到这种方式
    2. 代码开源,直接用源码跑

    hsqldb的代码正好是开源的,那么这里就直接用源码来开启一个servlet吧。

    环境

    • hsqldb source代码是1.8的,现在新版已经2.5.x了,为了和f5中的hsqldb吻合,还是用1.8的代码吧
    • JDK7u21,F5 BIG-IP 14版本使用的JDK7,所以这里尽量和它吻合避免各种问题

    虽然开源了,但是拖到idea依然还有些问题,我修改了一些代码,让他正常跑起来了,修改好的代码放到github上了,最后项目结构如下:

    使用http方式利用hsqldb漏洞(ysoserial cc6,很多其他链也行):

    利用requests发包模拟hsqldb RCE

    java hsqldb https问题无法解决,那就用requests来发https包就可以了,先模拟http的包。

    抓取上面利用java代码发送的payload包,一共发送了三个,第一个是连接包,连接hsqldb数据库的,第二、三包是执行语句的包:

    根据代码看下第一个数据包返回的具体信息,主要读取与写入的信息都是由Result这个类处理的,一共20个字节:

    • 1~4:总长度00000014,共20字节
    • 5~8:mode,connection为ResultConstants.UPDATECOUNT,为1,00000001
    • 9~12:databaseID,如果直接像上面这样默认配置,databaseID在服务端不会赋值,由jdk初始化为0,00000000
    • 13~16:sessionID,这个值是DatabaseManager.newSession分配的值,每次连接都是一个新的值,本次为00000003
    • 17~20:connection时,为updateCount,注释上面写的 max rows (out) or update count (in),如果像上面这样默认配置,updateCount在服务端不会赋值,由jdk初始化为0,00000000

    连接信息分析完了,接下来的包肯定会利用到第一次返回包的信息,把他附加到后面发送包中,这里只分析下第二个发送包,第三个包和第二个是一样的,都是执行语句的包:

    • 1~4:总长度00000082,这里为130
    • 5~8:mode,这里为ResultConstants.SQLEXECDIRECT,0001000b
    • 9~12:databaseID,为上面的00000000
    • 13~16:sessionID,为上面的00000003
    • 17~20:updateCount,为上面的00000000
    • 21~25:statementID,这是客户端发送的,其实无关紧要,本次为00000000
    • 26~30:执行语句的长度
    • 31~:后面都是执行语句了

    可以看到上面这个处理过程很简单,通过这个分析,很容易用requests发包了。对于https来说,只要设置verify=False就行了。

    反序列化触发位置

    这里反序列化触发位置在:

    其实并不是org.hsqldb.util.ScriptTool.main这个地方导致的,而是hsqldb解析器语法解析中途导致的反序列化。将ScriptTool随便换一个都可以,例如org.hsqldb.sample.FindFile.main

    F5 BIG-IP hsqldb调试

    如果还想调试下F5 BIG-IP hsqldb,也是可以的,F5 BIG-IP里面的hsqldb自己加了些代码,反编译他的代码,然后修改反编译出来的代码错误,再重新打包放进去,就可以调试了。

    F5 BIG-IP hsqldb回显

    • 既然能反序列化了,那就可以结合Template相关的利用链写到response
    • 利用命令执行找socket的fd文件,写到socket
    • 这次本来就有一个fileRead.jsp,命令执行完写到这里就可以了

    hsqldb的连接安全隐患

    从数据包可以看到,hsqldb第一次返回信息并不多,在后面附加用到的信息也就databaseID,sessionID,updateCount,且都只为4字节(32位),但是总有数字很小的连接排在前面,所以可以通过爆破出可用的databaseID、sessionID、updateCount。不过对于本次的F5 BIG-IP,直接用上面默认的就行了,无需爆破。

    总结

    虽然写得不多,写完了看起来还挺容易,不过过程其实还是很艰辛的,一开始并不是根据代码看包的,只是发了几个包对比然后就写了个脚本,结果跑不了F5 BIG-IP hsqldb,后面还是调试了F5 hsqldb代码,很多问题需要解决。同时还看到了hsqldb其实是存在一定安全隐患的,如果我们直接爆破databaseID,sessionID,updateCount,也很容易爆破出可用的databaseID,sessionID,updateCount。


    Paper

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

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