逆向工程:揭示Google Colab未公開的秘密
來(lái)源|Open Source Data Science Tools
翻譯|程浩源
Google Colaboratory,簡(jiǎn)稱 “Colab” ,是一個(gè)免費(fèi)的Jupyter notebook云平臺(tái)。Colab 不僅可以為用戶提供 Python 和 R notebooks 的運(yùn)行環(huán)境,而且還允許用戶免費(fèi)共享部分 GPU 和 TPU 資源。
對(duì)于負(fù)責(zé)在 Jupyter Notebook 編程的數(shù)據(jù)科學(xué)家來(lái)說(shuō),Colab早已成為了默認(rèn)的運(yùn)行環(huán)境。然而,將 Colab 的算力運(yùn)用到除 Jupter Notebooks 以外的其他應(yīng)用,則是一件極其困難的事。
對(duì)于那些想生產(chǎn)模型,并將其帶出Notebook階段的機(jī)器學(xué)習(xí)工程師而言,這樣的問(wèn)題尤為明顯。雖然 Notebooks 非常適合用來(lái)探索,但將它與訓(xùn)練過(guò)程編入正式流水線的高級(jí)MLOps工具一起使用時(shí),效果不佳。
在遇到類似問(wèn)題后,我決定不讓 Colab 的局限性改變我的工作流程,而是嘗試圍繞我的工作流程去改變 Colab!
出于這個(gè)原因,今天我們將探究 Google Colab 的內(nèi)部結(jié)構(gòu),并嘗試稍微改變 Colab 的內(nèi)置規(guī)則。需要提前聲明的是,我們只是想探究 Colab,不會(huì)對(duì) Colab 本身或者它的用戶造成任何影響。
揭開幕后的秘密
Colab 的秘密在于它的后端:谷歌服務(wù)器為 Colab 提供基礎(chǔ)設(shè)施支持,讓用戶可以輕松運(yùn)行代碼。因此,我們第一步先分析 Colab 的 API,最簡(jiǎn)單的方法是檢查 Colab 在正常運(yùn)行期間進(jìn)行的 API 調(diào)用。
首先打開谷歌開發(fā)者工具,找到網(wǎng)絡(luò)(Network)選項(xiàng),然后運(yùn)行一段代碼,開發(fā)者工具開始記錄 Colab 發(fā)出的每個(gè)請(qǐng)求,然后我們發(fā)現(xiàn)了一些有趣的東西。
看上去這個(gè)URL(/tun/m/<id>/socket.io)是遠(yuǎn)程機(jī)器上運(yùn)行的 Jupyter socket 的代理。
如果我們從 Colab 界面的左窗格打開 Files 窗格(默認(rèn)顯示 /content 目錄),就會(huì)發(fā)現(xiàn)另一個(gè)有趣的請(qǐng)求:
這次 JSON 枚舉遠(yuǎn)程主機(jī)上的文件做出了響應(yīng)。這個(gè)URL(/tun/m/<id>/api/contents/)似乎指向提供文件元數(shù)據(jù)的服務(wù)。
雙擊 Files 窗格里的文件,Colab 就會(huì)開始下載文件并且展示文件詳細(xì)信息。如果單擊 /content/sample_data/README.md,則會(huì)對(duì) /tun/m/<id>/files/ 發(fā)出請(qǐng)求,返回該文件的內(nèi)容。
很明顯,https://colab.research.google.com/tun/m/<id>/ 是運(yùn)行 Colab 實(shí)例服務(wù)器的反向代理,它提供了 /socket.io 、 /files 和 /api/contents 端點(diǎn)。
讓我們看看是否有任何服務(wù)在 Colab 容器實(shí)例內(nèi)運(yùn)行。Colab 中內(nèi)置有 lsof 程序,運(yùn)行 lsof -iTCP -sTCP:LISTEN,列出所有在 TCP 端口上監(jiān)聽網(wǎng)絡(luò)請(qǐng)求的進(jìn)程。
看!colab-fileshim、node 和 jupyter-notebook 看起來(lái)都值得一探究竟。由于我們已經(jīng)使用過(guò)Files窗格,所以先看看 colab-fileshim,它有 PID 28,因此檢查 /proc 文件系統(tǒng),查看進(jìn)程的完整命令行:
接下來(lái)研究 /usr/local/bin/colab-fileshim.py。諷刺的是,我們其實(shí)可以直接在 Files 窗格做到這一點(diǎn)。這個(gè)程序更像是一個(gè)無(wú)趣的文件服務(wù)器,除了服務(wù)器本身可以響應(yīng) localhost:3453/files (帶有文件內(nèi)容)和 localhost:3453/api/contents (帶有 JSON 元數(shù)據(jù))。這意味著 Colab 會(huì)將這些請(qǐng)求從信道 URL 轉(zhuǎn)發(fā)到實(shí)例本身的端口3453。
在谷歌開發(fā)者工具的網(wǎng)絡(luò)選項(xiàng)中,我們可以右鍵單擊,復(fù)制 cURL 命令來(lái)重現(xiàn)它。例如,這是用于查看 README.md 文件的 cURL 命令:
如果在本地計(jì)算機(jī)終端上運(yùn)行此命令,會(huì)將該 README 文件的內(nèi)容打印到我們的終端,通過(guò)不斷嘗試和糾錯(cuò),我們可以減少大部分標(biāo)頭,只留下如下內(nèi)容:
x-colab-tunnel 標(biāo)頭表面上是為了防止 XSS 攻擊,實(shí)際上是為了防止我們或黑客從常規(guī)瀏覽器選項(xiàng)發(fā)出這些請(qǐng)求。
cookie 標(biāo)頭用于向 Google 提供身份驗(yàn)證,證明我們可以訪問(wèn)筆記本實(shí)例。由于 cookie 比較長(zhǎng)且難以處理,在本文的其余部分我們將其存儲(chǔ)到 shell 變量 $COLAB_COOKIE 中。
勝利1:揭示我們的服務(wù)器
現(xiàn)在,我們已經(jīng)發(fā)現(xiàn)了 Colab 的反向代理,看看是否可以用它為傳輸我們自己的請(qǐng)求。
我們可以簡(jiǎn)單地用自己的服務(wù)器替換進(jìn)程,而不會(huì)影響現(xiàn)有的 colab-fileshim.py 服務(wù)器!運(yùn)行 pkill -f colab-fileshim 來(lái)終止現(xiàn)有服務(wù)器,這樣就可以在同一個(gè)端口上啟動(dòng)我們自己的服務(wù)器。
下面做一個(gè)簡(jiǎn)短的演示,我們將啟動(dòng) Python 默認(rèn)的 HTTP 服務(wù)器,然后在 localhost:3453/files 中提供我們自己的文件。
瞧!我們現(xiàn)在可以更改 cURL 命令來(lái)下載我們自己的文件!
注意 Colab 單元中的日志行,可以證明我們的服務(wù)器處理了請(qǐng)求:
遺憾的是,由于需要 x-colab-tunnel: Google 標(biāo)頭,所以我們無(wú)法從瀏覽器直接訪問(wèn)服務(wù)器。
進(jìn)一步研究
繼續(xù)進(jìn)行研究,這次看一下之前發(fā)現(xiàn)的另一個(gè)有趣的東西,node。如果我們檢查 /proc/7/cmdline,會(huì)看到進(jìn)程正在運(yùn)行 /datalab/web/app.js。
一旦我們跳轉(zhuǎn)并閱讀該代碼,會(huì)發(fā)現(xiàn) /datalab/web 包含一個(gè)相當(dāng)標(biāo)準(zhǔn)的NodeJS應(yīng)用程序。除了之前看到的 /socketio/ 路由,它還公開了 /_proxy/{port}/ 路由。這應(yīng)該讓我們可以從 Colab 實(shí)例上的任何端口訪問(wèn)任何 URL!
啟動(dòng)一個(gè)快速服務(wù)器并測(cè)試一下。
可惜我們并不能從瀏覽器選項(xiàng)中查看這個(gè) HTML 頁(yè)面,Colab 拒絕代理任何請(qǐng)求,除非設(shè)置了 x-colab-tunnel: Google 標(biāo)頭,如果我們嘗試從瀏覽器訪問(wèn)這些URL,會(huì)看到一個(gè) HTTP 400 客戶端錯(cuò)誤頁(yè)面:
勝利2:揭示整個(gè)網(wǎng)頁(yè)
幸運(yùn)的是,我們可以使用谷歌瀏覽器擴(kuò)展程序?qū)?HTTP 標(biāo)頭即時(shí)插入瀏覽器請(qǐng)求中。我們將其設(shè)置為在所有請(qǐng)求上發(fā)送 x-colab-tunnel: Google 標(biāo)頭:
然后我們可以在瀏覽器中啟動(dòng)信道 URL!
前往Jupyter Notebook
最后,讓我們看看第三個(gè),也是最后一個(gè)有趣的進(jìn)程,jupyter-notebook,它監(jiān)聽端口 9000。
我們可以通過(guò)訪問(wèn) /tun/m/<id>/_proxy/9000 來(lái)使用之前的代理和標(biāo)頭,嘗試從瀏覽器直接訪問(wèn)端口。遺憾的是,出現(xiàn)了 HTTP 500 服務(wù)器錯(cuò)誤頁(yè)面,而不是 Jupyter 用戶界面。
奇怪的是,當(dāng)我們從 Colab notebook 本身運(yùn)行 !curl -i localhost:9000 來(lái)診斷這個(gè)問(wèn)題時(shí),仍然報(bào)錯(cuò)了:
之前 lsof 的輸出為我們提供了一個(gè)線索:Jupyter 只監(jiān)聽提供給 Colab 實(shí)例的私有 IP,而不是監(jiān)聽 0.0.0.0/:: (所有接口上的所有 IP),這大概是為了避免將 Jupyter 接口暴露給我們。
谷歌并沒(méi)有盡全力隱藏接口,因此有一個(gè)快速修復(fù)的方法。
為了繞過(guò)監(jiān)聽地址的限制,我們需要?jiǎng)?chuàng)建一個(gè)進(jìn)程來(lái)監(jiān)聽所有接口和 IP,并將它獲得的所有流量轉(zhuǎn)發(fā)到 Jupyter 正在監(jiān)聽的特定 IP 地址。我們可以安裝socket代理工具 socat(Socket Cat) 來(lái)做到這一點(diǎn)。使用 socat 將流量在 localhost:9000 和 $HOSTNAME:9000 之間來(lái)回轉(zhuǎn)發(fā):
這是一個(gè)開始!如果我們?cè)跒g覽器中重新加載 URL,我們會(huì)看到部分 Jupyter 用戶界面,但顯然出現(xiàn)了問(wèn)題。
這是因?yàn)?Jupyter 設(shè)定在域的根目錄下訪問(wèn)(URL路徑 /),但我們的 Colab 信道的路徑是 /tun/m/<id>/_proxy/9000,這會(huì)弄亂 CSS 和 JS 文件等資源的絕對(duì)路徑。
目前還沒(méi)有簡(jiǎn)單的解決方案,我們需要一個(gè)完整的(子)域來(lái)將流量轉(zhuǎn)發(fā)到Jupyter服務(wù)器。
勝利3:顯示Jupyter用戶界面
萬(wàn)幸,Colab 有一個(gè)隱蔽的端口轉(zhuǎn)發(fā)的官方解決方案,它提供了一個(gè)完整的子域!它隱藏得非常好,發(fā)現(xiàn)它比發(fā)現(xiàn)內(nèi)部反向代理花費(fèi)了更長(zhǎng)的時(shí)間!
如何使用 Colab 的官方端口轉(zhuǎn)發(fā)流量?從左側(cè)邊欄中打開 Code Snippets 選項(xiàng),然后找到 Output Handling 代碼段。
單擊“查看Source Notebook”,將會(huì)看到advanced_outputs.ipynb,這是Colab 高級(jí)用戶片段,展示了該平臺(tái)鮮為人知的功能。我們需要的特定片段可以在“瀏覽內(nèi)核上執(zhí)行的服務(wù)器”標(biāo)題下找到。
我們可以使用此代碼段將 Jupyter 用戶界面公開為子域。
現(xiàn)在,我們可以單擊該鏈接(將 /tree 附加到 URL 來(lái)穩(wěn)住 Jupyter),然后就可以查看功能齊全的 Jupyter UI!
終于,幾乎完成了所有工作。谷歌似乎已將官方代理限制為僅GET請(qǐng)求,只允許我們查看但不能運(yùn)行Notebooks。
結(jié)語(yǔ)
恭喜你看到了最后,希望這對(duì)展示你不了解的Colab相關(guān)工作原理以及學(xué)習(xí)逆向工程工具的半結(jié)構(gòu)化方法會(huì)有價(jià)值。也希望能激發(fā)你更深入地了解自己每天使用的工具和產(chǎn)品的內(nèi)部結(jié)構(gòu)!
原文:
https://dagshub.com/blog/reverse-engineering-google-colab/
*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。
物聯(lián)網(wǎng)相關(guān)文章:物聯(lián)網(wǎng)是什么
交換機(jī)相關(guān)文章:交換機(jī)工作原理