This very simple demo will take you through the process of setting up both a custom slash command and an incoming webhook.
Wikipedia is good to use for this because you don't need an API key to access their search API. All you need to do is identify your script with a user agent string (which we'll cover in a bit). In fact, because this is the default for all MediaWiki installations, you could repurpose this script to search any site built on MediaWiki.
Our script is going to:
- Take the values from a slash command on Slack and turn them into variables
- Use cURL to send the search string entered by your user to Wikipedia's Search API
- Accept the results returned by the Wikipedia search and figure out what to do with them
- Format the results into a proper JSON payload for the incoming webhook
- Use cURL to send the formatted JSON to the incoming webhook's URL
- Post the results to the Slack channel where the slash command was used
This tutorial makes use of the following technologies:
Don't worry too much if you've never used one or more of those. Our use of them will be thoroughly explained in the tutorial.
You'll need the following tools:
- A plain text editor. If you want a free one, TextWrangler for Mac or Notepad++ for Windows are both great.
- A hosting account running PHP 5 and cURL where you can put the script we're going to write. Pretty much any shared hosting account in the world should work for this.
- A Slack account with privileges to create integrations. (A free team is fine.)
Slack's custom slash commands perform a very simple task: they take whatever text you enter after the command itself (along with some other predefined values), send it to a URL, then accept whatever the script returns and posts it as a Slackbot message to the person who issued the command. What you do with that text at the URL is what makes slash commands so useful.
If you're not familiar with slash commands, I recommend reading through my slash command tutorial before you tackle this one.
Webhooks are common tools for services with APIs. They provide a structured way to send information into or out of the service. Slack's incoming webhooks accept data in a format called JSON, which is a widely-used format for this kind of data. We'll go over a little bit of it when we're setting up the webhook, but what you need to know for now is that an incoming webhook lives at a specific URL, and when send this formatted JSON to that URL, Slack can take it and post a message to any channel, group, or direct message that you have access to.
What that means for our project is that when someone uses our Wikipedia search slash command, we have a way to post the results publicly for others to see.
Set up Slack
Go to your integrations page at Slack (http://my.slack.com/services/new) and scroll down to the bottom section, "DIY Integrations & Customizations". Click on the "Add" button for "Slash Commands".
Create the text command itself. This is the text that the user will type after the slash. I use , because it's just enough to indicate that this isn't just any wiki that you're searching. But you could use the entire word or . Whatever makes the most sense for your command and your users.
For now you can leave everything else empty. We'll come back and finish setting this up in a bit. Just scroll down to the bottom and click the "Save Integration" button.
Go to your custom integrationsconfiguration page Click on the "Add Configuration" button for "Incoming Webhooks". If your team limits which users can install and configure custom integrations and apps, you may see a "Request" button instead, in which case this becomes a lot more complicated! You may want to follow along with this tutorial with your own personal test team as a development environment.
All incoming webhooks require a default channel to post to. We're going to see how to override that default later, but for now, either pick one of your existing channels or use the "create new channel" option to make new channel.
When you've done that click the "Add Incoming Webhook Integration" button.
Put "Slackipedia" in the Descriptive Label field. This will help you distinguish this webhook from any others you set up in your list of configured integrations.
Also put "Slackipedia" in the Customize Name field. This is what the webhook will use as a "username" when it posts to your channels.
Now you can upload the Wikipedia logo icon that's in the code package you downloaded. (Or, you know, use something inferior.)
Save your settings again, and you're done with the webhook for the moment.
The PHP script
Now we're going to go step by step through the PHP script. If PHP isn't your jam, this should still be pretty simple to apply to the language of your choice.
If you're familiar with cURL, feel free to jump over this section.
cURL is an open source command line tool that lets you transfer data with URL syntax, which is what web browsers use, and as a result, much of the web uses. Being able to transfer data with URL syntax is what makes webhooks work. The thing about cURL that's useful for us is that not only can you use it from the command line (which makes it easy to use for testing things), but you can interact with it from most modern scripting language.
PHP has had support for cURL for years, and we're going to take advantage of that so that our script can receive data from Slack and then send it back in. We'll be using a few very basic commands that are common for this type of task. All of the cURL that we use in this script will be transferable to any other webhook script that you want to write.
Set up your config
There are a few things we're going to need for the script, so let's set those up at the very top.
First, your incoming webhook URL. This tells the script where to send the reply it gets back from Wikipedia. Get this URL from your incoming webhook configuration page.
Next, the icon for your integration. You probably remember that we already set a custom icon for the webhook on the configuration page, but you can also specify within the webhook's payload. This is useful if you want to reuse the webhook itself for a few slash commands, or just as a fallback for the one on the configuration page.
And now some defaults for Wikipedia itself.
You can change the language you're searching in with this. Set it to for English. You can find other ISO 639-1 language codes found here.
By default, the WikiMedia API will return 10 search results. That will make for a very long message in Slack, so I like to default it to 4. You can play around with this and see what makes the most sense for your team.
Finally, the WikiMedia API requests that the client is identified by a User-Agent string. You could take our word for it or you could read the genuine article. This allows the Wikipedia folks to look through their logs and see how often our script is being used to search their site. Feel free to leave this set to this, or you can update it with any info you want.
Create some variables from the slash command
The first thing you need to do when the script is called by your slash command is grab all the values from the command and make variables out of them. We'll use most of them at various points in the script, and the variable names will be easy to remember.
First, the command string itself. In our case, we know it's . But it's good to set it as a variable if you decide you want to test against it.
Next, the text that was entered with the command. For this webhook, this will be the search string we send to Wikipedia.
The token is an additional identifier that's sent with the slash command that you could use to verify that what's calling your script is actually your slash command.
The channel ID is the channel where the slash command was issued. We'll use this later in the script to tell the webhook to return the results to that same channel.
We're going to display the user name with the webhook message, just so it's clear who caused the message to appear in the channel.
Finally, we want to encode the search string properly. All API searches go to Wikipedia as requests, which means the search query gets appended to the URL of API request. That means we need to properly encode the text for use in a URL. PHP has a function just for that.
Build the URL to send to Wikipedia
Before we can actually send the URL to Wikipedia, we need to create it. We'll make one more new variable called , which uses PHP's string concatenation to add some of the variables we've been creating to the standard Wikipedia API search URL.
The MediaWiki API responds to GET requests, which means that all the search values are passed to the server as part of the URL. For our search we're going to pass four values.
- , which tells the Wikipedia server what function of the API we're going to use. In our case it's going to be .
- , which is the string will be looking for. In our case it will be our variable.
- , which tells the Wikipedia server how we want to receive the data it sends back. We want .
- , which tells the Wikipedia server how many results to return to us. We're going to limit it to for this tutorial. You can do any number, but bear in mind the context of a Slack channel and whether it would make sense in that context to have 10, or even 20, results. It probably wouldn't.
The completed URL will look like this:
Taking that and replacing all the appropriate parts with our variables results in this:
Send the URL to Wikipedia
Now we're ready to actually send the search to Wikipedia. This is where the first use of cURL happens.
First we have to initialize cURL, and we'll create a variable with that, which we'll use in the next few lines to set some options and execute the call. (It's very common to use the variable name for this. It stands for and it can be used over and over for all your cURL handles. However, I like to give my cURL handles more descriptive names.)
Next we set a couple of options. The first one, , tells cURL that we expect to get something back from the URL that we're calling. In this case it's the search results. The second one, is where we pass that User-Agent string that we created to identify our script.
Now we execute our cURL. This is basically like your script opening up a web browser, typing a URL into the address field, and hitting "Return". Just like with the initialization command, we're going to set a variable for this, which will allow us to do a test immediately.
Now we check to see if that execution returned anything we can use. If not, the contents of that variable will be . In that case, we create a variable called and put in an error message. Later on, we're going to send the value of back to Slack. If there are no results, we want to let user know that. However, if there are results, then we'll leave blank, and fill it in with some of the results (after we do some formatting).
Note the use of the triple equal signs! There's a very good PHP reason for doing your comparison with triple equals instead of just double. We use to enforce a little type safety, ensuring that isn't just something false-like enough to satisfy the lazy evaluation of a but is in fact, a boolean value that could be specified as none other than itself.
And now we close the cURL connection, so there's not something on your server just taking up memory.
Understanding the returned data, or:
A short discursion into JSON and arrays.
Assuming we got data back from Wikipedia, we need to get it properly organized for sending back through our webhook. Let's take a look at what Wikipedia sends back in their search results. It's important to know that all sites structure their JSON in the way that makes sense for their data, which means you need to understand what that structure is and how to transform it into the structure that Slack's webhooks use.
We'll use the search term as our example. If you paste this URL into a browser's address bar (or run it with cURL in a terminal):
You'll get back a JSON object that looks like this:
It's a very simple object, with no more information in it than is absolutely necessary. The first item in the array is the search term. That's followed by three more arrays, each of which has four quoted strings in it, separated by commas. Knowing what we know about Wikipedia, we can make some guesses as to what each of those objects.
- First is a page title
- Second is a page summary
- Third is a page URL
Fortunately, that's all we need to build a nicely formatted and informative message to return to Slack.
However, it will be much easier to work with the data in PHP if we turn it into an array first. So we'll use PHP's function, which does just that.
That will give us a standard indexed PHP array which would look like this. Notice that all of the items we got back from Wikipedia now have numbers associated with them. The number for each item is called its , and an array that uses numbers to identify its contents like this is called an "indexed array".
In an indexed array, we reference each item in the array by its index. Using the array above, if we wanted to make a variable with the title for the Wikipedia page "Airship", we would use . That basically tells PHP "Look in the array called , go to index , and within that, find index .
Building on that, if we want to make variables from the title, summary, and URL for the page "Airship".
To do the same thing for the page "Airship Italia", it would look like this.
See the pattern? All the titles are in index , all the summaries are in index , all the URLs are in index . Now that we understand how the results are coming back, we can start building the message that we're going to send into the webhook.
Designing your message
The information you send into Slack and how it's formatted will directly effect how successful your integration is. No matter what the message is, you want to provide enough data for the user to take any action that might need, but not so much that it overwhelms the channel.
In the case of our Wikipedia search, the message is something that will be triggered by a human user so there shouldn't be too much concern about spamming a channel, at least from a technical standpoint.
Other good practices for bot messages in Slack:
- Identify the bot that posted the message. We'll do that with a custom username and a custom icon. These are both helpful for team members to see in the context of a Slack channel to help distinguish human messages from bot and integration messages.
- Identify the team member that caused the message to be posted, if possible. This good etiquette for both the message poster and their fellow team members.
We've already set the username and icon webhook configuration page. Setting the name on the configuration page can be very useful for finding Slackipedia in your list of integrations if you have a lot of incoming webhooks.
For our message, we have two cases that we'll need to handle:
- In most cases, Wikipedia will return one page as its best result, along with as many other options as it can, up to your number. In those cases, we want to display the title and summary of the page, as well as a link to and then list the other options, in case the primary result wasn't what the user was looking for.
- If Wikipedia doesn't have one page that it considers the best for that search, it will return a link to a disambiguation page, along with a list of possible options. In that case, we need to make sure that's clear to the user.
Options for displaying data in a webhook message
There are two basic places in a webhook message to display text.
- The parameter, which is the main body of the message. This is required unless you send in .
- Attachments in incoming webhooks are not attachments in the sense of email attachments; they're not files. They're additional chunks of data that can help with formatting and clarifying the message that your webhook posts. You can have up to ten attachments per message.
We'll put the linked name of the person who performed the search in the main message field, along with the text they searched for.
The search results will go in an attachment. Because Wikipedia will always return more than one result, we'll put the primary result in the attachment's text field, then the other options in attachment fields, which will help differentiate them from the primary result. To keep the in-channel message from getting too long, we'll just display the link for each of those items.
Creating the webhook payload
We'll use an statement to see if contains anything, and if it does, we'll decode the JSON and put it into a PHP array, which is much faster and easier to work with than the JSON text string.
While we're working with the array, we'll put our non-primary results into their own array called so they'll be easier to work with. We're only going to display the link for each of these items, which are all in the third sub-array of .
Since we only need the alternate options, we can use to remove the first item from our new array. Finally, we'll set the number of alternate options as a variable that we can display later.
To identify who performed the Wikipedia search, we'll insert the linked username of the user, along with the text that was searched for. Both the user_id and user_name are part of the variables that came over with the slash command. This will also be the start of the string that we'll send to Slack as the parameter of the main message.
Next we want to verify whether the primary search result is a disambiguation page or not. If the first item in contains the string "may refer to:", then we know it's a disambiguation page. (Note: This is not the greatest way to test something like this, because Wikipedia could conceivably change that text, but it's the only bit of info in our JSON object that identifies these pages.)
In PHP we can use the function to check whether a specific string exists within any other string. That's useful in this case because we don't know what the value of is at this point.
In keeping with my habit of obvious naming for later maintainability, we'll set some variables for the pieces of the primary result
And finally we're ready to create the message and attachments that we'll send into Slack through the webhook.
To start with, if we got a reply from Wikipedia, but the reply was that nothing could be found, then will be empty, and that's all we need to send back through the webhook.
If we did get results, then first we check to see whether we're dealing with a disambiguation page, and make the reflect that.
Then we loop through the other options, putting a line break between each one. Remember, we're only displaying the link for each of the other options, and Slack will autoformat a complete link, so all we have to do is send the link itself.
Building the final PHP Array for the Payload
The last step is to put all the variables we just made into a new array, and encode them as JSON to pass to the incoming webhook. (The first two items, and are not strictly necessary because we already set those on the configuration page. I included them here because you can use these to override the default settings of a webhook.)
To include your new slash command in the autocomplete list, check the box, then add a short description and a usage hint. This is especially useful if you need to create a longer command for your users. The description and usage hint will display in the autocomplete list.
Finally, enter the descriptive label. This is what will show on the list of slash commands in your integrations list page, so make it something relevant.
In the (belated) spirit of April Fool’s Day, I wanted to slightly diverge from a strictly infosec topic and talk about something that can be used for good (work) or evil (trolling coworkers): Slack bots.
Incoming Webhooks allow external applications to post into Slack. The webhook processes HTTP requests sent to a provided URL. The data is stored in a JSON payload and processed by Slack on receipt. Incoming webhooks are a good choice when you want to post data into Slack from an external source that wasn’t requested by a user. Incoming webhooks can currently only post in public channels, they do not support direct messages, group chats, or private channels.
To use incoming webhooks, you need to add and configure the integration in your team’s Slack settings.
In the Slack application or web interface, click the Team dropdown in the top-left. Select , then click in the top-right, and search for “Incoming WebHooks” in the search bar. Click to add an incoming webhook to your channel.
Set to any channel. You can specify the channel the post will be sent to with JSON later.
Click Add Incoming WebHooks Integration.
Note the WebHook URL, we will use that in every webhook request.
Scroll down to Integration Settings. Modify the Descriptive Label, Customize-Name, or Customize Icon settings as desired; however, the bot name and icon will be modified with curl later.
Now we’re good to go!
As an aside, if you plan to use this for trolling, it might make sense to make a new channel to add the webhook to. A notification is posted to the channel when the integration is added.
Using the WebHook
Basic usage example with curl:
The payload section of the request is where the magic happens. We used it to configure the bot’s username, the bot icon (icon_emoji), channel, and message text. Since we can modify this info on the fly, we really only need one webhook integration for all of our bots.
Slack provide a bunch of neat features and functions for webhooks. Check out the official documentation for messages for info about basic formatting, attaching content & links, threading messages, interactive buttons, and message guidelines. The documentation also provides a message builder to easily hone how your message will look with a live-updating preview.
Cobalt Strike Integrations
To illustrate how incoming webhooks can be useful for security testers, let’s make a script that sends the Cobalt Strike event log to Slack. Something similar to this could be modified for use with just about any post-exploitation framework; however, some frameworks will require more creativity than others to implement. As a heads up, there will be some light Sleep and Aggressor scripting ahead! This post won’t go through the whole script, but will cover its basics to demonstrate how to post to Slack from Cobalt Strike.
Update 4/25/17: It’s important to note that you will be sending potentially sensitive information to Slack, such as usernames, target IPs, target hostnames, and teamserver info. Be sure to review the code and make modifications to prevent unwanted sensitive information from going to Slack, especially if you plan to use this on a production test.
The full code for this script is available on GitHub here. The final script has a GUI menu to configure the script settings within Cobalt Strike. Creating the GUI is outside the scope of this post, but for more info check out the official documentation on Custom Dialogs.
The script will use curl to send any messages received by the Aggressor event events, such as event_action or event_beacon_initial. For the curl command, we’ll need the incoming webhook URL, a channel to post the updates in, and an emoji shortcode for the bot icon. We get the webhook URL from the Slack configuration page, we can set up a new channel (#pentest in this example), and the emoji is easily found in the Slack interface.
Here are snippets of the script that send the message to Slack:
The first four lines set the scalars (variables) for the curl command. Line 5 sets the script to be disabled by default, to reduce message errors before configuring script settings. The sub sendMessage portion of the script defines a function to define the curl command and send the final message. The function is passed two arguments: a timestamp and message to send. This information is dervied from the triggering Aggressor scripting events.
Here’s a snippet of a few triggers:
Each event returns various arguments that can be used within their scriptblock. For example, on event_beacon_initial returns the contents of the event message as $1 and the message post timestamp as $2. The arguments are passed to sendMessage, with extra text as needed, for sending. We individually define the argument mapping and test for an enabled value of ‘true’ for each event in the final script.
Here’s the script in action:
Sidenote: To get the script to run even when no users are connected, use agscript within screen on the teamserver.
For another example of using incoming webhooks with Cobalt Strike, check out the post Slack Notifications for Cobalt Strike by Andrew Chiles at Threat Express
Custom Slash Commands
Slash commands provide the ability for Slack users to trigger and interact with third-party applications. The most popular example is using to return a random GIF matching the search phrase. Slash commands are more complex to implement than an incoming webhook, but not prohibitively so.
Here’s how the whole process works:
When a user runs the slash command, Slack sends the following information to the configured server:
The server then validates the token, performs its magic, and returns a response to the response_url. Slack accepts similar formatting for Slash commands as it does for incoming webhooks. For more details, check out the official documentation.
To illustrate how to set up a custom slash command, we’ll walk through an example of creating a bot that takes a provided user’s name and returns a pre-canned troll for that user. The source for the example is available on GitHub here. The script is loosely based on Jan-Piet Mens’s Where are you slack team members at the moment? blog post and script.
Under the App Directory, search for “Slash Commands.”
Choose the trigger command, enter it in the box, and click “Add Slash Command Integration.” For our example, we’ll configure our trigger as .
Under Integration Settings, we will need to configure a URL, Method, Name, Icon, and (optionally) Autocomplete help text.
The URL will be the address of the host processing our slash command and returning the output. For our example, the server will be listening on port 8080.
The Method can remain POST. The options provided are POST and GET.
We’ll fill in the remaining info and click save.
Note the token string. We will use that to filter unwanted requests to the server.
Now when we run in Slack, it will send a request with “troll” as the command attribute, “brody” as the text attribute, and the other applicable information detailed above to the configured URL.
The server will consist of a Python script and a troll dictionary text file. You can either run it on a VPS or a PaaS provider, like Heroku. Using a VPS provides additional easy customizations, such as pulling our troll dictionary from a text file rather than a hardcoded dictionary, so this example will do that.
In short, our script will act as a web server and wait for Slack requests. When a request is received, the script validates the Slack token, looks in for a name (key) matching the provided name, and returns a random troll associated with that name to Slack. Slack will post the message into the channel from which the slash command originated.
Two important things to keep in mind when processing slash commands requests:
- Responses must be received by Slack within three seconds of the outgoing request. Otherwise, Slack will discard the response. If the script may take significant time to process a slash command request, use incoming webhooks for the response. (For an example of that, check out the story bot in the next section.)
- Responses can either be In Channel or Ephemeral. In Channel responses are posted in the channel the slash command originated from and are visible to everyone in that channel. Ephemeral responses are only visible to the user who ran the slash command. For more info, check out Slack’s documentation.
Our server script will rely on the Python Bottle framework to handle processing the incoming and outgoing requests. Install Bottle if it isn’t already:
Next, we put our script, , and our dictionary of trolls, , up on the server.
Modify the following settings in , as needed:
- Change the slack_token variable on line 19 to your slash command’s token. This is required for the script to work properly.
- Modify the file path for in the trollfile variable on line 22 if the file is not in the same directory as .
- To change the port, modify the serverport variable on line 23.
Update with your dank trolls using the format:
If multiple trolls are provided for one username, the script will select one randomly. As a caution, single quotes can cause the script to error out if they aren’t escaped properly.
Now we’re ready to go! Start the server:
Here’s a demo of the script in action:
Other Evil Ideas
Of course, there’s more evil that can be had using webhooks and slash commands in Slack.
Nothing beats a good old-fashioned spamming. With incoming webhooks, spam is as easy as a while true loop.
A Bash script is available here for ease.
Slack limits incoming webhooks to one message per second, but it does “allow bursts over that limit for short periods.” In my “testing” I’ve found the bursts are generous. In fact, if you run a few of these loops in parallel, you can post so much that it causes everyone’s Slack app to slow down to the point of being unusable. Not that I know that from experience…
Everyone likes a good story. Why not add a nice, long story to Slack that can be triggered with a slash command? The source for this example is available on GitHub here.
The Slack and server setup are the same as the troll bot example above, with a few exceptions:
- We also need an incoming webhook configured
- We’ll use a different trigger word
- The script is
- The story itself will be put in , which is configurable with the storyfile variable on line 22.
Since the story will take longer than the three seconds Slack expects its response within, we’ll return an ephemeral response to the user who triggered the story as an acknowledgment and use incoming webhooks to post the story. The story will be pulled from a text file with each message’s text on a new line. To pace the story correctly, we’ll add some random sleep times between lines.
When the command is issued, the bot will run.
Using the script provided, it would be trivial to add more stories to the bot and trigger different ones with the slash command, like .
Twitter can be a great source of useful, and less than useful, information that can be pulled into Slack. For instance, we could pull trending posts in a certain hashtag or see if a certain mountain in Seattle is “out.” Instead, we’ll make a slash command to pull a provided user’s internet fame (Twitter follower count) and post it in the channel.
To set this up, we’ll need to set up a slash command. In my example, I use the following syntax:
Place the final script on your server and modify the Slack slash command token (line 22) and, optionally, server port (line 24). The script uses Beautiful Soup to parse the follower count from Twitter. Install Beautiful Soup if it’s not present on your server:
Beautiful Soup usage is out of the scope of this post, but here is the the code that handles the follower parsing:
The script concatenates the follower count with a random snarky phrase about fame (pulled from a hardcoded list on line 37) and returns an In Channel message:
Errors are returned as an Ephemeral message.
Providing a mechanism for your Slack bots to parse web content opens the door to countless other lulz, but that’s an exercise left to the reader to determine and implement.
Slack also provides a Bot User API, which allows users to interact with third-party applications via natural chat. The API provides a wealth of options and could prove useful for both work and teh lulz.
This post focused on Slack; however, most of the functionality covered should be possible on competing platforms. Both HipChat and Microsoft Teams provide APIs for third-party integrations.
We covered the incredibly useful incoming webhook and slash command APIs in Slack and demonstrated a few ways they can be used for work, such as integrating Cobalt Strike with Slack, and for fun, such as annoying coworkers. The post only scratches the surface of how powerful Slack integrations are. If these examples interested you, I encourage you to check out some of the cool ways people have integrated third-party applications with Slack and try it on your own. I’d love to hear about any Slack bots you come up with, especially those designed to annoy and harass those in your Slack team!