作者:LoRexxar'@知道創宇404實驗室
時間:2021年4月16日

4月12號,@cursered在starlabs上公開了一篇文章《You Talking To Me?》,里面分享了關于Webdriver的一些機制以及安全問題,通過一串攻擊鏈,成功實現了對Webdriver的RCE,我們就順著文章的思路來一起看看~

什么是Webdriver?

WebDriver是W3C的一個標準,由Selenium主持。具體的協議標準可以從http://code.google.com/p/selenium/wiki/JsonWireProtocol#Command_Reference查看。

通俗的講,WebDriver就是一個閹割版的瀏覽器,他提供了用于自動化控制瀏覽器的協議和接口。

你可以通過https://chromedriver.chromium.org/downloads來下載chrome版本的Webdriver,其中chrome還提供了headless模式以供沒有桌面系統的服務器運行。

一般來說,Webdriver應用于爬蟲等需要大范圍Web請求掃描的場景,在安全領域,掃描器一般都需要通過selenium來控制webdriver完成前置掃描。在CTF當中,我們也能常常見到通過控制Webdriver來訪問XSS挑戰的XSS Bot.

這里我借用一張原博的圖來描述一下Webdriver是如何工作的。

img

在整個流程當中,Selenium端點通過向Webdriver端口相應的seesion接口發送請求控制webdriver,webdriver通過預定的調試接口以及相應的協議來和瀏覽器交互(如Chrome通過Chrome DevTools Protocol來交互)。

由于不同的瀏覽器廠商都定義了自己的driver,因此不同的瀏覽器和driver之間使用的協議可能會有所不同。比如Chrome就是用hrome DevTools Protocol。

img

當然,需要注意的是,這里提到的端口為啟動webdriver時的默認端口,一般來說,我們通過selenium操作的Webdriver將會啟動在隨機端口上。

總之,在正常通過Selenium開啟的webdriver的主機上,將會開放兩個端口,一個是提供selenium操作webdriver的REST API服務,一個則是通過某種協議操作瀏覽器的服務端口。

這里我們用一個普通的python3腳本來啟動一個webdriver來確認這個結論。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import selenium
from selenium import webdriver  
from selenium.webdriver.common.keys import Keys  
from selenium.common.exceptions import WebDriverException
import os 

chromedriver = "./chromedriver_win32.exe"  

browser = webdriver.Chrome(executable_path=chromedriver)  
url = "https://lorexxar.cn"     
browser.get(url)
# browser.quit()

在腳本執行后顯示的日志中的端口為CDP端口

image-20210414145046829

通過查看進程其中命令可以確認webdriver的端口

image-20210414145731903

Chrome Webdriver 攻擊與利用

在了解了Webdriver基礎之后,我們一起來探討一些整個流程中到底有什么樣得安全隱患。

任意文件讀?

如果對Chrome DevTools Protocol有一些簡單的了解的話,不難發現他本身提供了一些接口來允許你自動化的操作webdriver。通過訪問/json/list可以獲取到所有的瀏覽器實例接口。

image-20210414153547538

通過這里的webSocketDebuggerUrl得到相應的接口路徑,然后我們可以通過websocket來和這個接口進行交互實現CDP的所有功能。例如我們可以通過Page.navigate訪問相應的url,包括file協議

image-20210414154248197

甚至,我們可以通過Runtime.evaluate來執行任意js

image-20210414154456944

如果你對CDP的api感興趣,可以參考https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate

但是問題也來了,我們如何才能從http://127.0.0.1:<CDP Port>/json/list讀取相應的webSocketDebuggerUrl 呢?至少我們沒辦法使用任何非0day來輕易的繞過同源策略的限制,那么我們就需要繼續探索~

通過REST API來RCE

前面提到,selenuim需要通過Webdriver開放的REST API來操作Webdriver。具體API可以參考webdriver協議或源碼https://source.chromium.org/chromium/chromium/src/+/master:chrome/test/chromedriver/server/http_handler.cc

這里我們主要關注幾個接口

  • GET /sessions 從這個端點我們可以獲取到所有目前活躍webdriver 進程的session,并且獲取相應的session id. image-20210414165208419

  • GET /session/{sessionid}/source如果我們獲取到Session id,那么我們就可以獲取到對應session的各種數據,比如頁面內容。

image-20210414165324321

相應的api可以參考https://www.w3.org/TR/webdriver/#endpoints

  • POST /session 通過POST數據我們可以發起一個新的會話,并且其中允許我們通過POST參數來配置新會話。

https://www.w3.org/TR/webdriver/#dfn-new-sessions

我們甚至可以直接通過設置新會話的bin路徑來啟動其他的應用程序

而相關的配置參數,我們可以直接參考selenium操作配置chrome的文檔https://chromedriver.chromium.org/capabilities

image-20210415113327289

這里我們可以展示通過post來啟動其他應用程序。并且我們可以通過配置args來配置參數。(要注意的是這個api對json的校驗非常嚴格,有任何不符合要求的請求都會報錯)

看到這里,我們有了一個大膽的想法,我們是不是可以通過fetch來發送post請求,即便我們無法獲取返回,我們也可以觸發操作。

image-20210415141629368

理想很豐滿,可惜現實很骨感~

當我們從其他域發起請求時,js請求會自動帶上Origin頭以展示請求來源。服務端會檢查來源,并返回Host header or origin header is specified and is not whitelisted or localhost.

