Skip to content

Commit 8bc5cb0

Browse files
authored
Merge pull request #57 from PaperMtn/release/4.2.0
Release/4.2.0
2 parents 70a002f + b1df488 commit 8bc5cb0

8 files changed

Lines changed: 48 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## [4.2.0] - 2024-09-27
2+
### Added
3+
- Added enumeration of conversations with populated Canvases attached. These can contain sensitive information, and are worth reviewing.
4+
- Added join domain to unauthenticated probe. This is the link to use to sign into a Workspace if you have an email with one of the approved domains.
5+
16
## [4.1.2] - 2024-09-14
27
### Added
38
- Added enumeration of authentication options for the Workspace you authed to.

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ You can run Slack Watchman to look for results going back as far as:
4949
It also enumerates the following:
5050
- User data
5151
- All users & all admins
52-
- Channel data
53-
- All channels, including externally shared channels
52+
- Conversation data
53+
- All conversations, including externally shared conversations
54+
- All conversations that include a Slack Canvas (which often contain sensitive or important information)
5455
- Workspace authentication options
5556

5657

@@ -110,7 +111,7 @@ team:read
110111
users:read
111112
users:read.email
112113
```
113-
**Note**: User tokens act on behalf of the user who authorises them, so I would suggest you create this app and authorise it using a service account, otherwise the app will have access to your private channels and chats.
114+
**Note**: User tokens act on behalf of the user who authorises them, so I would suggest you create this app and authorise it using a service account, otherwise the app will have access to your private conversations and chats.
114115

115116
### Cookie Authentication
116117
Alternatively, Slack Watchman can also authenticate to Slack using a user `d` cookie, which is stored in the browser of each user logged into a workspace.

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "slack-watchman"
3-
version = "4.1.2"
3+
version = "4.2.0"
44
description = "Monitoring and enumerating Slack for exposed secrets"
55
authors = ["PaperMtn <papermtn@protonmail.com>"]
66
license = "GPL-3.0"

src/slack_watchman/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,15 @@ def main():
321321
OUTPUT_LOGGER.log(
322322
'SUCCESS',
323323
f'Users output to CSV file: {os.path.join(os.getcwd(), "slack_channels.csv")}')
324-
324+
OUTPUT_LOGGER.log('INFO', 'Finding public Canvases')
325+
for channel in channel_list:
326+
if not channel.canvas_empty and channel.canvas_id:
327+
canvas_information = {
328+
'channel_name': channel.name,
329+
'canvas_url': f'{workspace_information.url}canvas/{channel.id}'
330+
}
331+
OUTPUT_LOGGER.log('CANVAS', canvas_information, detect_type='Canvas',
332+
notify_type='canvas')
325333
if everything or not pii and not secrets:
326334
OUTPUT_LOGGER.log('INFO', 'Searching for PII and Secrets')
327335
for signature in signature_list:

src/slack_watchman/models/conversation.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import time
22
from dataclasses import dataclass
3-
from typing import List, Dict
3+
from typing import List, Dict, Optional
44

55

66
def _convert_timestamp(timestamp: str or int) -> str or None:
@@ -44,6 +44,8 @@ class Conversation(object):
4444
previous_names: List[str]
4545
purpose: str
4646
topic: str
47+
canvas_empty: Optional[bool]
48+
canvas_id: Optional[str]
4749
num_members: int
4850
is_member: bool
4951
is_pending_ext_shared: bool
@@ -71,6 +73,8 @@ class ConversationSuccinct(object):
7173
is_im: bool
7274
is_mpim: bool
7375
is_archived: bool
76+
canvas_empty: Optional[bool]
77+
canvas_id: Optional[str]
7478
creator: str
7579
num_members: int
7680

@@ -107,6 +111,8 @@ def create_from_dict(conv_dict: Dict, verbose: bool) -> Conversation or Conversa
107111
previous_names=conv_dict.get('previous_names'),
108112
is_member=conv_dict.get('is_member'),
109113
purpose=conv_dict.get('purpose', {}).get('value'),
114+
canvas_empty=conv_dict.get('properties', {}).get('canvas', {}).get('is_empty'),
115+
canvas_id=conv_dict.get('properties', {}).get('canvas', {}).get('file_id'),
110116
topic=conv_dict.get('topic', {}).get('value')
111117
)
112118
else:
@@ -119,5 +125,7 @@ def create_from_dict(conv_dict: Dict, verbose: bool) -> Conversation or Conversa
119125
is_im=conv_dict.get('is_im'),
120126
is_mpim=conv_dict.get('is_mpim'),
121127
is_archived=conv_dict.get('is_archived'),
128+
canvas_empty=conv_dict.get('properties', {}).get('canvas', {}).get('is_empty'),
129+
canvas_id=conv_dict.get('properties', {}).get('canvas', {}).get('file_id'),
122130
creator=conv_dict.get('creator'),
123131
)

src/slack_watchman/slack_wrapper.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,7 @@ def find_auth_information(domain_url: str) -> Dict[str, List[str]] | None:
583583

584584
output = {
585585
'formatted_email_domains': props_data.get('formattedEmailDomains', None),
586+
'join_url': f'https://join.slack.com/t/{props_data.get("teamDomain")}/signup',
586587
'user_oauth': [
587588
'google' if props_data.get('userOauth', {}).get('google', {}).get('enabled', False) else None,
588589
'apple' if props_data.get('userOauth', {}).get('apple', {}).get('enabled', False) else None
@@ -594,7 +595,8 @@ def find_auth_information(domain_url: str) -> Dict[str, List[str]] | None:
594595
'sso_enabled': props_data.get('isSSOAuthMode', None),
595596
'two_factor_required': props_data.get('twoFactorRequired', None)
596597
}
597-
if output.get('formatted_email_domains') == "":
598-
output['formatted_email_domains'] = "N/A"
598+
if output.get('formatted_email_domains') == '':
599+
output['formatted_email_domains'] = 'N/A'
600+
output['join_url'] = 'N/A'
599601

600602
return output

src/slack_watchman/sw_logger.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def log(self,
5353
f' TEAM_ID: {message.get("team_id")} \n' \
5454
f' PAID_TEAM: {message.get("paid_team")} \n' \
5555
f' APPROVED_DOMAINS: {message.get("formatted_email_domains")} \n' \
56+
f' JOIN_URL: {message.get("join_url")} \n' \
5657
f' OAUTH_PROVIDERS: {message.get("user_oauth")} \n' \
5758
f' STANDARD_AUTH: {message.get("standard_auth_enabled")} \n' \
5859
f' SSO_ENABLED: {message.get("sso_enabled")} \n' \
@@ -68,6 +69,11 @@ def log(self,
6869
f' OWNER: {message.get("is_owner")} \n' \
6970
f' HAS_2FA: {message.get("has_2fa")}'
7071
mes_type = 'USER'
72+
if notify_type == "canvas":
73+
message = f'CANVAS: \n' \
74+
f' CHANNEL: {message.get("channel_name")} \n' \
75+
f' CANVAS_URL: {message.get("canvas_url")}'
76+
mes_type = 'USER'
7177
if notify_type == "result":
7278
if message.get('message'):
7379
if message.get('message').get('conversation').get('is_im'):
@@ -257,6 +263,8 @@ def __init__(self, name: str = 'Slack Watchman', **kwargs):
257263
'{"timestamp": "%(asctime)s", "level": "WORKSPACE_AUTH", "message": %(message)s}')
258264
self.workspace_probe_format = logging.Formatter(
259265
'{"timestamp": "%(asctime)s", "level": "WORKSPACE_PROBE", "message": %(message)s}')
266+
self.canvas_format = logging.Formatter(
267+
'{"timestamp": "%(asctime)s", "level": "CANVAS", "message": %(message)s}')
260268
self.logger = logging.getLogger(self.name)
261269
self.handler = logging.StreamHandler(sys.stdout)
262270
self.logger.addHandler(self.handler)
@@ -293,6 +301,11 @@ def log(self,
293301
self.logger.info(json.dumps(
294302
log_data,
295303
cls=EnhancedJSONEncoder))
304+
elif level.upper() == 'CANVAS':
305+
self.handler.setFormatter(self.canvas_format)
306+
self.logger.info(json.dumps(
307+
log_data,
308+
cls=EnhancedJSONEncoder))
296309
elif level.upper() == 'WORKSPACE':
297310
self.handler.setFormatter(self.workspace_format)
298311
self.logger.info(json.dumps(

0 commit comments

Comments
 (0)