标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2024-3163] 作者: ecawen 发表于: [2022-08-20]
本文共 [417] 位读者顶过
概 述
[出自:jiwo.org]
老洞新用
资产收集找到了如图所示的资产,即使从来没碰到过,根据页面也能发现使用的是OnlyOffice。
扫描发现存在/index.html路由,直接拿到了目标的版本号,在dockerhub上找到了一样的版本onlyoffice/documentserver:5.4.2.46,5.4.2已经是3年前的版本了,很可能会存在一些老洞。
搜索发现了存在多个老洞,https://github.com/moehw/poc_exploits,CVE-2021-3199是一个任意文件写漏洞,影响小于5.6.3的版本,并且还有POC https://github.com/moehw/poc_exploits/blob/master/CVE-2021-3199/poc_uploadImageFile.py,使用该POC验证我们的目标时,文件上传都无法成功。分析发现CVE-2021-3199 是利用的uploadImageFile方法,存在一定的限制。
uploadImageFile方法中,formatStr变量来自于根据文件内容进行识别。如果encrypted为true,那么就会从post body中解析出formatStr,从而实现控制文件名。
而encrypted需要配置了cfgTokenEnableBrowser,"Directory traversal with Remote Code Execution when JWT is used in Document Server before 5.6.3",需要配置了JWT时才能利用,Docker环境默认未启用,目标也未启用。 其他的老洞没POC,只能下一份5.4.2.46版本的代码,分析是否存在其他的利用了。 DocService/sources/server.js 存在一个savefile路由,看到这名字就能猜到是文件上传相关的路由。
路由方法具体实现如下:
首先对cmd参数进行了一次JSON解析,由于默认未开启cfgTokenEnableBrowser,所以可以直接跳过中间一部分。
最后调用了storage.putObject方法写文件, 文件地址:cmd.getSaveKey() + '/' + cmd.getOutputPath() 文件内容来源于req.body , 也就是POST body。
cmd变量来自于对cmd参数值的JSON解析,在yield* addRandomKeyTaskCmd(cmd)方法中,
调用了savekey setter,随机生成taskkey再重新设置savekey属性值,导致不再可控。虽然savekey无法控制,但是outputpath没有被重新设置,还是来源于参数值,所以这里通过outputpath可以实现目录穿越到任意地址写文件。
返回400,不过文件实际已经写入,这里的web路径来源于搭建的docker环境中找到的,在测试目标时,使用这个目录也成功写入,怀疑目标一样也是用的docker。
虽然已经能任意地址文件写,但也仅仅只是开始。 Nodejs不像传统PHP、JAVA、ASPX,能够通过写入脚本类Webshell文件实现代码执行。那么如何从一个文件写到RCE?
武器化利用
1► 写计划任务
最容易想到的方法,写入文件到计划任务目录中实现RCE,
不过从搭建的docker环境中发现,跑express的用户非root,应该是无法通过计划任务实现RCE。实际测试目标,通过计划任务往 /var/www/onlyoffice/documentserver/server/welcome/目录写文件,最终也未成功,应该就是权限问题。
2► 覆盖原始web文件注册路由
跑express的用户为ds, web下的文件所属用户也都为ds,那么可以通过覆盖web的一些文件实现RCE。
首先会想到覆盖js文件,新增路由实现RCE,但是比较麻烦的是node需要重启才会加载上新增的路由,而我们暂时无法做到让目标重启。
<iframe id="aswift_1" width="918" height="0" frameborder="0" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-2245584124133133&output=html&h=280&adk=3915486513&adf=3265533771&pi=t.aa~a.2143935327~i.155~rp.4&w=918&fwrn=4&fwrnh=100&lmt=1661001752&num_ads=1&rafmt=1&armr=3&sem=mc&pwprc=4778642273&psa=0&ad_type=text_image&format=918x280&url=http%3A%2F%2Fcn-sec.com%2Farchives%2F1178448.html&fwr=0&pra=3&rh=200&rw=917&rpe=1&resp_fmts=3&wgl=1&fa=27&dt=1661001763258&bpp=3&bdt=12610&idt=3&shv=r20220817&mjsv=m202208160101&ptt=9&saldr=aa&abxe=1&cookie=ID%3Dbd790b99a3cb44d6-22592f99b0d5004a%3AT%3D1661001773%3ART%3D1661001773%3AS%3DALNI_MY9I1JCLvm7M_uMHOzJRb6-04D2Iw&gpic=UID%3D000008cec179b359%3AT%3D1661001773%3ART%3D1661001773%3AS%3DALNI_MbiIDCjZ7n4HB1USi6ocH0UvEBkkQ&prev_fmts=0x0&nras=2&correlator=7281757768493&frm=20&pv=1&ga_vid=243457661.1661001763&ga_sid=1661001763&ga_hid=844659622&ga_fc=0&u_tz=480&u_his=1&u_h=900&u_w=1440&u_ah=870&u_aw=1440&u_cd=24&u_sd=1&adx=51&ady=5020&biw=1423&bih=799&scr_x=0&scr_y=1901&eid=44759875%2C44759926%2C44759837%2C31068486%2C31068991&oid=2&pvsid=3512455768241726&tmod=684777477&uas=1&nvt=1&ref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3De5ZMevG3RnQo7OPphtqjoBv_SBMMT0N58ZDinXa97uMUOGfoBsJ6OdvaTO8-etWAAprPiyzoojs96g_eYXlfUa%26wd%3D%26eqid%3Dd18f513a009e0f3f000000026300e010&eae=0&fc=1408&brdim=0%2C0%2C0%2C0%2C1440%2C0%2C1440%2C870%2C1440%2C799&vis=1&rsz=%7C%7Cs%7C&abl=NS&fu=128&bc=23&ifi=2&uci=a!2&btvi=1&fsb=1&xpc=FAcjLBJfCF&p=http%3A/cn-sec.com&dtd=M" style="width:918px;height:0px;">
既然不能重启,那么很容易就能想到通过覆盖模板文件再通过SSTI RCE,覆盖模板文件后不需要重启服务即可利用,不过最后发现onlyoffice根本就没用到模板。 至此,只能找OnlyOffice中是否存在命令执行调用elf的路由,通过任意文件写覆盖elf来实现命令执行。
存在一个docbuilder路由,看名字应该是用来生成文档的,该路由方法的实现代码大概如下:
调用addTask方法,将生成doc的任务加到队列中。
在接收到任务后,调用ExecuteTask方法,执行该任务,
简单点说就是在ExecuteTask方法中,会通过spawnAsync命令执行方法调用 /var/www/onlyoffice/documentserver/server/FileConverter/bin/docbuilder ELF来生成文档,docbuilder文件所属用户也是ds,那么可以通过之前的文件写漏洞覆盖掉docbuilder ELF,再通过docbuilder路由触发我们上传的ELF。 写了一个简易的bash反弹脚本覆盖了/var/www/onlyoffice/documentserver/server/FileConverter/bin/docbuilder文件,本地测试Docker环境直接反弹SHELL成功,但是以我们对目标公司的了解,99.99%是无法连接外网的。
<iframe id="aswift_2" width="918" height="0" frameborder="0" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-2245584124133133&output=html&h=280&adk=3915486513&adf=697719288&pi=t.aa~a.2143935327~i.201~rp.4&w=918&fwrn=4&fwrnh=100&lmt=1661001752&num_ads=1&rafmt=1&armr=3&sem=mc&pwprc=4778642273&psa=0&ad_type=text_image&format=918x280&url=http%3A%2F%2Fcn-sec.com%2Farchives%2F1178448.html&fwr=0&pra=3&rh=200&rw=917&rpe=1&resp_fmts=3&wgl=1&fa=27&dt=1661001763267&bpp=2&bdt=12615&idt=2&shv=r20220817&mjsv=m202208160101&ptt=9&saldr=aa&abxe=1&cookie=ID%3Dbd790b99a3cb44d6-22592f99b0d5004a%3AT%3D1661001773%3ART%3D1661001773%3AS%3DALNI_MY9I1JCLvm7M_uMHOzJRb6-04D2Iw&gpic=UID%3D000008cec179b359%3AT%3D1661001773%3ART%3D1661001773%3AS%3DALNI_MbiIDCjZ7n4HB1USi6ocH0UvEBkkQ&prev_fmts=0x0%2C918x280&nras=3&correlator=7281757768493&frm=20&pv=1&ga_vid=243457661.1661001763&ga_sid=1661001763&ga_hid=844659622&ga_fc=0&u_tz=480&u_his=1&u_h=900&u_w=1440&u_ah=870&u_aw=1440&u_cd=24&u_sd=1&adx=51&ady=6862&biw=1423&bih=799&scr_x=0&scr_y=3701&eid=44759875%2C44759926%2C44759837%2C31068486%2C31068991&oid=2&pvsid=3512455768241726&tmod=684777477&uas=1&nvt=1&ref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3De5ZMevG3RnQo7OPphtqjoBv_SBMMT0N58ZDinXa97uMUOGfoBsJ6OdvaTO8-etWAAprPiyzoojs96g_eYXlfUa%26wd%3D%26eqid%3Dd18f513a009e0f3f000000026300e010&eae=0&fc=1408&brdim=0%2C0%2C0%2C0%2C1440%2C0%2C1440%2C870%2C1440%2C799&vis=1&rsz=%7C%7Cs%7C&abl=NS&fu=128&bc=23&ifi=3&uci=a!3&btvi=2&fsb=1&xpc=FjFu8Or20W&p=http%3A/cn-sec.com&dtd=M" style="width:918px;height:0px;">
首先还是得让命令执行执行得更顺畅一点,新增一个命令执行的路由,这时候我们已经能够通过docbuilder的命令执行 新增路由、重启服务了。 在本地的docker环境中,服务是ROOT用户通过supervisor来启动的。
虽然supervisor是ROOT启动的,不过任意用户都能重启supervisor服务。
在DocService/sources/server.js中,新增了一个如上的runExec.json路由,用于命令执行直接回显。
最开始为了实现nodejs命令执行回显,直接从网上复制了一段child_process.execSync命令执行的代码,差点被这个坑死,execSync在子进程完全关闭之前不会返回。导致在通过execSync命令执行curl一个不存在的站时会等待一段时间,这时候OnlyOffice这web就直接卡死访问不到了,还好curl默认会一段时间超时结束掉,如果执行了ping xx或者运行了一个无返回的elf会直接导致站挂掉,吓得赶紧把execSync换成spawn。
3► 搭建代理直通内网
通过 /runExec.json 命令执行,验证了目标确实各种协议都无法出网,为了进行内网渗透肯定得搭建代理。 在常规渗透中如果目标无法出网,通常会使用Neo-Regeorg等工具落地脚本文件实现代理进行内网渗透,但是很尴尬的是Neo-Regeorg并未提供js的脚本。Regeorg倒提供了JS版本https://github.com/sensepost/reGeorg/blob/master/tunnel.js ,但是实测问题很多没法使用。为了解决这个问题,我们开始吭哧吭哧的改Nodejs版的Neo-Regeorg,折腾了一两天改出来的Nodejs版Neo存在Bug只能代理HTTP流量,HTTPS、SSH等服务都有问题,也没发现是哪出的问题,然后就暂时搁置了这套方案。 在没有内网代理的情况下,内网渗透搁置了下来,大概思考了下,想出了三个方案: 1、继续修改Neo-Regeorg 2、通过命令执行,利用curl横移拿下一些机器后可能存在能够出网的机器 3、做WebSocket代理 (没找到现成合适的工具)
为了实现curl横向,先通过命令执行写入了一个fscan的base64到OnlyOffice机器上,直接开扫。
很幸运的是直接扫到了一些存在漏洞的财务系统,直接利用bsh实现了命令执行,但是很尴尬,拿了好几台内网的机器依然都是各种协议无法出网,再次陷入僵局。 虽然横向的机器也无法出网,不过该Web应用是Java,所以能直接使用现成的Neo-Regeorg。那么可以落地一个Neo-Regeorg到该Java系统上,虽然外网无法访问到,可以通过OnlyOffice新增一个转发路由,将外网的HTTP请求直接转发到内网该系统上。
<iframe id="aswift_3" width="918" height="0" frameborder="0" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-2245584124133133&output=html&h=280&adk=3915486513&adf=396097069&pi=t.aa~a.2143935327~i.279~rp.4&w=918&fwrn=4&fwrnh=100&lmt=1661001752&num_ads=1&rafmt=1&armr=3&sem=mc&pwprc=4778642273&psa=0&ad_type=text_image&format=918x280&url=http%3A%2F%2Fcn-sec.com%2Farchives%2F1178448.html&fwr=0&pra=3&rh=200&rw=917&rpe=1&resp_fmts=3&wgl=1&fa=27&dt=1661001763275&bpp=2&bdt=12617&idt=2&shv=r20220817&mjsv=m202208160101&ptt=9&saldr=aa&abxe=1&cookie=ID%3Dbd790b99a3cb44d6-22592f99b0d5004a%3AT%3D1661001773%3ART%3D1661001773%3AS%3DALNI_MY9I1JCLvm7M_uMHOzJRb6-04D2Iw&gpic=UID%3D000008cec179b359%3AT%3D1661001773%3ART%3D1661001773%3AS%3DALNI_MbiIDCjZ7n4HB1USi6ocH0UvEBkkQ&prev_fmts=0x0%2C918x280%2C918x280&nras=4&correlator=7281757768493&frm=20&pv=1&ga_vid=243457661.1661001763&ga_sid=1661001763&ga_hid=844659622&ga_fc=0&u_tz=480&u_his=1&u_h=900&u_w=1440&u_ah=870&u_aw=1440&u_cd=24&u_sd=1&adx=51&ady=8951&biw=1423&bih=799&scr_x=0&scr_y=5801&eid=44759875%2C44759926%2C44759837%2C31068486%2C31068991&oid=2&pvsid=3512455768241726&tmod=684777477&uas=1&nvt=1&ref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3De5ZMevG3RnQo7OPphtqjoBv_SBMMT0N58ZDinXa97uMUOGfoBsJ6OdvaTO8-etWAAprPiyzoojs96g_eYXlfUa%26wd%3D%26eqid%3Dd18f513a009e0f3f000000026300e010&eae=0&fc=1408&brdim=0%2C0%2C0%2C0%2C1440%2C0%2C1440%2C870%2C1440%2C799&vis=1&rsz=%7C%7Cs%7C&abl=NS&fu=128&bc=23&ifi=4&uci=a!4&btvi=3&fsb=1&xpc=H1VVRDuZqm&p=http%3A/cn-sec.com&dtd=M" style="width:918px;height:0px;">
通过bsh写Neo-Regeorg,直接new java.io.FileOutputStream("path").write(bytes)写文件即可。
<iframe id="aswift_4" width="918" height="0" frameborder="0" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-2245584124133133&output=html&h=280&adk=3915486513&adf=877723186&pi=t.aa~a.2143935327~i.281~rp.4&w=918&fwrn=4&fwrnh=100&lmt=1661001752&num_ads=1&rafmt=1&armr=3&sem=mc&pwprc=4778642273&psa=0&ad_type=text_image&format=918x280&url=http%3A%2F%2Fcn-sec.com%2Farchives%2F1178448.html&fwr=0&pra=3&rh=200&rw=917&rpe=1&resp_fmts=3&wgl=1&fa=27&dt=1661001763282&bpp=4&bdt=12624&idt=4&shv=r20220817&mjsv=m202208160101&ptt=9&saldr=aa&abxe=1&cookie=ID%3Dbd790b99a3cb44d6-22592f99b0d5004a%3AT%3D1661001773%3ART%3D1661001773%3AS%3DALNI_MY9I1JCLvm7M_uMHOzJRb6-04D2Iw&gpic=UID%3D000008cec179b359%3AT%3D1661001773%3ART%3D1661001773%3AS%3DALNI_MbiIDCjZ7n4HB1USi6ocH0UvEBkkQ&prev_fmts=0x0%2C918x280%2C918x280%2C918x280&nras=5&correlator=7281757768493&frm=20&pv=1&ga_vid=243457661.1661001763&ga_sid=1661001763&ga_hid=844659622&ga_fc=0&u_tz=480&u_his=1&u_h=900&u_w=1440&u_ah=870&u_aw=1440&u_cd=24&u_sd=1&adx=51&ady=8982&biw=1423&bih=799&scr_x=0&scr_y=5901&eid=44759875%2C44759926%2C44759837%2C31068486%2C31068991&oid=2&pvsid=3512455768241726&tmod=684777477&uas=1&nvt=1&ref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3De5ZMevG3RnQo7OPphtqjoBv_SBMMT0N58ZDinXa97uMUOGfoBsJ6OdvaTO8-etWAAprPiyzoojs96g_eYXlfUa%26wd%3D%26eqid%3Dd18f513a009e0f3f000000026300e010&eae=0&fc=1408&brdim=0%2C0%2C0%2C0%2C1440%2C0%2C1440%2C870%2C1440%2C799&vis=1&rsz=%7C%7Cs%7C&abl=NS&fu=128&bc=23&ifi=5&uci=a!5&btvi=4&fsb=1&xpc=8oYGDzPP52&p=http%3A/cn-sec.com&dtd=M" style="width:918px;height:0px;">
虽然开发比较菜导致写的Nodejs版Neo-Regeorg一直有问题,但是转发HTTP请求的Nodejs代码还是非常简单的,只需要原样将uri,headers,body转发到java服务器上,再原样将response拿回来即可。
新增forward路由后,重启OnlyOffice服务,通过以下命令成功代理到了目标内网。
项目结束后,问了下 purpleroc 大佬,直接帮我解决了Bug,直接完善了方案1,通过新增如下的路由,重启服务后可直接实现代理。
他也提了pull requests,https://github.com/L-codes/Neo-reGeorg/pull/66,添加该路由后就能直接使用NodeJS版本正向代理。
总 结
在项目结束后,我们去分析了最新版的savefile路由,发现该漏洞已经修复,同时我们也去分析了最新版本的OnlyOffice代码,也找到了最新版本的RCE漏洞,已提交给官方。此次攻击流程颇为艰辛与复杂,最后还是通过代码审计与武器开发能力,完成最后的攻击。 |