Compare commits

...

39 Commits

Author SHA1 Message Date
Alexandre Dulaunoy 28dcfb6f39
Merge pull request #82 from arcsector/master
Bringing up-to-date with recent PyMISP and Optional Deduplication
2022-06-14 09:44:27 +02:00
George Haraksin 2aa3522cae excepting Exception instead of BaseException 2022-06-14 00:02:15 -07:00
George Haraksin 43a27c83b1 even better logging 2022-06-14 00:01:09 -07:00
George Haraksin 8ec9fc18c5 Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-06-16 01:13:11 -07:00
haraksin 67d08c443f try/except on pymisp loading stix failing 2020-06-16 01:12:23 -07:00
George Haraksin f25304c815 Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-06-15 19:26:25 -07:00
haraksin e020452a3d invalid syntaxi in except clause 2020-06-15 19:26:07 -07:00
George Haraksin aa7e345e68 Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-06-15 19:24:39 -07:00
haraksin 0aaed66540 Checking for MISP existence 2020-06-15 19:24:18 -07:00
George Haraksin 4fd353fbec Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-06-15 19:01:31 -07:00
haraksin 67f9c52def adding try/except for pymisp error 2020-06-15 19:00:47 -07:00
George Haraksin 27e667b2f0 Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-06-15 18:53:46 -07:00
George Haraksin 03aa19467e fixing merge conflicts 2020-06-15 18:49:45 -07:00
haraksin e4ab93e954 fixing logging changes 2020-06-15 18:12:24 -07:00
haraksin 6056501ff4 Adding checking if connection to MISP is valid 2020-06-15 18:08:33 -07:00
George Haraksin a850a0051f Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-05-14 16:53:20 -07:00
haraksin 461452f420 Adding log about MISP publish skip 2020-05-14 16:43:01 -07:00
George Haraksin 59fb6f27e8 Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-05-14 16:38:47 -07:00
haraksin 5cc5f99c45 Making sure collections returned from manager instance are only the ones we want 2020-05-14 16:38:28 -07:00
George Haraksin 4db2a43e6d Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-05-14 16:33:43 -07:00
haraksin 4d9f63522b Using UUID instead of ID for publish event log 2020-05-14 16:33:15 -07:00
George Haraksin 0303688af3 Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-05-14 16:27:12 -07:00
haraksin 310d8145d5 Making sure config can't be switched to unknown if it's false 2020-05-14 16:26:51 -07:00
George Haraksin 0326daf2b1 Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-05-14 16:19:16 -07:00
haraksin 9b8ed9ddf9 Adding better logging 2020-05-14 16:18:53 -07:00
George Haraksin ad1834acb2 Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-05-14 16:15:43 -07:00
haraksin 4a41e963df getting collection names for name lookup 2020-05-14 16:15:23 -07:00
George Haraksin 1a083c7378 Merge branch 'master' of https://github.com/arcsector/MISP-Taxii-Server 2020-05-14 15:55:50 -07:00
haraksin 934bcf1b6a Adding collections and publish args and features. 2020-05-13 19:21:52 -07:00
George Haraksin c28ab6f3af fixing merge conflicts 2020-05-13 15:30:46 -07:00
haraksin 00e70113ad Add src/ to gitignore 2020-05-13 15:29:20 -07:00
George Haraksin f3c583f0f4 adding src/ to gitignure 2020-05-13 15:28:11 -07:00
haraksin 8a20458898 removing gitignore changes 2020-05-13 13:59:56 -07:00
haraksin 823822bbf1 Making dedup optional 2020-05-13 13:49:34 -07:00
haraksin c55f9994c6 Adding deduplication as an optional item 2020-05-13 13:41:12 -07:00
haraksin 277dfcdb97 Dealing with key issue in search
Line 70
2020-05-06 21:05:36 -07:00
haraksin b50a60485f adding Loader to push_published_to_taxii
adding local-server default yaml and remote-servers default yaml
2020-05-06 17:28:04 -07:00
arcsector 53309e3477
Include Loader in hooks.py 2020-05-06 17:00:04 -07:00
arcsector c86e4940af
Including pyaml's Loader to suppress warnings
See these docs for info: https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation
2020-05-06 16:58:21 -07:00
7 changed files with 177 additions and 36 deletions

