Skip to content

Commit

Permalink
Merge pull request TheHive-Project#811 from TheHive-Project/feature/s…
Browse files Browse the repository at this point in the history
…pamassassin

Feature/spamassassin
  • Loading branch information
dadokkio authored Aug 10, 2020
2 parents 00de577 + cc0dc41 commit a7a25b5
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 0 deletions.
64 changes: 64 additions & 0 deletions analyzers/SpamAssassin/SpamAssassin.json
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.
1 change: 1 addition & 0 deletions analyzers/SpamAssassin/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cortexutils
120 changes: 120 additions & 0 deletions analyzers/SpamAssassin/spamassassin.py
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()
36 changes: 36 additions & 0 deletions thehive-templates/SpamAssassin_1_0/long.html
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>
3 changes: 3 additions & 0 deletions thehive-templates/SpamAssassin_1_0/short.html
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>

0 comments on commit a7a25b5

Please sign in to comment.