我們可以從chromium種相應的代碼窺得相應的限制。

https://source.chromium.org/chromium/chromium/src/+/master:chrome/test/chromedriver/server/http_server.cc;l=28

到目前為止,我們仍然沒有找到任何可以遠程利用的方式,無論是通過webdriver的REST API 來執行命令,

這里我認為比較重要的是,這個校驗來源是std::string origin_header = info.GetHeaderValue("origin");,也就是說,是當發送請求頭中帶Origin時,才會導致這個校驗,眾所周知,只有當使用js發送POST請求時,才會自動帶上這個頭,換言之,這里的校驗并不會影響我們發送GET請求。

image-20210415155113706

跟著源碼,我們可以大致總結這部分的校驗內容

image-20210416104805262

除開上半部分中關于POST請求的校驗以外,下半部分的校驗更加直白,只要allow_remote為假,就一定回進入判斷,也就一定會經過net::IsLocalhost的校驗,而這里的allow_remote默認為假,只有當開啟allow-ips的時候才會為真。所以結論和原文相同。

  • 如果chromedriver沒有--allowed-ips參數
    • 無論任何類型的請求HOST都需要經過net::IsLocalhost校驗
    • 如果帶有Origin頭,那么Origin頭數據也需要經過net::IsLocalhost校驗
  • 如果chromedriver帶有--allowed-ips參數
    • GET請求不會檢查HOST
    • POST請求:
      • 如果帶有Origin頭,那么Origin頭數據需要經過net::IsLocalhost校驗。
      • 如果不帶有Origin頭,那么沒有額外的校驗。(如何用js完成沒有Origin的post請求呢?)
      • 如果HOST為ip:port格式,那么ip需要在whitelist中。

綜合前面的所有條件,我們能比較清楚的弄明白,只有在開啟--allowed-ips參數時,我們可以通過綁定域名來發起GET請求對應的API。否則我們就必須讓HOST通過檢查,但可惜的是,僅有ip和localhost能通過net::IsLocalhost校驗。我們可以簡單驗證這一點。

image-20210416111133589

那么問題來了,如果我們可以通過綁定域名來發送GET請求,那么是不是可以通過DNS Rebinding來讀取頁面內容呢?

配合DNS Rebinding來讀取GET返回

我們這里通過模擬一次DNS重綁定來探測,這里用一段簡單的代碼來做check

var i = 0;
var sessionid;

function waitdata(){

    fetch("http://r.d73ha3.ceye.io:22827/sessions", {
        method: "GET",
        mode: "no-cors"
    }).then(res => res.json()).then(res => function () {
        if(res.value){
            sessionid = res.value[0].id;
        }
    }());

    stopwait();
}

function stopwait(){

    if(sessionid!=undefined){
        console.log(sessionid);
        clearInterval(t1);

    }
}

t1 = setInterval('i +=1;console.log("wait dns rebinding...test "+i);waitdata()',1000);

image-20210416155420387

可以看到經過63次請求,dns cache失效并成功獲取到了127.0.0.1對應的seesionid。

attack chain!

總結前后的幾個利用點,我們現在可以嘗試把他們串聯起來。

  • 受害者使用webdriver訪問exp.com/a.html,a.html掃描127.0.0.1對應webdriver端口。
  • 跳轉到exp.com:<webdriver port>,開始執行JS+DNS Rebinding。
  • 通過構造JS+DNS Rebinding,我們可以讀取webdriver端口GET請求的返回,并通過GET /sessions獲取對應Session的debug端口以及session id。
  • 通過Session id,我們可以使用GET /session/{sessionid}/source獲取對應窗口的頁面內容。
  • 通過Session對應的debug端口,我們可以使瀏覽器訪問http://127.0.0.1:<CDP Port>/json/list,并且通過GET /session/{sessionid}/source獲取返回對應瀏覽器窗口的webSocketDebuggerUrl。
  • 通過webSocketDebuggerUrl與瀏覽器窗口會話交互,使用Runtime.evaluate方法執行JS代碼。
  • 構造JS代碼POST /session執行命令。

這里借用原文當中的一張圖片來展示整個exp利用過程。

img

寫在最后

在前文中提到過,不同的瀏覽器會采用專屬自己的瀏覽器協議,但其中差異比較大的是firefox和對應的Geckodriver,在Geckodriver上,firefox設計了一套與chrome邏輯差異比較大的調試協議,在原文中,作者使用了一個TCP連接拆分錯誤來完成相應的利用,并且在Firefox 87.0當中被修復。而safaridriver實現了更嚴格的host檢查,導致DNS rebinding漏洞并不能生效。而包括chrome、MS Edge 和 Opera在內的瀏覽器仍然受到這個漏洞威脅。

但可惜的是,盡管這里我們通過實現一個很棒的利用鏈構造利用,但唯一的限制條件,--allowed-ips這個配置卻非常的少見,在普遍通過Selenium來操作webdriver的場景中,一般的用戶都只會配置Chrome的參數選項,而不是webdriver的參數,而且在官網中也明確提出--allowed-ips會導致可能的安全問題。

https://chromedriver.chromium.org/security-considerations

這個條件讓整個漏洞利用變得苛刻起來,但也許在未來的某一天,Chrome的某個新功能就會重寫這部分功能呢?這也說不好對吧~~


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1559/