2
.gitignore vendored
View File

@ -6,3 +6,5 @@ config.yaml
__pycache__ __pycache__
build build
dist dist
src
vscode/

View File

@ -29,6 +29,11 @@ zmq:
misp: misp:
url: "http://localhost" url: "http://localhost"
api: KEY api: KEY
dedup: true
collections:
- my_collection
- my_collection2
publish: false
taxii: taxii:
auth: auth:

View File

@ -0,0 +1,12 @@
host: localhost
port: 9000
discovery_path: /services/discovery
inbox_path: /services/inbox
use_https: False
taxii_version: '1.1'
headers:
auth:
username: test
password: test
collections:
- collection

View File

@ -0,0 +1,18 @@
- name: 'default'
host: localhost
port: 9000
discovery_path:
use_https: False
taxii_version: '1.1'
headers:
auth:
username:
password:
cacert_path:
cert_file:
key_file:
key_password:
jwt_auth_url:
verify_ssl: True
collections:
- collection

View File

@ -9,43 +9,84 @@ import pymisp
import tempfile import tempfile
import logging import logging
from pyaml import yaml from pyaml import yaml
from yaml import Loader
from io import StringIO from io import StringIO
from requests.exceptions import ConnectionError
from pymisp.exceptions import PyMISPError
import warnings
warnings.filterwarnings("ignore")
log = logging.getLogger("__main__") TOTAL_ATTRIBUTES_SENT = 0
TOTAL_ATTRIBUTES_ANALYZED = 0
logging_level = logging.INFO
log = logging.getLogger("misp_taxii_server")
log.setLevel(logging_level)
handler = logging.StreamHandler()
handler.setLevel(logging_level)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
if log.handlers:
log.handlers = []
log.addHandler(handler)
from opentaxii.signals import ( from opentaxii.signals import (
CONTENT_BLOCK_CREATED, INBOX_MESSAGE_CREATED CONTENT_BLOCK_CREATED, INBOX_MESSAGE_CREATED
) )
def env_config_helper(env_name):
if env_name in os.environ:
if env_name == "MISP_COLLECTIONS":
name = os.environ[env_name]
return name.split(',')
return os.environ[env_name]
else:
log.error("Missing env setting {0}. Set OPENTAXII_CONFIG or {0}.".format(env_name))
return "UNKNOWN"
def yaml_config_helper(config_name, CONFIG):
if config_name in CONFIG["misp"]:
if not CONFIG["misp"][config_name] and CONFIG["misp"][config_name] != False:
CONFIG["misp"][config_name] = "UNKNOWN"
else:
CONFIG["misp"][config_name] = "UNKNOWN"
return CONFIG
## CONFIG ## CONFIG
if "OPENTAXII_CONFIG" in os.environ: if "OPENTAXII_CONFIG" in os.environ:
print("Using config from {}".format(os.environ["OPENTAXII_CONFIG"])) log.debug("Using config from {}".format(os.environ["OPENTAXII_CONFIG"]))
CONFIG = yaml.load(open(os.environ["OPENTAXII_CONFIG"], "r")) CONFIG = yaml.load(open(os.environ["OPENTAXII_CONFIG"], "r"), Loader=Loader)
# validate dedup and collections and publish
CONFIG = yaml_config_helper("dedup", CONFIG)
CONFIG = yaml_config_helper("collections", CONFIG)
CONFIG = yaml_config_helper("publish", CONFIG)
else: else:
print("Trying to use env variables...") log.debug("Trying to use env variables...")
if "MISP_URL" in os.environ: misp_url = env_config_helper("MISP_URL")
misp_url = os.environ["MISP_URL"] misp_api = env_config_helper("MISP_API")
else: misp_dedup = env_config_helper("MISP_DEDUP")
print("Unkown misp URL. Set OPENTAXII_CONFIG or MISP_URL.") misp_collections = env_config_helper("MISP_COLLECTIONS")
misp_url = "UNKNOWN" misp_publish = env_config_helper("MISP_PUBLISH")
if "MISP_API" in os.environ:
misp_api = os.environ["MISP_API"]
else:
print("Unknown misp API key. Set OPENTAXII_CONFIG or MISP_API.")
misp_api = "UNKNOWN"
CONFIG = { CONFIG = {
"misp" : { "misp" : {
"url" : misp_url, "url" : misp_url,
"api" : misp_api "api" : misp_api,
"dedup" : misp_dedup,
"collections": misp_collections
} }
} }
MISP = ''
MISP = pymisp.PyMISP( try:
MISP = pymisp.PyMISP(
CONFIG["misp"]["url"], CONFIG["misp"]["url"],
CONFIG["misp"]["api"], CONFIG["misp"]["api"],
ssl = CONFIG["misp"].get("verifySSL", True) ssl = CONFIG["misp"].get("verifySSL", True)
) )
except PyMISPError:
log.error("Cannot connect to MISP; please ensure that MISP is up and running at {}. Skipping MISP upload.".format(CONFIG['misp']['url']))
def post_stix(manager, content_block, collection_ids, service_id): def post_stix(manager, content_block, collection_ids, service_id):
''' '''
@ -53,34 +94,95 @@ def post_stix(manager, content_block, collection_ids, service_id):
Will convert it to a MISPEvent and push to the server Will convert it to a MISPEvent and push to the server
''' '''
# make sure collections, if specified are supposed to be sent to
if CONFIG["misp"]["collections"] != "UNKNOWN" or CONFIG["misp"]["collections"] == False:
log.debug("Using collections")
should_send_to_misp = False
collection_names = [collection.name for collection in manager.get_collections(service_id) if collection.id in collection_ids]
for collection in CONFIG["misp"]["collections"]:
if collection in collection_names or collection in collection_ids:
log.debug("Collection specified matches push collection: {}".format(collection))
should_send_to_misp = True
break
if should_send_to_misp == False:
log.debug('''No collections match misp.collections; aborting MISP extraction.''')
log.debug("Collection ids whitelisted: {}".format(CONFIG["misp"]["collections"]))
log.debug("Collection ids sent to: {}".format(collection_ids))
log.debug('''Collection names sent to: {}'''.format(collection_names))
return None
# Load the package # Load the package
log.info("Posting STIX...") log.debug("Posting STIX...")
block = content_block.content block = content_block.content
if isinstance(block, bytes): if isinstance(block, bytes):
block = block.decode() block = block.decode()
package = pymisp.tools.stix.load_stix(StringIO(block)) try:
log.info("STIX loaded succesfully.") package = pymisp.tools.stix.load_stix(StringIO(block))
except Exception:
log.error('Could not load stix into MISP format; exiting.')
return 0
log.debug("STIX loaded succesfully.")
values = [x.value for x in package.attributes] values = [x.value for x in package.attributes]
log.info("Extracted %s", values) log.debug("Extracted %s", values)
for attrib in values: TOTAL_ATTRIBUTES_ANALYZED = len(values)
log.info("Checking for existence of %s", attrib)
search = MISP.search("attributes", values=str(attrib)) # if deduping is enabled, start deduping
if search["response"]["Attribute"] != []: if (
# This means we have it! CONFIG["misp"]["dedup"] or
log.info("%s is a duplicate, we'll ignore it.", attrib) CONFIG["misp"]["dedup"] == "True" or
package.attributes.pop([x.value for x in package.attributes].index(attrib)) CONFIG["misp"]["dedup"] == "UNKNOWN"
else: ):
log.info("%s is unique, we'll keep it", attrib) for attrib in values:
log.debug("Checking for existence of %s", attrib)
search = ''
if MISP:
search = MISP.search("attributes", values=str(attrib))
else:
return 0
if 'response' in search:
if search["response"]["Attribute"] != []:
# This means we have it!
log.debug("%s is a duplicate, we'll ignore it.", attrib)
package.attributes.pop([x.value for x in package.attributes].index(attrib))
else:
log.debug("%s is unique, we'll keep it", attrib)
elif 'Attribute' in search:
if search["Attribute"] != []:
# This means we have it!
log.debug("%s is a duplicate, we'll ignore it.", attrib)
package.attributes.pop([x.value for x in package.attributes].index(attrib))
else:
log.debug("%s is unique, we'll keep it", attrib)
else:
log.error("Something went wrong with search, and it doesn't have an 'attribute' or a 'response' key: {}".format(search.keys()))
else:
log.debug("Skipping deduplication")
# Push the event to MISP # Push the event to MISP
# TODO: There's probably a proper method to do this rather than json_full # TODO: There's probably a proper method to do this rather than json_full
# But I don't wanna read docs # But I don't wanna read docs
if (len(package.attributes) > 0): if (len(package.attributes) > 0):
log.info("Uploading event to MISP with attributes %s", [x.value for x in package.attributes]) log.debug("Uploading event to MISP with attributes %s", [x.value for x in package.attributes])
MISP.add_event(package) event = ''
try:
if MISP:
event = MISP.add_event(package)
TOTAL_ATTRIBUTES_SENT = len(package.attributes)
except ConnectionError:
log.error("Cannot push to MISP; please ensure that MISP is up and running at {}. Skipping MISP upload.".format(CONFIG['misp']['url']))
if (
CONFIG["misp"]["publish"] == True or
CONFIG["misp"]["publish"] == "True"
):
log.info("Publishing event to MISP with ID {}".format(event.get('uuid')))
if MISP:
MISP.publish(event)
else:
log.debug("Skipping MISP event publishing")
else: else:
log.info("No attributes, not bothering.") log.info("No attributes, not bothering.")
log.debug("total_attributes_analyzed={}, total_attributes_sent={}".format(TOTAL_ATTRIBUTES_ANALYZED, TOTAL_ATTRIBUTES_SENT))
# Make TAXII call our push function whenever it gets new data # Make TAXII call our push function whenever it gets new data
CONTENT_BLOCK_CREATED.connect(post_stix) CONTENT_BLOCK_CREATED.connect(post_stix)

