mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2025-12-07 09:42:46 +01:00
malwarebazaar dep upgrades + use auth
This commit is contained in:
@@ -34,6 +34,8 @@ sensoroni:
|
|||||||
api_version: community
|
api_version: community
|
||||||
localfile:
|
localfile:
|
||||||
file_path: []
|
file_path: []
|
||||||
|
malwarebazaar:
|
||||||
|
api_key:
|
||||||
otx:
|
otx:
|
||||||
base_url: https://otx.alienvault.com/api/v1/
|
base_url: https://otx.alienvault.com/api/v1/
|
||||||
api_key:
|
api_key:
|
||||||
|
|||||||
@@ -2,12 +2,21 @@ import requests
|
|||||||
import helpers
|
import helpers
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
|
||||||
# supports querying for hash, gimphash, tlsh, and telfhash
|
# supports querying for hash, gimphash, tlsh, and telfhash
|
||||||
# usage is as follows:
|
# usage is as follows:
|
||||||
# python3 malwarebazaar.py '{"artifactType":"x", "value":"y"}'
|
# python3 malwarebazaar.py '{"artifactType":"x", "value":"y"}'
|
||||||
|
|
||||||
|
|
||||||
|
def checkConfigRequirements(conf):
|
||||||
|
if not conf.get('api_key'):
|
||||||
|
sys.exit(126)
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def buildReq(observ_type, observ_value):
|
def buildReq(observ_type, observ_value):
|
||||||
# determine correct query type to send based off of observable type
|
# determine correct query type to send based off of observable type
|
||||||
unique_types = {'gimphash': 1, 'telfhash': 1, 'tlsh': 1}
|
unique_types = {'gimphash': 1, 'telfhash': 1, 'tlsh': 1}
|
||||||
@@ -18,10 +27,13 @@ def buildReq(observ_type, observ_value):
|
|||||||
return {'query': qtype, observ_type: observ_value}
|
return {'query': qtype, observ_type: observ_value}
|
||||||
|
|
||||||
|
|
||||||
def sendReq(meta, query):
|
def sendReq(conf, meta, query):
|
||||||
# send a post request with our compiled query to the API
|
# send a post request with our compiled query to the API
|
||||||
url = meta['baseUrl']
|
url = meta['baseUrl']
|
||||||
response = requests.post(url, query)
|
headers = {}
|
||||||
|
if conf.get('api_key'):
|
||||||
|
headers['Auth-Key'] = conf['api_key']
|
||||||
|
response = requests.post(url, query, headers=headers)
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
@@ -113,10 +125,11 @@ def prepareResults(raw):
|
|||||||
return {'response': raw, 'summary': summary, 'status': status}
|
return {'response': raw, 'summary': summary, 'status': status}
|
||||||
|
|
||||||
|
|
||||||
def analyze(input):
|
def analyze(conf, input):
|
||||||
# put all of our methods together, pass them input, and return
|
# put all of our methods together, pass them input, and return
|
||||||
# properly formatted json/python dict output
|
# properly formatted json/python dict output
|
||||||
data = json.loads(input)
|
checkConfigRequirements(conf)
|
||||||
|
data = helpers.parseArtifact(input)
|
||||||
meta = helpers.loadMetadata(__file__)
|
meta = helpers.loadMetadata(__file__)
|
||||||
helpers.checkSupportedType(meta, data["artifactType"])
|
helpers.checkSupportedType(meta, data["artifactType"])
|
||||||
|
|
||||||
@@ -127,7 +140,7 @@ def analyze(input):
|
|||||||
# twice for the sake of retrieving more specific data.
|
# twice for the sake of retrieving more specific data.
|
||||||
|
|
||||||
initialQuery = buildReq(data['artifactType'], data['value'])
|
initialQuery = buildReq(data['artifactType'], data['value'])
|
||||||
initialRaw = sendReq(meta, initialQuery)
|
initialRaw = sendReq(conf, meta, initialQuery)
|
||||||
|
|
||||||
# To prevent double-querying when a tlsh/gimphash is invalid,
|
# To prevent double-querying when a tlsh/gimphash is invalid,
|
||||||
# this if statement is necessary.
|
# this if statement is necessary.
|
||||||
@@ -140,16 +153,22 @@ def analyze(input):
|
|||||||
return prepareResults(initialRaw)
|
return prepareResults(initialRaw)
|
||||||
|
|
||||||
query = buildReq(data['artifactType'], data['value'])
|
query = buildReq(data['artifactType'], data['value'])
|
||||||
response = sendReq(meta, query)
|
response = sendReq(conf, meta, query)
|
||||||
return prepareResults(response)
|
return prepareResults(response)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) == 2:
|
dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
results = analyze(sys.argv[1])
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Search MalwareBazaar for a given artifact')
|
||||||
|
parser.add_argument(
|
||||||
|
'artifact', help='the artifact represented in JSON format')
|
||||||
|
parser.add_argument('-c', '--config', metavar='CONFIG_FILE', default=dir + '/malwarebazaar.yaml',
|
||||||
|
help='optional config file to use instead of the default config file')
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.artifact:
|
||||||
|
results = analyze(helpers.loadConfig(args.config), args.artifact)
|
||||||
print(json.dumps(results))
|
print(json.dumps(results))
|
||||||
else:
|
|
||||||
print("ERROR: Input is not in proper JSON format")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
api: "{{ salt['pillar.get']('sensoroni:analyzers:malwarebazaar:api_key', '') }}"
|
||||||
@@ -6,22 +6,18 @@ import unittest
|
|||||||
|
|
||||||
|
|
||||||
class TestMalwarebazaarMethods(unittest.TestCase):
|
class TestMalwarebazaarMethods(unittest.TestCase):
|
||||||
def test_main_missing_input(self):
|
|
||||||
with patch('sys.stdout', new=StringIO()) as mock_cmd:
|
|
||||||
sys.argv = ["cmd"]
|
|
||||||
malwarebazaar.main()
|
|
||||||
self.assertEqual(mock_cmd.getvalue(),
|
|
||||||
'ERROR: Input is not in proper JSON format\n')
|
|
||||||
|
|
||||||
def test_main_success(self):
|
def test_main_success(self):
|
||||||
with patch('sys.stdout', new=StringIO()) as mock_cmd:
|
output = {"test": "val"}
|
||||||
with patch('malwarebazaar.malwarebazaar.analyze',
|
config = {"api_key": "test_key"}
|
||||||
new=MagicMock(return_value={'test': 'val'})) as mock:
|
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
||||||
|
with patch('malwarebazaar.malwarebazaar.analyze', new=MagicMock(return_value=output)) as mock_analyze:
|
||||||
|
with patch('helpers.loadConfig', new=MagicMock(return_value=config)) as mock_config:
|
||||||
sys.argv = ["cmd", "input"]
|
sys.argv = ["cmd", "input"]
|
||||||
malwarebazaar.main()
|
malwarebazaar.main()
|
||||||
expected = '{"test": "val"}\n'
|
expected = '{"test": "val"}\n'
|
||||||
self.assertEqual(mock_cmd.getvalue(), expected)
|
self.assertEqual(mock_stdout.getvalue(), expected)
|
||||||
mock.assert_called_once()
|
mock_analyze.assert_called_once()
|
||||||
|
mock_config.assert_called_once()
|
||||||
|
|
||||||
def test_isInJson_tail_greater_than_max_depth(self):
|
def test_isInJson_tail_greater_than_max_depth(self):
|
||||||
max_depth = 1000
|
max_depth = 1000
|
||||||
@@ -84,6 +80,7 @@ class TestMalwarebazaarMethods(unittest.TestCase):
|
|||||||
and then we compared results['summary'] with 'no result' """
|
and then we compared results['summary'] with 'no result' """
|
||||||
sendReqOutput = {'threat': 'no_result', "query_status": "ok",
|
sendReqOutput = {'threat': 'no_result', "query_status": "ok",
|
||||||
'data': [{'sha256_hash': 'notavalidhash'}]}
|
'data': [{'sha256_hash': 'notavalidhash'}]}
|
||||||
|
config = {"api_key": "test_key"}
|
||||||
input = '{"artifactType": "hash", "value": "1234"}'
|
input = '{"artifactType": "hash", "value": "1234"}'
|
||||||
input2 = '{"artifactType": "tlsh", "value": "1234"}'
|
input2 = '{"artifactType": "tlsh", "value": "1234"}'
|
||||||
input3 = '{"artifactType": "gimphash", "value": "1234"}'
|
input3 = '{"artifactType": "gimphash", "value": "1234"}'
|
||||||
@@ -94,9 +91,9 @@ class TestMalwarebazaarMethods(unittest.TestCase):
|
|||||||
new=MagicMock(return_value=sendReqOutput)) as mock:
|
new=MagicMock(return_value=sendReqOutput)) as mock:
|
||||||
with patch('malwarebazaar.malwarebazaar.prepareResults',
|
with patch('malwarebazaar.malwarebazaar.prepareResults',
|
||||||
new=MagicMock(return_value=prep_res_sim)) as mock2:
|
new=MagicMock(return_value=prep_res_sim)) as mock2:
|
||||||
results = malwarebazaar.analyze(input)
|
results = malwarebazaar.analyze(config, input)
|
||||||
results2 = malwarebazaar.analyze(input2)
|
results2 = malwarebazaar.analyze(config, input2)
|
||||||
results3 = malwarebazaar.analyze(input3)
|
results3 = malwarebazaar.analyze(config, input3)
|
||||||
self.assertEqual(results["summary"], prep_res_sim['summary'])
|
self.assertEqual(results["summary"], prep_res_sim['summary'])
|
||||||
self.assertEqual(results2["summary"], prep_res_sim['summary'])
|
self.assertEqual(results2["summary"], prep_res_sim['summary'])
|
||||||
self.assertEqual(results3["summary"], prep_res_sim['summary'])
|
self.assertEqual(results3["summary"], prep_res_sim['summary'])
|
||||||
@@ -113,6 +110,7 @@ class TestMalwarebazaarMethods(unittest.TestCase):
|
|||||||
and then we compared results['summary'] with 'no result' """
|
and then we compared results['summary'] with 'no result' """
|
||||||
sendReqOutput = {'threat': 'threat', "query_status": "notok", 'data': [
|
sendReqOutput = {'threat': 'threat', "query_status": "notok", 'data': [
|
||||||
{'sha256_hash': 'validhash'}]}
|
{'sha256_hash': 'validhash'}]}
|
||||||
|
config = {"api_key": "test_key"}
|
||||||
input = '{"artifactType": "hash", "value": "1234"}'
|
input = '{"artifactType": "hash", "value": "1234"}'
|
||||||
input2 = '{"artifactType": "tlsh", "value": "1234"}'
|
input2 = '{"artifactType": "tlsh", "value": "1234"}'
|
||||||
input3 = '{"artifactType": "gimphash", "value": "1234"}'
|
input3 = '{"artifactType": "gimphash", "value": "1234"}'
|
||||||
@@ -123,9 +121,9 @@ class TestMalwarebazaarMethods(unittest.TestCase):
|
|||||||
new=MagicMock(return_value=sendReqOutput)) as mock:
|
new=MagicMock(return_value=sendReqOutput)) as mock:
|
||||||
with patch('malwarebazaar.malwarebazaar.prepareResults',
|
with patch('malwarebazaar.malwarebazaar.prepareResults',
|
||||||
new=MagicMock(return_value=prep_res_sim)) as mock2:
|
new=MagicMock(return_value=prep_res_sim)) as mock2:
|
||||||
results = malwarebazaar.analyze(input)
|
results = malwarebazaar.analyze(config, input)
|
||||||
results2 = malwarebazaar.analyze(input2)
|
results2 = malwarebazaar.analyze(config, input2)
|
||||||
results3 = malwarebazaar.analyze(input3)
|
results3 = malwarebazaar.analyze(config, input3)
|
||||||
self.assertEqual(results["summary"], prep_res_sim['summary'])
|
self.assertEqual(results["summary"], prep_res_sim['summary'])
|
||||||
self.assertEqual(results2["summary"], prep_res_sim['summary'])
|
self.assertEqual(results2["summary"], prep_res_sim['summary'])
|
||||||
self.assertEqual(results3["summary"], prep_res_sim['summary'])
|
self.assertEqual(results3["summary"], prep_res_sim['summary'])
|
||||||
@@ -239,7 +237,18 @@ class TestMalwarebazaarMethods(unittest.TestCase):
|
|||||||
def test_sendReq(self):
|
def test_sendReq(self):
|
||||||
with patch('requests.post',
|
with patch('requests.post',
|
||||||
new=MagicMock(return_value=MagicMock())) as mock:
|
new=MagicMock(return_value=MagicMock())) as mock:
|
||||||
|
conf = {"api_key": "test_key"}
|
||||||
response = malwarebazaar.sendReq(
|
response = malwarebazaar.sendReq(
|
||||||
{'baseUrl': 'https://www.randurl.xyz'}, 'example_data')
|
conf, {'baseUrl': 'https://www.randurl.xyz'}, 'example_data')
|
||||||
self.assertIsNotNone(response)
|
self.assertIsNotNone(response)
|
||||||
mock.assert_called_once()
|
mock.assert_called_once()
|
||||||
|
|
||||||
|
def test_checkConfigRequirements_valid(self):
|
||||||
|
config = {"api_key": "test_key"}
|
||||||
|
self.assertTrue(malwarebazaar.checkConfigRequirements(config))
|
||||||
|
|
||||||
|
def test_checkConfigRequirements_missing_key(self):
|
||||||
|
config = {}
|
||||||
|
with self.assertRaises(SystemExit) as cm:
|
||||||
|
malwarebazaar.checkConfigRequirements(config)
|
||||||
|
self.assertEqual(cm.exception.code, 126)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -174,6 +174,14 @@ sensoroni:
|
|||||||
sensitive: False
|
sensitive: False
|
||||||
advanced: True
|
advanced: True
|
||||||
forcedType: "[]string"
|
forcedType: "[]string"
|
||||||
|
malwarebazaar:
|
||||||
|
api_key:
|
||||||
|
description: API key for the malwarebazaar analyzer.
|
||||||
|
helpLink: sensoroni.html
|
||||||
|
global: False
|
||||||
|
sensitive: True
|
||||||
|
advanced: False
|
||||||
|
forcedType: string
|
||||||
otx:
|
otx:
|
||||||
api_key:
|
api_key:
|
||||||
description: API key for the OTX analyzer.
|
description: API key for the OTX analyzer.
|
||||||
|
|||||||
Reference in New Issue
Block a user