├── README.md └── whoamislack.py /README.md: -------------------------------------------------------------------------------- 1 | # WhoAmISlack 2 | Simple Command Line Tool to Enumerate Slack Workspace Names from Slack Webhook URLs. 3 | 4 | You need a Slack OAuth User Token with team:read privs to enumerate a workspace name. These are free and easy to create. 5 | 6 | **Important: You can use any account's OAuth token to enumerate cross-workspace names. This means you can enumerate a workspace name for any Slack Webhook.** 7 | 8 | ## Installation & Usage 9 | 10 | 1. Install the `requests` pypi library. 11 | 12 | ``` 13 | pip install requests 14 | ``` 15 | 16 | 2. Run the `whoamislack.py` script. 17 | 18 | ``` 19 | python3 whoamislack.py --token https://hooks.slack.com/T234DSLKJ/BSDFJWEK23/e9Wi324jlkasdf 20 | ``` 21 | 22 | The script takes one (or two) command line arguments. 23 | - A Slack User OAuth token with `team:read` privs (see below how to generate it). You can set an env variable named `SLACK_OAUTH_TOKEN` to the token value or pass the token on the command line with the `--token` flag. 24 | - A Slack Webhook URL. This is a required and a positional argument. 25 | 26 | ## Generate a Slack User OAuth Token 27 | 28 | 1. Login to any Slack workspace (or create a new one) in the browser and visit [https://api.slack.com/apps](https://api.slack.com/apps). 29 | 2. Click `Create New App`. Select `From scratch`. Name it and associate it with a workspace. 30 | 3. Click `OAuth and Permissions` under `Features`. 31 | 4. Under `Scopes` > `User Token Scopes`, add the `team:read` permission. 32 | 5. Scroll up. Under `OAuth Tokens for Your Workspace`, click `Install to Workspace`. 33 | 6. Copy your `User OAuth Token`. 34 | -------------------------------------------------------------------------------- /whoamislack.py: -------------------------------------------------------------------------------- 1 | import argparse, os, re, requests 2 | 3 | def validate_args(args: argparse.Namespace) -> argparse.Namespace: 4 | # Use --token if provided, otherwise use env var, otherwise exit 5 | args.token = args.token or os.environ.get('SLACK_OAUTH_TOKEN') 6 | if not args.token: 7 | print("Slack OAuth Token with team:read scope is required. You can pass this in as an env var named SLACK_OAUTH_TOKEN or via --token on the command line. See the README on how to generate this token.") 8 | exit(1) 9 | 10 | # Validate webhook URL format 11 | pattern = r"https://hooks\.slack\.com/services/(T[a-zA-Z0-9]+)/[a-zA-Z0-9]+(/[a-zA-Z0-9]+)?" 12 | match = re.match(pattern, args.webhook) 13 | if not match: 14 | print("Invalid Slack webhook URL format.") 15 | exit(1) 16 | 17 | # Add match to args for use in get_slack_team_info 18 | args.webhook_match = match 19 | 20 | return args 21 | 22 | def get_slack_team_info(webhook_match: re.Match, token: str) -> dict: 23 | # Extract Team ID from webhook URL 24 | team_id = webhook_match.group(1) 25 | 26 | # Send POST request to Slack API team.info endpoint 27 | api_url = f"https://slack.com/api/team.info?team={team_id}&pretty=1" 28 | headers = { 29 | 'authority': 'slack.com', 30 | 'accept': '*/*', 31 | 'accept-language': 'en-US,en;q=0.9', 32 | 'content-type': 'multipart/form-data; boundary=----WebKitFormBoundary2EjSfkUlbGELqAyL', 33 | 'origin': 'https://api.slack.com', 34 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36', 35 | } 36 | data = f'''------WebKitFormBoundary2EjSfkUlbGELqAyL\r\nContent-Disposition: form-data; name="content"\r\n\r\nnull\r\n------WebKitFormBoundary2EjSfkUlbGELqAyL\r\nContent-Disposition: form-data; name="token"\r\n\r\n{token}\r\n------WebKitFormBoundary2EjSfkUlbGELqAyL--\r\n''' 37 | r = requests.post(api_url, headers=headers, data=data) 38 | 39 | # Check response and return JSON when successful 40 | if r.status_code == 200: 41 | if r.json().get('ok'): 42 | return r.json() 43 | 44 | # On error, print error message and exit 45 | try: 46 | print(f"Failed to retrieve Slack Workspace info. Error Message: {r.json().get('error')}") 47 | except: 48 | print(f"Failed to retrieve Slack Workspace info.") 49 | exit(1) 50 | 51 | def print_team_info(team_info: dict): 52 | #Parse JSON response 53 | id = team_info.get('team').get('id') 54 | workspace_name = team_info.get('team').get('name') 55 | domain = team_info.get('team').get('domain') 56 | 57 | # Print team id, workspace name, and domain (if different from workspace name) 58 | print(f"Team ID: {id}") 59 | print(f"Workspace Name: {workspace_name}") 60 | if workspace_name != domain: 61 | print(f"Domain: {domain}") 62 | 63 | if __name__ == "__main__": 64 | # Parse args and validate 65 | parser = argparse.ArgumentParser(description="Retrieve Slack Workspace Names from a Slack Webhook URL.") 66 | parser.add_argument("--token", required=False, help="Slack User OAuth Token with team:read scope (note: a token from any Slack account works). You can pass this in as an env var named SLACK_OAUTH_TOKEN.") 67 | parser.add_argument("webhook", help="Webhook URL for Slack") 68 | args = parser.parse_args() 69 | args = validate_args(args) 70 | 71 | # Get Slack Team Info and print 72 | team_info = get_slack_team_info(args.webhook_match, args.token) 73 | print_team_info(team_info) 74 | 75 | 76 | --------------------------------------------------------------------------------