View File

@ -4,6 +4,7 @@ import sys
import json import json
import pymisp import pymisp
from pyaml import yaml from pyaml import yaml
from yaml import Loader
from cabby import create_client from cabby import create_client
from misp_stix_converter.converters import lint_roller from misp_stix_converter.converters import lint_roller
import logging import logging
@ -20,7 +21,7 @@ log.setLevel(logging.DEBUG)
log.info("Starting...") log.info("Starting...")
# Try to load in config # Try to load in config
if "OPENTAXII_CONFIG" in os.environ: if "OPENTAXII_CONFIG" in os.environ:
config = yaml.load(open(os.environ["OPENTAXII_CONFIG"], "r")) config = yaml.load(open(os.environ["OPENTAXII_CONFIG"], "r"), Loader=Loader)
else: else:
print("OPENTAXII CONFIG NOT EXPORTED") print("OPENTAXII CONFIG NOT EXPORTED")
sys.exit() sys.exit()

View File

@ -2,6 +2,7 @@
from cabby import create_client from cabby import create_client
from pyaml import yaml from pyaml import yaml
from yaml import Loader
import pytz import pytz
import argparse import argparse
import os import os
@ -51,14 +52,14 @@ config_file = "{}/remote-servers.yml".format(
log.debug("Opening config file %s", config_file) log.debug("Opening config file %s", config_file)
with open(config_file, "r") as f: with open(config_file, "r") as f:
config = yaml.load(f.read()) config = yaml.load(f.read(), Loader=Loader)
log.debug("Config read %s", config) log.debug("Config read %s", config)
# Read in the local server configuration # Read in the local server configuration
local_config = "{}/local-server.yml".format(os.path.expanduser(args.configdir)) local_config = "{}/local-server.yml".format(os.path.expanduser(args.configdir))
log.debug("Reading local server config") log.debug("Reading local server config")
with open(local_config, "r") as f: with open(local_config, "r") as f:
local_config = yaml.load(f.read()) local_config = yaml.load(f.read(), Loader=Loader)
# Attempt to make contact with the local server # Attempt to make contact with the local server
log.info("Connecting to local server...") log.info("Connecting to local server...")