forked from TheHive-Project/Cortex-Analyzers
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request TheHive-Project#811 from TheHive-Project/feature/s…
…pamassassin Feature/spamassassin
- Loading branch information
Showing
8 changed files
with
224 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
{ | ||
"name": "SpamAssassin", | ||
"author": "Davide Arcuri - LDO-CERT", | ||
"license": "AGPL-V3", | ||
"url": "https://github.com/TheHive-Project/Cortex-Analyzers", | ||
"version": "1.0", | ||
"description": "Get spam score from local SpamAssassin instance", | ||
"dataTypeList": [ | ||
"file" | ||
], | ||
"baseConfig": "SpamAssassin", | ||
"command": "SpamAssassin/spamassassin.py", | ||
"configurationItems": [ | ||
{ | ||
"name": "url", | ||
"description": "SpamAssassin url", | ||
"multi": false, | ||
"required": true, | ||
"type": "string" | ||
}, | ||
{ | ||
"name": "port", | ||
"description": "SpamAssassin port", | ||
"type": "number", | ||
"defaultValue": 783, | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "spam_score", | ||
"description": "Minimum score to consider mail as spam", | ||
"type": "number", | ||
"multi": false, | ||
"required": false, | ||
"defaultValue": 5 | ||
}, | ||
{ | ||
"name": "timeout", | ||
"description": "Timout for socket operations in seconds", | ||
"type": "number", | ||
"multi": false, | ||
"required": false, | ||
"defaultValue": 20 | ||
} | ||
], | ||
"registration_required": false, | ||
"subscription_required": false, | ||
"free_subscription": false, | ||
"service_homepage": "https://spamassassin.apache.org/", | ||
"service_logo": { | ||
"path": "assets/SpamAssassin_logo.png", | ||
"caption": "logo" | ||
}, | ||
"screenshots": [ | ||
{ | ||
"path": "assets/SpamAssassin_long.png", | ||
"caption": "SpamAssassin long report sample" | ||
}, | ||
{ | ||
"path": "assets/SpamAssassin_short.png", | ||
"caption:": "SpamAssassin mini report sample" | ||
} | ||
] | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
cortexutils |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
#!/usr/bin/env python3 | ||
# encoding: utf-8 | ||
|
||
import socket | ||
import select | ||
import re | ||
from io import BytesIO | ||
from cortexutils.analyzer import Analyzer | ||
|
||
|
||
divider_pattern = re.compile(br'^(.*?)\r?\n(.*?)\r?\n\r?\n', re.DOTALL) | ||
first_line_pattern = re.compile(br'^SPAMD/[^ ]+ 0 EX_OK$') | ||
|
||
|
||
class SpamAssassinAnalyzer(Analyzer): | ||
def __init__(self): | ||
Analyzer.__init__(self) | ||
url = self.get_param("config.url", None) | ||
port = self.get_param("config.port", None) | ||
self.spam_score = self.get_param("config.spam_score", 5) | ||
self.timeout = self.get_param("config.timeout", 20) | ||
if url and port: | ||
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
self.client.settimeout(self.timeout) | ||
self.client.connect((url, port)) | ||
|
||
|
||
def _build_message(self, message): | ||
reqfp = BytesIO() | ||
data_len = str(len(message)).encode() | ||
reqfp.write(b'REPORT SPAMC/1.2\r\n') | ||
reqfp.write(b'Content-Length: ' + data_len + b'\r\n') | ||
reqfp.write(b'User: cx42\r\n\r\n') | ||
reqfp.write(message) | ||
return reqfp.getvalue() | ||
|
||
|
||
def _parse_response(self, response): | ||
if response == b'': | ||
return None | ||
|
||
match = divider_pattern.match(response) | ||
if not match: | ||
return None | ||
|
||
first_line = match.group(1) | ||
headers = match.group(2) | ||
body = response[match.end(0):] | ||
|
||
match = first_line_pattern.match(first_line) | ||
if not match: | ||
return None | ||
|
||
report_list = [s.strip() for s in body.decode('utf-8', errors="ignore").strip().split('\n')] | ||
linebreak_num = report_list.index([s for s in report_list if "---" in s][0]) | ||
tablelists = [s for s in report_list[linebreak_num + 1:]] | ||
|
||
tablelists_temp = [] | ||
if tablelists: | ||
for counter, tablelist in enumerate(tablelists): | ||
if len(tablelist)>1: | ||
if (tablelist[0].isnumeric() or tablelist[0] == '-') and (tablelist[1].isnumeric() or tablelist[1] == '.'): | ||
tablelists_temp.append(tablelist) | ||
else: | ||
if tablelists_temp: | ||
tablelists_temp[-1] += " " + tablelist | ||
tablelists = tablelists_temp | ||
|
||
report_json = {"values": []} | ||
for tablelist in tablelists: | ||
wordlist = re.split('\s+', tablelist) | ||
report_json['values'].append({'partscore': float(wordlist[0]), 'description': ' '.join(wordlist[1:]), 'name': wordlist[1]}) | ||
|
||
headers = headers.decode('utf-8').replace(' ', '').replace(':', ';').replace('/', ';').split(';') | ||
report_json['score'] = float(headers[2]) | ||
report_json['is_spam'] = float(headers[2]) > self.spam_score | ||
return report_json | ||
|
||
|
||
def summary(self, raw): | ||
taxonomies = [] | ||
level = "suspicious" if raw.get('is_spam', None) else "info" | ||
taxonomies.append(self.build_taxonomy(level, "Spamassassin", "score", raw.get('score', 0))) | ||
return {"taxonomies": taxonomies} | ||
|
||
|
||
def run(self): | ||
Analyzer.run(self) | ||
|
||
data = self.get_param("file", None, "File is missing") | ||
if self.data_type != "file": | ||
self.error("Invalid data type") | ||
|
||
with open(data, 'rb') as f: | ||
message = f.read() | ||
|
||
self.client.sendall(self._build_message(message)) | ||
self.client.shutdown(socket.SHUT_WR) | ||
|
||
resfp = BytesIO() | ||
while True: | ||
ready = select.select([self.client], [], [], self.timeout) | ||
if ready[0] is None: | ||
self.error("Timeout during socket operation") | ||
|
||
data = self.client.recv(4096) | ||
if data == b'': | ||
break | ||
|
||
resfp.write(data) | ||
|
||
self.client.close() | ||
self.client = None | ||
|
||
|
||
self.report(self._parse_response(resfp.getvalue())) | ||
|
||
|
||
if __name__ == "__main__": | ||
SpamAssassinAnalyzer().run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<div class="panel panel-info" ng-if="success"> | ||
|
||
<div class="panel-heading"> | ||
SpamAssassin score for <strong>{{(artifact.data || artifact.attachment.name) | fang}}</strong> | ||
</div> | ||
<div class="panel-body"> | ||
<h4>Final Score</h4> | ||
<dl class="dl-horizontal"> | ||
<dt>Score</dt> | ||
<dd>{{content.score}} </dd> | ||
</dl> | ||
<dl class="dl-horizontal"> | ||
<dt>Is Spam?</dt> | ||
<dd><span ng-if="content.is_spam" class="label label-warning">true</span><span ng-if="!content.is_spam" class="label label-primary">false</span></dd> | ||
</dl> | ||
<hr> | ||
<h4>Score Detail</h4> | ||
<dl class="dl-horizontal" ng-if="content.values.length > 0" ng-repeat="value in content.values"> | ||
<dt>{{value.name}}</dt> | ||
<dd>{{value.description}} [+{{value.partscore}}]</dd> | ||
</dl> | ||
</div> | ||
</div> | ||
|
||
<!-- on error --> | ||
<div class="panel panel-danger" ng-if="!success"> | ||
<div class="panel-heading"> | ||
SpamAssassin score <b>Error</b> | ||
</div> | ||
<div class="panel-body"> | ||
<dl class="dl-horizontal"> | ||
<dt>Error: </dt> | ||
<dd>{{content.errorMessage}}</dd> | ||
</dl> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<span class="label" ng-repeat="t in content.taxonomies" ng-class="{'info': 'label-info', 'safe': 'label-success', 'suspicious': 'label-warning', 'malicious':'label-danger'}[t.level]"> | ||
{{t.namespace}}:{{t.predicate}}="{{t.value}}" | ||
</span> |