From b27425f30685b58ba8ac02c7268a144881323da5 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 6 Mar 2018 11:03:34 +0100 Subject: [PATCH 001/153] GoAML modules documentation first try --- doc/export_mod/goamlexport.json | 8 ++++++++ doc/import_doc/goamlimport.json | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 doc/export_mod/goamlexport.json create mode 100644 doc/import_doc/goamlimport.json diff --git a/doc/export_mod/goamlexport.json b/doc/export_mod/goamlexport.json new file mode 100644 index 0000000..3a00724 --- /dev/null +++ b/doc/export_mod/goamlexport.json @@ -0,0 +1,8 @@ +{ + "description": "This module is used to export MISP events containing transaction objects into GoAML format.", + "requirements": ["PyMISP","MISP objects"], + "features": "The module works as long as there is at least one transaction object in the Event.\n\nThen in order to have a valid GoAML document, please follow these guidelines:\n- For each transaction object, use either a bank-account, person, or legal-entity object to describe the origin of the transaction, and again one of them to describe the target of the transaction.\n- Create an object reference for both origin and target objects of the transaction.\n- A bank-account object needs a signatory, which is a person object, put as object reference of the bank-account.\n- A person can have an address, which is a geolocation object, put as object reference of the person.\n\nSupported relation types for object references that are recommended for each object are the folowing:\n- transaction:\n\t- 'from', 'from_my_client': Origin of the transaction - at least one of them is required.\n\t- 'to', 'to_my_client': Target of the transaction - at least one of them is required.\n\t- 'address': Location of the transaction - optional.\n- bank-account:\n\t- 'signatory': Signatory of a bank-account - the reference from bank-account to a signatory is required, but the relation-type is optional at the moment since this reference will always describe a signatory.\n\t- 'entity': Entity owning the bank account - optional.\n- person:\n\t- 'address': Address of a person - optional.", + "references": ["http://goaml.unodc.org/"], + "input": "MISP objects (transaction, bank-account, person, legal-entity, geolocation), with references, describing financial transactions and their origin and target.", + "output": "GoAML format file, describing financial transactions, with their origin and target (bank accounts, persons or entities)." +} diff --git a/doc/import_doc/goamlimport.json b/doc/import_doc/goamlimport.json new file mode 100644 index 0000000..414a967 --- /dev/null +++ b/doc/import_doc/goamlimport.json @@ -0,0 +1,8 @@ +{ + "description": "Module to import MISP objects about financial transactions from GoAML files.", + "requirements": ["PyMISP"], + "features": "Unlike the GoAML export module, there is here no special feature to import data from GoAML external files, since the module will import MISP Objects with their References on its own, as it is required for the export module to rebuild a valid GoAML document.", + "references": "http://goaml.unodc.org/", + "input": "GoAML format file, describing financial transactions, with their origin and target (bank accounts, persons or entities).", + "output": "MISP objects (transaction, bank-account, person, legal-entity, geolocation), with references, describing financial transactions and their origin and target." +} From ba2a5f7515e8a1ab148d89bf681661b159c76b77 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 6 Mar 2018 11:04:19 +0100 Subject: [PATCH 002/153] CSV import documentation first try --- doc/import_doc/csvimport.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 doc/import_doc/csvimport.json diff --git a/doc/import_doc/csvimport.json b/doc/import_doc/csvimport.json new file mode 100644 index 0000000..8d959af --- /dev/null +++ b/doc/import_doc/csvimport.json @@ -0,0 +1,8 @@ +{ + "description": "Module to import MISP attributes from a csv file.", + "requirements": ["PyMISP"], + "features": "In order to parse data from a csv file, a header is required to let the module know which column is matching with known attribute fields / MISP types.\nThis header is part of the configuration of the module and should be filled out in MISP plugin settings, each field separated by COMMAS. Fields that do not match with any type known in MISP can be ignored in import, using a space or simply nothing between two separators (example: 'ip-src, , comment, ').\nThere is also one type that is confused and can be either a MISP attribute type or an attribute field: 'comment'. In this case, using 'attrComment' specifies that the attribute field 'comment' should be considered, otherwise it will be considered as the MISP attribute type.\n\nFor each MISP attribute type, an attribute is created.\nAttribute fields that are imported are the following: value, type, category, to-ids, distribution, comment, tag.", + "references": ["https://tools.ietf.org/html/rfc4180", "https://tools.ietf.org/html/rfc7111"], + "input": "CSV format file.", + "output": "MISP attributes" +} From 013e552f90c5b0f98b27e31d15217a3c9043576b Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 6 Mar 2018 16:17:22 +0100 Subject: [PATCH 003/153] Added Documentation explanations on readme file --- README.md | 15 +++++++++++++-- doc/export_mod/cef_export.json | 3 +++ doc/export_mod/liteexport.json | 3 +++ doc/export_mod/pdfexport.json | 3 +++ doc/export_mod/testexport.json | 3 +++ doc/export_mod/threatStream_misp_export.json | 3 +++ doc/export_mod/threat_connect_export.json | 3 +++ doc/import_doc/cuckooimport.json | 3 +++ doc/import_doc/email_import.json | 3 +++ doc/import_doc/mispjson.json | 3 +++ doc/import_doc/ocr.json | 3 +++ doc/import_doc/openiocimport.json | 3 +++ doc/import_doc/stiximport.json | 3 +++ doc/import_doc/threatanalyzer_import.json | 3 +++ doc/import_doc/vmray_import.json | 3 +++ 15 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 doc/export_mod/cef_export.json create mode 100644 doc/export_mod/liteexport.json create mode 100644 doc/export_mod/pdfexport.json create mode 100644 doc/export_mod/testexport.json create mode 100644 doc/export_mod/threatStream_misp_export.json create mode 100644 doc/export_mod/threat_connect_export.json create mode 100644 doc/import_doc/cuckooimport.json create mode 100644 doc/import_doc/email_import.json create mode 100644 doc/import_doc/mispjson.json create mode 100644 doc/import_doc/ocr.json create mode 100644 doc/import_doc/openiocimport.json create mode 100644 doc/import_doc/stiximport.json create mode 100644 doc/import_doc/threatanalyzer_import.json create mode 100644 doc/import_doc/vmray_import.json diff --git a/README.md b/README.md index 67ba189..d488813 100644 --- a/README.md +++ b/README.md @@ -372,7 +372,7 @@ Recommended Plugin.Import_ocr_enabled true Enable or disable the ocr In this same menu set any other plugin settings that are required for testing. ## Install misp-module on an offline instance. -First, you need to grab all necessery packages for example like this : +First, you need to grab all necessery packages for example like this : Use pip wheel to create an archive ~~~ @@ -380,7 +380,7 @@ mkdir misp-modules-offline pip3 wheel -r REQUIREMENTS shodan --wheel-dir=./misp-modules-offline tar -cjvf misp-module-bundeled.tar.bz2 ./misp-modules-offline/* ~~~ -On offline machine : +On offline machine : ~~~ mkdir misp-modules-bundle tar xvf misp-module-bundeled.tar.bz2 -C misp-modules-bundle @@ -439,3 +439,14 @@ cd tests/ curl -s http://127.0.0.1:6666/query -H "Content-Type: application/json" --data @MY_TEST_FILE.json -X POST cd ../ ~~~ + +## Documentation + +In order to provide documentation about some modules that require specific input / output / configuration, the [doc](doc) directory contains detailed information about the general purpose, requirements, features, input and ouput of each of these modules: + +- ***description** - quick description of the general purpose of the module, as the one given by the moduleinfo +- **requirements** - special libraries needed to make the module work +- **features** - description of the way to use the module, with the required MISP features to make the module give the intended result +- **references** - link(s) giving additional information about the format concerned in the module +- **input** - description of the format of data used in input +- **output** - description of the format given as the result of the module execution diff --git a/doc/export_mod/cef_export.json b/doc/export_mod/cef_export.json new file mode 100644 index 0000000..44a09e7 --- /dev/null +++ b/doc/export_mod/cef_export.json @@ -0,0 +1,3 @@ +{ + "description": "Module to export a MISP event in CEF format." +} diff --git a/doc/export_mod/liteexport.json b/doc/export_mod/liteexport.json new file mode 100644 index 0000000..f8c3e90 --- /dev/null +++ b/doc/export_mod/liteexport.json @@ -0,0 +1,3 @@ +{ + "description": "Lite export of a MISP event." +} diff --git a/doc/export_mod/pdfexport.json b/doc/export_mod/pdfexport.json new file mode 100644 index 0000000..987dde6 --- /dev/null +++ b/doc/export_mod/pdfexport.json @@ -0,0 +1,3 @@ +{ + "description": "Simple export of a MISP event to PDF." +} diff --git a/doc/export_mod/testexport.json b/doc/export_mod/testexport.json new file mode 100644 index 0000000..213ea92 --- /dev/null +++ b/doc/export_mod/testexport.json @@ -0,0 +1,3 @@ +{ + "description": "Skeleton export module." +} diff --git a/doc/export_mod/threatStream_misp_export.json b/doc/export_mod/threatStream_misp_export.json new file mode 100644 index 0000000..f311c87 --- /dev/null +++ b/doc/export_mod/threatStream_misp_export.json @@ -0,0 +1,3 @@ +{ + "description": "Module to export a structured CSV file for uploading to threatStream." +} diff --git a/doc/export_mod/threat_connect_export.json b/doc/export_mod/threat_connect_export.json new file mode 100644 index 0000000..5ad2469 --- /dev/null +++ b/doc/export_mod/threat_connect_export.json @@ -0,0 +1,3 @@ +{ + "description": "Module to export a structured CSV file for uploading to ThreatConnect." +} diff --git a/doc/import_doc/cuckooimport.json b/doc/import_doc/cuckooimport.json new file mode 100644 index 0000000..d0d17d6 --- /dev/null +++ b/doc/import_doc/cuckooimport.json @@ -0,0 +1,3 @@ +{ + "description": "Module to import Cuckoo JSON." +} diff --git a/doc/import_doc/email_import.json b/doc/import_doc/email_import.json new file mode 100644 index 0000000..9d6abad --- /dev/null +++ b/doc/import_doc/email_import.json @@ -0,0 +1,3 @@ +{ + "description": "Module to import emails in MISP." +} diff --git a/doc/import_doc/mispjson.json b/doc/import_doc/mispjson.json new file mode 100644 index 0000000..b9be29b --- /dev/null +++ b/doc/import_doc/mispjson.json @@ -0,0 +1,3 @@ +{ + "description": "Module to import MISP JSON format for merging MISP events." +} diff --git a/doc/import_doc/ocr.json b/doc/import_doc/ocr.json new file mode 100644 index 0000000..79d4f43 --- /dev/null +++ b/doc/import_doc/ocr.json @@ -0,0 +1,3 @@ +{ + "description": "Optical Character Recognition (OCR) module for MISP." +} diff --git a/doc/import_doc/openiocimport.json b/doc/import_doc/openiocimport.json new file mode 100644 index 0000000..c49db67 --- /dev/null +++ b/doc/import_doc/openiocimport.json @@ -0,0 +1,3 @@ +{ + "description": "Module to import OpenIOC packages." +} diff --git a/doc/import_doc/stiximport.json b/doc/import_doc/stiximport.json new file mode 100644 index 0000000..00442a4 --- /dev/null +++ b/doc/import_doc/stiximport.json @@ -0,0 +1,3 @@ +{ + "description": "Module to import some stix stuff." +} diff --git a/doc/import_doc/threatanalyzer_import.json b/doc/import_doc/threatanalyzer_import.json new file mode 100644 index 0000000..179307a --- /dev/null +++ b/doc/import_doc/threatanalyzer_import.json @@ -0,0 +1,3 @@ +{ + "description": "Module to import ThreatAnalyzer archive.zip / analysis.json files." +} diff --git a/doc/import_doc/vmray_import.json b/doc/import_doc/vmray_import.json new file mode 100644 index 0000000..11b413b --- /dev/null +++ b/doc/import_doc/vmray_import.json @@ -0,0 +1,3 @@ +{ + "description": "Module to import VMRay (VTI) results." +} From 834f0228245d07ff823b72002ade911c7fe652ae Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 6 Mar 2018 17:29:53 +0100 Subject: [PATCH 004/153] First try of documentation for import & export modules - Providing information about the general purpose of the modules, their requirements, how to use them (if there are special features), some references about the format concerned or the vendors, and their input and output. - Documentation to be completed by additional fields of documentation and / or more detailed descriptions --- doc/export_mod/cef_export.json | 7 ++++++- doc/export_mod/liteexport.json | 7 ++++++- doc/export_mod/pdfexport.json | 7 ++++++- doc/export_mod/threatStream_misp_export.json | 7 ++++++- doc/export_mod/threat_connect_export.json | 7 ++++++- doc/import_doc/csvimport.json | 2 +- doc/import_doc/cuckooimport.json | 7 ++++++- doc/import_doc/email_import.json | 7 ++++++- doc/import_doc/mispjson.json | 7 ++++++- doc/import_doc/ocr.json | 7 ++++++- doc/import_doc/openiocimport.json | 7 ++++++- doc/import_doc/stiximport.json | 7 ++++++- doc/import_doc/threatanalyzer_import.json | 7 ++++++- doc/import_doc/vmray_import.json | 7 ++++++- 14 files changed, 79 insertions(+), 14 deletions(-) diff --git a/doc/export_mod/cef_export.json b/doc/export_mod/cef_export.json index 44a09e7..84bba8e 100644 --- a/doc/export_mod/cef_export.json +++ b/doc/export_mod/cef_export.json @@ -1,3 +1,8 @@ { - "description": "Module to export a MISP event in CEF format." + "description": "Module to export a MISP event in CEF format.", + "requirements": [], + "features": "The module takes a MISP event in input, to look every attribute. Each attribute matching with some predefined types is then exported in Common Event Format.\nThus, there is no particular feature concerning MISP Events since any event can be exported. However, 4 configuration parameters recognized by CEF format are required and should be provided by users before exporting data: the device vendor, product and version, as well as the default severity of data.", + "references": ["https://community.softwaregrp.com/t5/ArcSight-Connectors/ArcSight-Common-Event-Format-CEF-Guide/ta-p/1589306?attachment-id=65537"], + "input": "MISP Event attributes", + "output": "Common Event Format file" } diff --git a/doc/export_mod/liteexport.json b/doc/export_mod/liteexport.json index f8c3e90..110577c 100644 --- a/doc/export_mod/liteexport.json +++ b/doc/export_mod/liteexport.json @@ -1,3 +1,8 @@ { - "description": "Lite export of a MISP event." + "description": "Lite export of a MISP event.", + "requirements": [], + "features": "This module is simply producing a json MISP event format file, but exporting only Attributes from the Event. Thus, MISP Events exported with this module should have attributes that are not internal references, otherwise the resulting event would be empty.", + "references": [], + "input": "MISP Event attributes", + "output": "Lite MISP Event" } diff --git a/doc/export_mod/pdfexport.json b/doc/export_mod/pdfexport.json index 987dde6..9803c77 100644 --- a/doc/export_mod/pdfexport.json +++ b/doc/export_mod/pdfexport.json @@ -1,3 +1,8 @@ { - "description": "Simple export of a MISP event to PDF." + "description": "Simple export of a MISP event to PDF.", + "requirements": ["PyMISP", "asciidoctor"], + "features": "The module takes care of the PDF file building, and work with any MISP Event. Except the requirement of asciidoctor, used to create the file, there is no special feature concerning the Event.", + "references": ["https://acrobat.adobe.com/us/en/acrobat/about-adobe-pdf.html"], + "input": "MISP Event", + "output": "MISP Event in a PDF file." } diff --git a/doc/export_mod/threatStream_misp_export.json b/doc/export_mod/threatStream_misp_export.json index f311c87..a275032 100644 --- a/doc/export_mod/threatStream_misp_export.json +++ b/doc/export_mod/threatStream_misp_export.json @@ -1,3 +1,8 @@ { - "description": "Module to export a structured CSV file for uploading to threatStream." + "description": "Module to export a structured CSV file for uploading to threatStream.", + "requirements": ["csv"], + "features": "The module takes a MISP event in input, to look every attribute. Each attribute matching with some predefined types is then exported in a CSV format recognized by ThreatStream.", + "references": ["https://www.anomali.com/platform/threatstream", "https://github.com/threatstream"], + "input": "MISP Event attributes", + "output": "ThreatStream CSV format file" } diff --git a/doc/export_mod/threat_connect_export.json b/doc/export_mod/threat_connect_export.json index 5ad2469..abc4c65 100644 --- a/doc/export_mod/threat_connect_export.json +++ b/doc/export_mod/threat_connect_export.json @@ -1,3 +1,8 @@ { - "description": "Module to export a structured CSV file for uploading to ThreatConnect." + "description": "Module to export a structured CSV file for uploading to ThreatConnect.", + "requirements": ["csv"], + "features": "The module takes a MISP event in input, to look every attribute. Each attribute matching with some predefined types is then exported in a CSV format recognized by ThreatConnect.\nUsers should then provide, as module configuration, the source of data they export, because it is required by the output format.", + "references": ["https://www.threatconnect.com"], + "input": "MISP Event attributes", + "output": "ThreatConnect CSV format file" } diff --git a/doc/import_doc/csvimport.json b/doc/import_doc/csvimport.json index 8d959af..6dc6182 100644 --- a/doc/import_doc/csvimport.json +++ b/doc/import_doc/csvimport.json @@ -4,5 +4,5 @@ "features": "In order to parse data from a csv file, a header is required to let the module know which column is matching with known attribute fields / MISP types.\nThis header is part of the configuration of the module and should be filled out in MISP plugin settings, each field separated by COMMAS. Fields that do not match with any type known in MISP can be ignored in import, using a space or simply nothing between two separators (example: 'ip-src, , comment, ').\nThere is also one type that is confused and can be either a MISP attribute type or an attribute field: 'comment'. In this case, using 'attrComment' specifies that the attribute field 'comment' should be considered, otherwise it will be considered as the MISP attribute type.\n\nFor each MISP attribute type, an attribute is created.\nAttribute fields that are imported are the following: value, type, category, to-ids, distribution, comment, tag.", "references": ["https://tools.ietf.org/html/rfc4180", "https://tools.ietf.org/html/rfc7111"], "input": "CSV format file.", - "output": "MISP attributes" + "output": "MISP Event attributes" } diff --git a/doc/import_doc/cuckooimport.json b/doc/import_doc/cuckooimport.json index d0d17d6..d72469c 100644 --- a/doc/import_doc/cuckooimport.json +++ b/doc/import_doc/cuckooimport.json @@ -1,3 +1,8 @@ { - "description": "Module to import Cuckoo JSON." + "description": "Module to import Cuckoo JSON.", + "requirements": [], + "features": "The module simply imports MISP Attributes from a Cuckoo JSON format file. There is thus no special feature to make it work.", + "references": ["https://cuckoosandbox.org/", "https://github.com/cuckoosandbox/cuckoo"], + "input": "Cuckoo JSON file", + "output": "MISP Event attributes" } diff --git a/doc/import_doc/email_import.json b/doc/import_doc/email_import.json index 9d6abad..1f53852 100644 --- a/doc/import_doc/email_import.json +++ b/doc/import_doc/email_import.json @@ -1,3 +1,8 @@ { - "description": "Module to import emails in MISP." + "description": "Module to import emails in MISP.", + "requirements": [], + "features": "This module can be used to import e-mail text as well as attachments and urls.\n3 configuration parameters are then used to unzip attachments, guess zip attachment passwords, and extract urls: set each one of them to True or False to process or not the respective corresponding actions.", + "references": [], + "input": "E-mail file", + "output": "MISP Event attributes" } diff --git a/doc/import_doc/mispjson.json b/doc/import_doc/mispjson.json index b9be29b..dd11405 100644 --- a/doc/import_doc/mispjson.json +++ b/doc/import_doc/mispjson.json @@ -1,3 +1,8 @@ { - "description": "Module to import MISP JSON format for merging MISP events." + "description": "Module to import MISP JSON format for merging MISP events.", + "requirements": [], + "features": "The module simply imports MISP Attributes from an other MISP Event in order to merge events together. There is thus no special feature to make it work.", + "references": [], + "input": "MISP Event", + "output": "MISP Event attributes" } diff --git a/doc/import_doc/ocr.json b/doc/import_doc/ocr.json index 79d4f43..14bbf0b 100644 --- a/doc/import_doc/ocr.json +++ b/doc/import_doc/ocr.json @@ -1,3 +1,8 @@ { - "description": "Optical Character Recognition (OCR) module for MISP." + "description": "Optical Character Recognition (OCR) module for MISP.", + "requirements": [], + "features": "The module tries to recognize some text from an image and import the result as a freetext attribute, there is then no special feature asked to users to make it work.", + "references": [], + "input": "Image", + "output": "freetext MISP attribute" } diff --git a/doc/import_doc/openiocimport.json b/doc/import_doc/openiocimport.json index c49db67..e173392 100644 --- a/doc/import_doc/openiocimport.json +++ b/doc/import_doc/openiocimport.json @@ -1,3 +1,8 @@ { - "description": "Module to import OpenIOC packages." + "description": "Module to import OpenIOC packages.", + "requirements": ["PyMISP"], + "features": "The module imports MISP Attributes from OpenIOC packages, there is then no special feature for users to make it work.", + "references": ["https://www.fireeye.com/blog/threat-research/2013/10/openioc-basics.html"], + "input": "OpenIOC packages", + "output": "MISP Event attributes" } diff --git a/doc/import_doc/stiximport.json b/doc/import_doc/stiximport.json index 00442a4..7b22029 100644 --- a/doc/import_doc/stiximport.json +++ b/doc/import_doc/stiximport.json @@ -1,3 +1,8 @@ { - "description": "Module to import some stix stuff." + "description": "Module to import some stix stuff.", + "requirements": ["stix"], + "features": "The module imports MISP Attributes from a STIX 1 file, using the stix library, there is thus no special feature for users to make it work.", + "references": ["https://oasis-open.github.io/cti-documentation/", "https://github.com/STIXProject/python-stix"], + "input": "STIX format file", + "output": "MISP Event attributes" } diff --git a/doc/import_doc/threatanalyzer_import.json b/doc/import_doc/threatanalyzer_import.json index 179307a..40e4436 100644 --- a/doc/import_doc/threatanalyzer_import.json +++ b/doc/import_doc/threatanalyzer_import.json @@ -1,3 +1,8 @@ { - "description": "Module to import ThreatAnalyzer archive.zip / analysis.json files." + "description": "Module to import ThreatAnalyzer archive.zip / analysis.json files.", + "requirements": [], + "features": "The module imports MISP Attributes from a ThreatAnalyzer format file. This file can be either ZIP, or JSON format.\nThere is by the way no special feature for users to make the module work.", + "references": ["https://www.threattrack.com/malware-analysis.aspx"], + "input": "ThreatAnalyzer format file", + "output": "MISP Event attributes" } diff --git a/doc/import_doc/vmray_import.json b/doc/import_doc/vmray_import.json index 11b413b..719730c 100644 --- a/doc/import_doc/vmray_import.json +++ b/doc/import_doc/vmray_import.json @@ -1,3 +1,8 @@ { - "description": "Module to import VMRay (VTI) results." + "description": "Module to import VMRay (VTI) results.", + "requirements": ["vmray_rest_api"], + "features": "The module imports MISP Attributes from VMRay format, using the VMRay api.\nUsers should then provide as the module configuration the API Key as well as the server url in order to fetch their data to import.", + "references": ["https://www.vmray.com/"], + "input": "VMRay format", + "output": "MISP Event attributes" } From 59b7688bdcbdc3d1d1e085ec6e8862c0c8585b33 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Thu, 28 Jun 2018 16:00:14 +0800 Subject: [PATCH 005/153] - Added initial PDF support, nothing is processed yet - Test to replace PIL with wand --- misp_modules/modules/import_mod/ocr.py | 27 +++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/import_mod/ocr.py b/misp_modules/modules/import_mod/ocr.py index aafe653..17d634c 100755 --- a/misp_modules/modules/import_mod/ocr.py +++ b/misp_modules/modules/import_mod/ocr.py @@ -1,8 +1,11 @@ import json import base64 +import magic from PIL import Image +from wand.image import Image as WImage + from pytesseract import image_to_string from io import BytesIO misperrors = {'error': 'Error'} @@ -10,7 +13,7 @@ userConfig = { }; inputSource = ['file'] -moduleinfo = {'version': '0.1', 'author': 'Alexandre Dulaunoy', +moduleinfo = {'version': '0.2', 'author': 'Alexandre Dulaunoy', 'description': 'Optical Character Recognition (OCR) module for MISP', 'module-type': ['import']} @@ -22,10 +25,28 @@ def handler(q=False): return False r = {'results': []} request = json.loads(q) - image = base64.b64decode(request["data"]) + document = base64.b64decode(request["data"]) + if magic.from_buffer(document, mime=True).split("/")[1] == 'pdf': + print("PDF Detected") + with WImage(blob=document) as pdf: + pages=len(pdf.sequence) + img = WImage(width=pdf.width, height=pdf.height * pages) + for p in range(pages): + img.composite(pdf.sequence[p], top=pdf.height * i, left=0) + image = document + image_file = BytesIO(image) image_file.seek(0) - ocrized = image_to_string(Image.open(image_file)) + + try: + im = WImage(blob=image_file) + except IOError: + misperrors['error'] = "Corrupt or not an image file." + return misperrors + + + ocrized = image_to_string(im) + freetext = {} freetext['values'] = ocrized freetext['types'] = ['freetext'] From 7c691af8075c5c4138cf57b0b902df998067cab8 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 28 Jun 2018 10:39:40 +0200 Subject: [PATCH 006/153] Updated the list of expansion modules --- misp_modules/modules/expansion/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index a9389e0..02c40ef 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,3 +1,3 @@ from . import _vmray -__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd'] +__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange'] From b1c90b411eb5792a6f78e15e1ab104055b51d5dc Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 28 Jun 2018 10:41:32 +0200 Subject: [PATCH 007/153] add: Sigma syntax validator expansion module --> Checks sigma rules syntax - Updated the expansion modules list as well - Updated the requirements list --- REQUIREMENTS | 1 + misp_modules/modules/expansion/__init__.py | 2 +- .../expansion/sigma_syntax_validator.py | 35 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 misp_modules/modules/expansion/sigma_syntax_validator.py diff --git a/REQUIREMENTS b/REQUIREMENTS index 9404855..a8baf52 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -20,3 +20,4 @@ pygeoip bs4 oauth2 yara +sigmatools diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index 02c40ef..b49c1dc 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,3 +1,3 @@ from . import _vmray -__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange'] +__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator'] diff --git a/misp_modules/modules/expansion/sigma_syntax_validator.py b/misp_modules/modules/expansion/sigma_syntax_validator.py new file mode 100644 index 0000000..0d5226f --- /dev/null +++ b/misp_modules/modules/expansion/sigma_syntax_validator.py @@ -0,0 +1,35 @@ +import json +try: + import yaml + from sigma.parser import SigmaParser + from sigma.config import SigmaConfiguration +except ModuleNotFoundError: + print("sigma or yaml is missing, use 'pip3 install sigmatools' to install it.") + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['sigma'], 'output': ['text']} +moduleinfo = {'version': '0.1', 'author': 'Christian Studer', 'module-type': ['expansion', 'hover'], + 'description': 'An expansion hover module to perform a syntax check on sigma rules'} +moduleconfig = [] + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + if not request.get('sigma'): + misperrors['error'] = 'Sigma rule missing' + return misperrors + config = SigmaConfiguration() + try: + parser = SigmaParser(yaml.load(request.get('sigma')), config) + result = ("Syntax valid: {}".format(parser.values)) + except Exception as e: + result = ("Syntax error: {}".format(str(e))) + return {'results': [{'types': mispattributes['output'], 'values': result}]} + +def introspection(): + return mispattributes + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 7dd8e988c0b0c6af51c9234b938f8ea7291d8a04 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 28 Jun 2018 10:51:40 +0200 Subject: [PATCH 008/153] Updated the list of modules (removed stiximport) --- misp_modules/modules/import_mod/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/misp_modules/modules/import_mod/__init__.py b/misp_modules/modules/import_mod/__init__.py index 886eaf7..0a732e2 100644 --- a/misp_modules/modules/import_mod/__init__.py +++ b/misp_modules/modules/import_mod/__init__.py @@ -1,4 +1,3 @@ from . import _vmray -__all__ = ['vmray_import', 'testimport', 'ocr', 'stiximport', 'cuckooimport', 'goamlimport', - 'email_import', 'mispjson', 'openiocimport', 'threatanalyzer_import', 'csvimport'] +__all__ = ['vmray_import', 'testimport', 'ocr', 'cuckooimport', 'goamlimport', 'email_import', 'mispjson', 'openiocimport', 'threatanalyzer_import', 'csvimport'] From 7885017981a122761ce1613858ff904115eb10cc Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Thu, 28 Jun 2018 16:59:03 +0800 Subject: [PATCH 009/153] - fixed typo move image back in scope --- misp_modules/modules/import_mod/ocr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/import_mod/ocr.py b/misp_modules/modules/import_mod/ocr.py index 17d634c..0748d35 100755 --- a/misp_modules/modules/import_mod/ocr.py +++ b/misp_modules/modules/import_mod/ocr.py @@ -32,8 +32,8 @@ def handler(q=False): pages=len(pdf.sequence) img = WImage(width=pdf.width, height=pdf.height * pages) for p in range(pages): - img.composite(pdf.sequence[p], top=pdf.height * i, left=0) - image = document + img.composite(pdf.sequence[p], top=pdf.height * p, left=0) + image = document image_file = BytesIO(image) image_file.seek(0) From e9ee09eec61fbca7fbd956fdcd8ef2a224e585a5 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 28 Jun 2018 11:27:35 +0200 Subject: [PATCH 010/153] Updated README to add sigma & some other missing modules --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b96814..14840ea 100644 --- a/README.md +++ b/README.md @@ -30,15 +30,22 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [GeoIP](misp_modules/modules/expansion/geoip_country.py) - a hover and expansion module to get GeoIP information from geolite/maxmind. * [hashdd](misp_modules/modules/expansion/hashdd.py) - a hover module to check file hashes against [hashdd.com](http://www.hashdd.com) including NSLR dataset. * [IPASN](misp_modules/modules/expansion/ipasn.py) - a hover and expansion to get the BGP ASN of an IP address. -* [iprep](misp-modules/modules/expansion/iprep.py) - an expansion module to get IP reputation from packetmail.net. +* [iprep](misp_modules/modules/expansion/iprep.py) - an expansion module to get IP reputation from packetmail.net. +* [onyphe](misp_modules/modules/expansion/onyphe.py) - a modules to process queries on Onyphe. +* [onyphe_full](misp_modules/modules/expansion/onyphe_full.py) - a modules to process full queries on Onyphe. * [OTX](misp_modules/modules/expansion/otx.py) - an expansion module for [OTX](https://otx.alienvault.com/). * [passivetotal](misp_modules/modules/expansion/passivetotal.py) - a [passivetotal](https://www.passivetotal.org/) module that queries a number of different PassiveTotal datasets. * [rbl](misp_modules/modules/expansion/rbl.py) - a module to get RBL (Real-Time Blackhost List) values from an attribute. +* [reversedns](misp_modules/modules/expansion/reversedns.py) - Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes. * [shodan](misp_modules/modules/expansion/shodan.py) - a minimal [shodan](https://www.shodan.io/) expansion module. +* [Sigma syntax validator](misp_modules/modules/expansion/sigma_syntax_validator.py) - Sigma syntax validator. * [sourcecache](misp_modules/modules/expansion/sourcecache.py) - a module to cache a specific link from a MISP instance. * [ThreatCrowd](misp_modules/modules/expansion/threatcrowd.py) - an expansion module for [ThreatCrowd](https://www.threatcrowd.org/). * [threatminer](misp_modules/modules/expansion/threatminer.py) - an expansion module to expand from [ThreatMiner](https://www.threatminer.org/). * [virustotal](misp_modules/modules/expansion/virustotal.py) - an expansion module to pull known resolutions and malware samples related with an IP/Domain from virusTotal (this modules require a VirusTotal private API key) +* [VMray](misp_modules/modules/expansion/vmray_submit.py) - a module to submit a sample to VMray. +* [VulnDB](misp_modules/modules/expansion/vulndb.py) - a module to query [VulnDB](https://www.riskbasedsecurity.com/). +* [whois](misp_modules/modules/expansion) - a module to query a local instance of [uwhois](https://github.com/rafiot/uwhoisd). * [wikidata](misp_modules/modules/expansion/wiki.py) - a [wikidata](https://www.wikidata.org) expansion module. * [xforce](misp_modules/modules/expansion/xforceexchange.py) - an IBM X-Force Exchange expansion module. * [YARA syntax validator](misp_modules/modules/expansion/yara_syntax_validator.py) - YARA syntax validator. From 60a3fbe28204c5178b2bceed2b3551a27e8c6ce4 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Thu, 28 Jun 2018 23:20:38 +0800 Subject: [PATCH 011/153] - added wand requirement - fixed missing return png byte-stream - move module import to handler to catch and report errorz --- REQUIREMENTS | 1 + misp_modules/modules/import_mod/ocr.py | 41 +++++++++++++++++++------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/REQUIREMENTS b/REQUIREMENTS index 9404855..c116763 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -14,6 +14,7 @@ git+https://github.com/MISP/PyMISP.git#egg=pymisp git+https://github.com/sebdraven/pyonyphe#egg=pyonyphe pillow pytesseract +wand SPARQLWrapper domaintools_api pygeoip diff --git a/misp_modules/modules/import_mod/ocr.py b/misp_modules/modules/import_mod/ocr.py index 0748d35..a30bba0 100755 --- a/misp_modules/modules/import_mod/ocr.py +++ b/misp_modules/modules/import_mod/ocr.py @@ -1,12 +1,5 @@ import json import base64 -import magic - -from PIL import Image - -from wand.image import Image as WImage - -from pytesseract import image_to_string from io import BytesIO misperrors = {'error': 'Error'} userConfig = { }; @@ -21,6 +14,32 @@ moduleconfig = [] def handler(q=False): + # try to import modules and return errors if module not found + try: + import magic + except ImportError: + misperrors['error'] = "Please pip(3) install magic" + return misperrors + + try: + from PIL import Image + except ImportError: + misperrors['error'] = "Please pip(3) install pillow" + return misperrors + + try: + # Official ImageMagick module + from wand.image import Image as WImage + except ImportError: + misperrors['error'] = "Please pip(3) install wand" + return misperrors + + try: + from pytesseract import image_to_string + except ImportError: + misperrors['error'] = "Please pip(3) install pytesseract" + return misperrors + if q is False: return False r = {'results': []} @@ -32,14 +51,16 @@ def handler(q=False): pages=len(pdf.sequence) img = WImage(width=pdf.width, height=pdf.height * pages) for p in range(pages): - img.composite(pdf.sequence[p], top=pdf.height * p, left=0) - image = document + image = img.composite(pdf.sequence[p], top=pdf.height * p, left=0) + image = img.make_blob('png') + else: + image = document image_file = BytesIO(image) image_file.seek(0) try: - im = WImage(blob=image_file) + im = Image.open(image_file) except IOError: misperrors['error'] = "Corrupt or not an image file." return misperrors From fbb3617f256d19e272d95e7a6f2c9e745acdfe94 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Fri, 29 Jun 2018 12:01:17 +0800 Subject: [PATCH 012/153] - Quick comment ToDo: Avoid using Magic in future releases --- misp_modules/modules/import_mod/ocr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/import_mod/ocr.py b/misp_modules/modules/import_mod/ocr.py index a30bba0..2248306 100755 --- a/misp_modules/modules/import_mod/ocr.py +++ b/misp_modules/modules/import_mod/ocr.py @@ -45,7 +45,7 @@ def handler(q=False): r = {'results': []} request = json.loads(q) document = base64.b64decode(request["data"]) - if magic.from_buffer(document, mime=True).split("/")[1] == 'pdf': + if magic.from_buffer(document, mime=True).split("/")[1] == 'pdf': # Eventually this could be replaced with wand.obj.format print("PDF Detected") with WImage(blob=document) as pdf: pages=len(pdf.sequence) From c7c93b53e8522c6318f9571d1c78d7f5a4b8b25f Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Fri, 29 Jun 2018 12:02:08 +0800 Subject: [PATCH 013/153] - Set tornado timeout to 300 seconds. --- misp_modules/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/__init__.py b/misp_modules/__init__.py index 1c1713b..3bb7253 100644 --- a/misp_modules/__init__.py +++ b/misp_modules/__init__.py @@ -193,7 +193,7 @@ class QueryModule(tornado.web.RequestHandler): if dict_payload.get('timeout'): timeout = datetime.timedelta(seconds=int(dict_payload.get('timeout'))) else: - timeout = datetime.timedelta(seconds=30) + timeout = datetime.timedelta(seconds=300) response = yield tornado.gen.with_timeout(timeout, self.run_request(jsonpayload)) self.write(response) except tornado.gen.TimeoutError: From ff793bc221aff4ab70911115b9ea3b18eb7be1e1 Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Fri, 29 Jun 2018 11:17:03 +0200 Subject: [PATCH 014/153] threatanalyzer_import - order of category tuned --- misp_modules/modules/import_mod/threatanalyzer_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/import_mod/threatanalyzer_import.py b/misp_modules/modules/import_mod/threatanalyzer_import.py index 757f849..83d8291 100755 --- a/misp_modules/modules/import_mod/threatanalyzer_import.py +++ b/misp_modules/modules/import_mod/threatanalyzer_import.py @@ -69,7 +69,7 @@ def handler(q=False): results.append({ 'values': current_sample_filename, 'data': base64.b64encode(file_data).decode(), - 'type': 'malware-sample', 'categories': ['Artifacts dropped', 'Payload delivery'], 'to_ids': True, 'comment': ''}) + 'type': 'malware-sample', 'categories': ['Payload delivery', 'Artifacts dropped'], 'to_ids': True, 'comment': ''}) if 'Analysis/analysis.json' in zip_file_name: with zf.open(zip_file_name, mode='r', pwd=None) as fp: From 60f772b9050ccd9ae3678dd38bbd9003de2df3c1 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 11:27:36 +0200 Subject: [PATCH 015/153] add new module dnstrails --- misp_modules/modules/expansion/dnstrails.py | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 misp_modules/modules/expansion/dnstrails.py diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py new file mode 100644 index 0000000..d77c8d1 --- /dev/null +++ b/misp_modules/modules/expansion/dnstrails.py @@ -0,0 +1,25 @@ +import logging +import sys + +log = logging.getLogger('dnstrails') +log.setLevel(logging.DEBUG) +ch = logging.StreamHandler(sys.stdout) +ch.setLevel(logging.DEBUG) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +ch.setFormatter(formatter) +log.addHandler(ch) + +misperrors = {'error': 'Error'} +mispattributes = { + 'input': ['hostname', 'domain', 'ip-src', 'ip-dst'], + 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'dns-soa-email'] +} + +moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', + 'description': 'Query on securitytrails.com', + 'module-type': ['expansion', 'hover']} + +# config fields that your code expects from the site admin +moduleconfig = ['apikey'] + + From 035606a21acfed52a951c56c3ffe5a551d3d4d74 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 11:47:11 +0200 Subject: [PATCH 016/153] add link pydnstrain in requirements --- REQUIREMENTS | 1 + 1 file changed, 1 insertion(+) diff --git a/REQUIREMENTS b/REQUIREMENTS index a8baf52..11393b0 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -12,6 +12,7 @@ asnhistory git+https://github.com/Rafiot/uwhoisd.git@testing#egg=uwhois&subdirectory=client git+https://github.com/MISP/PyMISP.git#egg=pymisp git+https://github.com/sebdraven/pyonyphe#egg=pyonyphe +git+https://github.com/sebdraven/pydnstrails#egg=pydnstrails pillow pytesseract SPARQLWrapper From cfe971a27185a1f64fc2927f3ffbe9b74a98ffcc Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 15:50:26 +0200 Subject: [PATCH 017/153] add expand domains --- misp_modules/modules/expansion/dnstrails.py | 140 ++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index d77c8d1..3655269 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -1,5 +1,7 @@ +import json import logging import sys +from dnstrails import DnsTrails log = logging.getLogger('dnstrails') log.setLevel(logging.DEBUG) @@ -23,3 +25,141 @@ moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', moduleconfig = ['apikey'] +def handler(q=False): + if q: + + request = json.loads(q) + + if not request.get('config') and not (request['config'].get('apikey')): + misperrors['error'] = 'DNS authentication is missing' + return misperrors + + api = DnsTrails(request['config'].get('apikey')) + + if not api: + misperrors['error'] = 'Onyphe Error instance api' + + ip = "" + dns_name = "" + + ip = '' + if request.get('ip-src'): + ip = request['ip-src'] + return handle_ip(api, ip, misperrors) + elif request.get('ip-dst'): + ip = request['ip-dst'] + return handle_ip(api, ip, misperrors) + elif request.get('domain'): + domain = request['domain'] + return handle_domain(api, domain, misperrors) + elif request.get('hostname'): + hostname = request['hostname'] + return handle_domain(api, hostname, misperrors) + else: + misperrors['error'] = "Unsupported attributes type" + return misperrors + else: + return False + + +def handle_domain(api, domain, misperrors): + result_filtered = {"results": []} + + r, status_ok = expand_domain_info(api, misperrors, domain) + + if status_ok: + result_filtered['results'].extend(r) + else: + misperrors['error'] = 'Error pastries result' + return misperrors + + return result_filtered + +def handle_ip(api, ip, misperrors): + pass + + +def expand_domain_info(api, misperror,domain): + r = [] + status_ok = False + ns_servers = [] + list_ipv4 = [] + list_ipv6 = [] + servers_mx = [] + soa_hostnames = [] + + results = api.domain(domain) + + if results: + if 'current_dns' in results: + if 'values' in results['current_dns']['ns']: + ns_servers = [ns_entry['nameserver'] for ns_entry in + results['current_dns']['ns']['values'] + if 'nameserver' in ns_entry] + if 'values' in results['current_dns']['a']: + list_ipv4 = [a_entry['ip'] for a_entry in + results['current_dns']['a']['values'] if + 'ip' in a_entry] + + if 'values' in results['current_dns']['aaaa']: + list_ipv6 = [ipv6_entry['ipv6'] for ipv6_entry in + results['current_ns']['aaaa']['values'] if + 'ipv6' in ipv6_entry] + + if 'values' in results['current_dns']['mx']: + servers_mx = [mx_entry['hostname'] for mx_entry in + results['current_dns']['mx']['values'] if + 'hostname' in mx_entry] + if 'values' in results['current_dns']['soa']: + soa_hostnames = [soa_entry['email'] for soa_entry in + results['current_dns']['soa']['values'] if + 'email' in soa_entry] + + if ns_servers: + r.append({'type': ['domain'], + 'values': ns_servers, + 'Category': ['Network Activity'], + 'comment': 'List of name servers of %s first seen %s ' % + (domain, results['current_dns']['ns']['first_seen']) + }) + + if list_ipv4: + r.append({'type': ['domain|ip'], + 'values': ['%s|%s' % (domain, ipv4) for ipv4 in list_ipv4], + 'Category': ['Network Activity'], + 'comment': ' List ipv4 of %s first seen %s' % + (domain, + results['current_dns']['a']['first_seen']) + + }) + if list_ipv6: + r.append({'type': ['domain|ip'], + 'values': ['%s|%s' % (domain, ipv6) for ipv6 in + list_ipv6], + 'Category': ['Network Activity'], + 'comment': ' List ipv6 of %s first seen %s' % + (domain, + results['current_dns']['aaaa']['first_seen']) + + }) + + if servers_mx: + r.append({'type': ['domain'], + 'values': servers_mx, + 'Category': ['Network Activity'], + 'comment': ' List mx of %s first seen %s' % + (domain, + results['current_dns']['mx']['first_seen']) + + }) + if soa_hostnames: + r.append({'type': ['domain'], + 'values': soa_hostnames, + 'Category': ['Network Activity'], + 'comment': ' List soa of %s first seen %s' % + (domain, + results['current_dns']['soa']['first_seen']) + }) + + + return r, status_ok From 09c52788b8f2479f1b0571be7514c66220d42baf Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 16:11:24 +0200 Subject: [PATCH 018/153] add methods --- misp_modules/modules/expansion/dnstrails.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 3655269..d357ed8 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -163,3 +163,11 @@ def expand_domain_info(api, misperror,domain): return r, status_ok + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo \ No newline at end of file From f3962d2d0599ef689d2d52b458c36c9cf123fea5 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 16:17:32 +0200 Subject: [PATCH 019/153] add status ! --- misp_modules/modules/expansion/dnstrails.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index d357ed8..5d5e9d4 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -70,7 +70,7 @@ def handle_domain(api, domain, misperrors): if status_ok: result_filtered['results'].extend(r) else: - misperrors['error'] = 'Error pastries result' + misperrors['error'] = 'Error dns result' return misperrors return result_filtered @@ -91,6 +91,7 @@ def expand_domain_info(api, misperror,domain): results = api.domain(domain) if results: + status_ok = True if 'current_dns' in results: if 'values' in results['current_dns']['ns']: ns_servers = [ns_entry['nameserver'] for ns_entry in From 0275e3ecd8b9e976a12f2de2bfa1ac146f4b3fbe Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 16:20:35 +0200 Subject: [PATCH 020/153] changes keys --- misp_modules/modules/expansion/dnstrails.py | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 5d5e9d4..41151e6 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -19,7 +19,7 @@ mispattributes = { moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', 'description': 'Query on securitytrails.com', - 'module-type': ['expansion', 'hover']} + 'module-types': ['expansion', 'hover']} # config fields that your code expects from the site admin moduleconfig = ['apikey'] @@ -56,7 +56,7 @@ def handler(q=False): hostname = request['hostname'] return handle_domain(api, hostname, misperrors) else: - misperrors['error'] = "Unsupported attributes type" + misperrors['error'] = "Unsupported attributes types" return misperrors else: return False @@ -117,27 +117,27 @@ def expand_domain_info(api, misperror,domain): 'email' in soa_entry] if ns_servers: - r.append({'type': ['domain'], + r.append({'types': ['domain'], 'values': ns_servers, - 'Category': ['Network Activity'], + 'categories': ['Network Activity'], 'comment': 'List of name servers of %s first seen %s ' % (domain, results['current_dns']['ns']['first_seen']) }) if list_ipv4: - r.append({'type': ['domain|ip'], + r.append({'types': ['domain|ip'], 'values': ['%s|%s' % (domain, ipv4) for ipv4 in list_ipv4], - 'Category': ['Network Activity'], + 'categories': ['Network Activity'], 'comment': ' List ipv4 of %s first seen %s' % (domain, results['current_dns']['a']['first_seen']) }) if list_ipv6: - r.append({'type': ['domain|ip'], + r.append({'types': ['domain|ip'], 'values': ['%s|%s' % (domain, ipv6) for ipv6 in list_ipv6], - 'Category': ['Network Activity'], + 'categories': ['Network Activity'], 'comment': ' List ipv6 of %s first seen %s' % (domain, results['current_dns']['aaaa']['first_seen']) @@ -145,18 +145,18 @@ def expand_domain_info(api, misperror,domain): }) if servers_mx: - r.append({'type': ['domain'], + r.append({'types': ['domain'], 'values': servers_mx, - 'Category': ['Network Activity'], + 'categories': ['Network Activity'], 'comment': ' List mx of %s first seen %s' % (domain, results['current_dns']['mx']['first_seen']) }) if soa_hostnames: - r.append({'type': ['domain'], + r.append({'types': ['domain'], 'values': soa_hostnames, - 'Category': ['Network Activity'], + 'categories': ['Network Activity'], 'comment': ' List soa of %s first seen %s' % (domain, results['current_dns']['soa']['first_seen']) From 2d1adf4aa959aa9874ef4dc7b0ec79a133f3fbc2 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 16:30:47 +0200 Subject: [PATCH 021/153] change categories --- misp_modules/modules/expansion/dnstrails.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 41151e6..d2fd5e1 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -119,7 +119,7 @@ def expand_domain_info(api, misperror,domain): if ns_servers: r.append({'types': ['domain'], 'values': ns_servers, - 'categories': ['Network Activity'], + 'categories': ['Network activity'], 'comment': 'List of name servers of %s first seen %s ' % (domain, results['current_dns']['ns']['first_seen']) }) @@ -127,7 +127,8 @@ def expand_domain_info(api, misperror,domain): if list_ipv4: r.append({'types': ['domain|ip'], 'values': ['%s|%s' % (domain, ipv4) for ipv4 in list_ipv4], - 'categories': ['Network Activity'], + 'categories': ['Network activity'], + 'comment': ' List ipv4 of %s first seen %s' % (domain, results['current_dns']['a']['first_seen']) @@ -137,7 +138,7 @@ def expand_domain_info(api, misperror,domain): r.append({'types': ['domain|ip'], 'values': ['%s|%s' % (domain, ipv6) for ipv6 in list_ipv6], - 'categories': ['Network Activity'], + 'categories': ['Network activity'], 'comment': ' List ipv6 of %s first seen %s' % (domain, results['current_dns']['aaaa']['first_seen']) @@ -147,7 +148,7 @@ def expand_domain_info(api, misperror,domain): if servers_mx: r.append({'types': ['domain'], 'values': servers_mx, - 'categories': ['Network Activity'], + 'categories': ['Network activity'], 'comment': ' List mx of %s first seen %s' % (domain, results['current_dns']['mx']['first_seen']) @@ -156,7 +157,7 @@ def expand_domain_info(api, misperror,domain): if soa_hostnames: r.append({'types': ['domain'], 'values': soa_hostnames, - 'categories': ['Network Activity'], + 'categories': ['Network activity'], 'comment': ' List soa of %s first seen %s' % (domain, results['current_dns']['soa']['first_seen']) From 64847a8a04253f68b016e0283dd06cf5972a98ca Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 17:19:21 +0200 Subject: [PATCH 022/153] add expand subdomains --- misp_modules/modules/expansion/dnstrails.py | 25 ++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index d2fd5e1..ffb4055 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -2,6 +2,7 @@ import json import logging import sys from dnstrails import DnsTrails +from dnstrails import APIError log = logging.getLogger('dnstrails') log.setLevel(logging.DEBUG) @@ -163,9 +164,31 @@ def expand_domain_info(api, misperror,domain): results['current_dns']['soa']['first_seen']) }) - return r, status_ok + +def expand_subdomains(api, domain): + + r = [] + status_ok = False + + try: + results = api.subdomains(domain) + + if results: + status_ok = True + if 'subdomains' in results: + r.append({ + 'type': ['domain'], + 'values': ['%s.%s' % (sub,domain) for sub in results['subdomains']], + } + + ) + except APIError as e: + misperrors['error'] = e + return r, status_ok + + def introspection(): return mispattributes From 0965def6bfd9d4e6def23d8b442e7898238b534c Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 17:22:19 +0200 Subject: [PATCH 023/153] add expand subdomains --- misp_modules/modules/expansion/dnstrails.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index ffb4055..abef1e5 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -1,8 +1,9 @@ import json import logging import sys -from dnstrails import DnsTrails + from dnstrails import APIError +from dnstrails import DnsTrails log = logging.getLogger('dnstrails') log.setLevel(logging.DEBUG) @@ -68,6 +69,14 @@ def handle_domain(api, domain, misperrors): r, status_ok = expand_domain_info(api, misperrors, domain) + if status_ok: + result_filtered['results'].extend(r) + else: + misperrors['error'] = 'Error dns result' + return misperrors + + r, status_ok = expand_subdomains(api, domain) + if status_ok: result_filtered['results'].extend(r) else: From 78d6de9b7a47d4a997cfa996a9ae7f7b8014ab95 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 17:25:37 +0200 Subject: [PATCH 024/153] add categories and comments --- misp_modules/modules/expansion/dnstrails.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index abef1e5..1fd7500 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -190,6 +190,8 @@ def expand_subdomains(api, domain): r.append({ 'type': ['domain'], 'values': ['%s.%s' % (sub,domain) for sub in results['subdomains']], + 'categories': ['Network activity'], + 'comment': 'subdomains of %s' % domain } ) From f1c6095914381aa41feed5a2043989665e04eabc Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 17:26:56 +0200 Subject: [PATCH 025/153] typo --- misp_modules/modules/expansion/dnstrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 1fd7500..79afeb3 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -188,7 +188,7 @@ def expand_subdomains(api, domain): status_ok = True if 'subdomains' in results: r.append({ - 'type': ['domain'], + 'types': ['domain'], 'values': ['%s.%s' % (sub,domain) for sub in results['subdomains']], 'categories': ['Network activity'], 'comment': 'subdomains of %s' % domain From 34da5cdb767232bdf03c087f2368eb104f0b88d2 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 29 Jun 2018 17:57:11 +0200 Subject: [PATCH 026/153] add expand whois --- misp_modules/modules/expansion/dnstrails.py | 53 ++++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 79afeb3..d1c276a 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -16,7 +16,10 @@ log.addHandler(ch) misperrors = {'error': 'Error'} mispattributes = { 'input': ['hostname', 'domain', 'ip-src', 'ip-dst'], - 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'dns-soa-email'] + 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'dns-soa-email', + 'whois-registrant-email', 'whois-registrant-phone', + 'whois-registrant-name', + 'whois-registrar', 'whois-creation-date', 'domain'] } moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', @@ -77,6 +80,14 @@ def handle_domain(api, domain, misperrors): r, status_ok = expand_subdomains(api, domain) + if status_ok: + result_filtered['results'].extend(r) + else: + misperrors['error'] = 'Error dns result' + return misperrors + + r, status_ok = expand_whois(api, domain) + if status_ok: result_filtered['results'].extend(r) else: @@ -181,6 +192,7 @@ def expand_subdomains(api, domain): r = [] status_ok = False + try: results = api.subdomains(domain) @@ -200,10 +212,47 @@ def expand_subdomains(api, domain): return r, status_ok +def expand_whois(api, domain): + r = [] + status_ok = False + + try: + results = api.whois(domain) + + if results: + status_ok = True + item_registrant = __select_registrant_item(results) + + r.append({ + 'types': ['whois-registrant-email', 'whois-registrant-phone', + 'whois-registrant-name', 'whois-registrar', + 'whois-creation-date'], + 'values': [item_registrant['email'], + item_registrant['telephone'], + item_registrant['name'], results['registrarName'], + results['creationDate']], + 'categories': ['attribution'], + 'comment': 'whois information of %s by securitytrails' % domain + } + + ) + + except APIError as e: + misperrors['error'] = e + + return r, status_ok + def introspection(): return mispattributes def version(): moduleinfo['config'] = moduleconfig - return moduleinfo \ No newline at end of file + return moduleinfo + + +def __select_registrant_item(entry): + if 'contacts' in entry: + for c in entry['contacts']: + if c['type'] == 'registrant': + return entry From ef3837077e1e5da2d1f7bf4f40730e10be6aea1f Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Sat, 30 Jun 2018 00:58:25 +0800 Subject: [PATCH 027/153] - Some more comments - Removed libmagic, wand can handle it better --- misp_modules/modules/import_mod/ocr.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/misp_modules/modules/import_mod/ocr.py b/misp_modules/modules/import_mod/ocr.py index 2248306..441adc4 100755 --- a/misp_modules/modules/import_mod/ocr.py +++ b/misp_modules/modules/import_mod/ocr.py @@ -1,8 +1,9 @@ import json import base64 from io import BytesIO + misperrors = {'error': 'Error'} -userConfig = { }; +userConfig = {}; inputSource = ['file'] @@ -15,12 +16,6 @@ moduleconfig = [] def handler(q=False): # try to import modules and return errors if module not found - try: - import magic - except ImportError: - misperrors['error'] = "Please pip(3) install magic" - return misperrors - try: from PIL import Image except ImportError: @@ -45,13 +40,18 @@ def handler(q=False): r = {'results': []} request = json.loads(q) document = base64.b64decode(request["data"]) - if magic.from_buffer(document, mime=True).split("/")[1] == 'pdf': # Eventually this could be replaced with wand.obj.format - print("PDF Detected") + document = WImage(blob=document) + if document.format == 'PDF': with WImage(blob=document) as pdf: + # Get number of pages pages=len(pdf.sequence) + print(f"PDF with {pages} page(s) detected") + # Create new image object where the height will be the number of pages. With huge PDFs this will overflow, break, consume silly memory etc… img = WImage(width=pdf.width, height=pdf.height * pages) + # Cycle through pages and stitch it together to one big file for p in range(pages): image = img.composite(pdf.sequence[p], top=pdf.height * p, left=0) + # Create a png blob image = img.make_blob('png') else: image = document From 2f5dd9928e89fb4adf9c7c1849e003f2e8b9a360 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Sat, 30 Jun 2018 11:38:26 +0800 Subject: [PATCH 028/153] - content was already a wand.obj --- misp_modules/modules/import_mod/ocr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/import_mod/ocr.py b/misp_modules/modules/import_mod/ocr.py index 441adc4..f37ba9b 100755 --- a/misp_modules/modules/import_mod/ocr.py +++ b/misp_modules/modules/import_mod/ocr.py @@ -42,7 +42,7 @@ def handler(q=False): document = base64.b64decode(request["data"]) document = WImage(blob=document) if document.format == 'PDF': - with WImage(blob=document) as pdf: + with document as pdf: # Get number of pages pages=len(pdf.sequence) print(f"PDF with {pages} page(s) detected") From ffce2aa5cc465823ae558953c2b46fc1fe88cef5 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Sat, 30 Jun 2018 11:52:12 +0800 Subject: [PATCH 029/153] - Added logger functionality for debug sessions --- misp_modules/modules/import_mod/ocr.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/import_mod/ocr.py b/misp_modules/modules/import_mod/ocr.py index f37ba9b..fc7acf7 100755 --- a/misp_modules/modules/import_mod/ocr.py +++ b/misp_modules/modules/import_mod/ocr.py @@ -2,6 +2,16 @@ import json import base64 from io import BytesIO +import logging + +log = logging.getLogger('ocr') +log.setLevel(logging.DEBUG) +ch = logging.StreamHandler(sys.stdout) +ch.setLevel(logging.DEBUG) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +ch.setFormatter(formatter) +log.addHandler(ch) + misperrors = {'error': 'Error'} userConfig = {}; @@ -45,14 +55,16 @@ def handler(q=False): with document as pdf: # Get number of pages pages=len(pdf.sequence) - print(f"PDF with {pages} page(s) detected") + log.debug(f"PDF with {pages} page(s) detected") # Create new image object where the height will be the number of pages. With huge PDFs this will overflow, break, consume silly memory etc… img = WImage(width=pdf.width, height=pdf.height * pages) # Cycle through pages and stitch it together to one big file for p in range(pages): + log.debug(f"Stitching page {p}") image = img.composite(pdf.sequence[p], top=pdf.height * p, left=0) # Create a png blob image = img.make_blob('png') + log.debug(f"Final image size is {pdf.width}x{pdf.height*p}") else: image = document From 184065cf741818d9a38a2fb885a77b7b441bb02e Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Sat, 30 Jun 2018 11:58:44 +0800 Subject: [PATCH 030/153] - Forgot to import sys --- misp_modules/modules/import_mod/ocr.py | 1 + 1 file changed, 1 insertion(+) diff --git a/misp_modules/modules/import_mod/ocr.py b/misp_modules/modules/import_mod/ocr.py index fc7acf7..b52722f 100755 --- a/misp_modules/modules/import_mod/ocr.py +++ b/misp_modules/modules/import_mod/ocr.py @@ -1,3 +1,4 @@ +import sys import json import base64 from io import BytesIO From 9f0313a97e0cf4e13cf4af580feddfe64591709f Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Sat, 30 Jun 2018 12:01:21 +0800 Subject: [PATCH 031/153] - Fixed log output --- misp_modules/modules/import_mod/ocr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/import_mod/ocr.py b/misp_modules/modules/import_mod/ocr.py index b52722f..15d660b 100755 --- a/misp_modules/modules/import_mod/ocr.py +++ b/misp_modules/modules/import_mod/ocr.py @@ -61,11 +61,11 @@ def handler(q=False): img = WImage(width=pdf.width, height=pdf.height * pages) # Cycle through pages and stitch it together to one big file for p in range(pages): - log.debug(f"Stitching page {p}") + log.debug(f"Stitching page {p+1}") image = img.composite(pdf.sequence[p], top=pdf.height * p, left=0) # Create a png blob image = img.make_blob('png') - log.debug(f"Final image size is {pdf.width}x{pdf.height*p}") + log.debug(f"Final image size is {pdf.width}x{pdf.height*(p+1)}") else: image = document From 549f32547d474d7d3a33651cf5dc8d9ce8a8720b Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Sun, 1 Jul 2018 22:08:42 +0800 Subject: [PATCH 032/153] - Reverted to <3.6 compatibility --- misp_modules/modules/import_mod/ocr.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/import_mod/ocr.py b/misp_modules/modules/import_mod/ocr.py index 15d660b..f14212b 100755 --- a/misp_modules/modules/import_mod/ocr.py +++ b/misp_modules/modules/import_mod/ocr.py @@ -56,16 +56,16 @@ def handler(q=False): with document as pdf: # Get number of pages pages=len(pdf.sequence) - log.debug(f"PDF with {pages} page(s) detected") + log.debug("PDF with {} page(s) detected".format(pages)) # Create new image object where the height will be the number of pages. With huge PDFs this will overflow, break, consume silly memory etc… img = WImage(width=pdf.width, height=pdf.height * pages) # Cycle through pages and stitch it together to one big file for p in range(pages): - log.debug(f"Stitching page {p+1}") + log.debug("Stitching page {}".format(p+1)) image = img.composite(pdf.sequence[p], top=pdf.height * p, left=0) # Create a png blob image = img.make_blob('png') - log.debug(f"Final image size is {pdf.width}x{pdf.height*(p+1)}") + log.debug("Final image size is {}x{}".format(pdf.width, pdf.height*(p+1))) else: image = document From 08d8459e1a6a41723bcc179bc6cead1b4ec966f1 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 2 Jul 2018 11:38:33 +0200 Subject: [PATCH 033/153] add: STIX2 pattern syntax validator --- README.md | 3 +- misp_modules/modules/expansion/__init__.py | 2 +- .../stix2_pattern_syntax_validator.py | 39 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 misp_modules/modules/expansion/stix2_pattern_syntax_validator.py diff --git a/README.md b/README.md index 14840ea..95019da 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [shodan](misp_modules/modules/expansion/shodan.py) - a minimal [shodan](https://www.shodan.io/) expansion module. * [Sigma syntax validator](misp_modules/modules/expansion/sigma_syntax_validator.py) - Sigma syntax validator. * [sourcecache](misp_modules/modules/expansion/sourcecache.py) - a module to cache a specific link from a MISP instance. +* [STIX2 pattern syntax validator](misp_modules/modules/expansion/stix2_pattern_syntax_validator.py) - a module to check a STIX2 pattern syntax. * [ThreatCrowd](misp_modules/modules/expansion/threatcrowd.py) - an expansion module for [ThreatCrowd](https://www.threatcrowd.org/). * [threatminer](misp_modules/modules/expansion/threatminer.py) - an expansion module to expand from [ThreatMiner](https://www.threatminer.org/). * [virustotal](misp_modules/modules/expansion/virustotal.py) - an expansion module to pull known resolutions and malware samples related with an IP/Domain from virusTotal (this modules require a VirusTotal private API key) @@ -380,7 +381,7 @@ Recommended Plugin.Import_ocr_enabled true Enable or disable the ocr In this same menu set any other plugin settings that are required for testing. ## Install misp-module on an offline instance. -First, you need to grab all necessery packages for example like this : +First, you need to grab all necessary packages for example like this : Use pip wheel to create an archive ~~~ diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index b49c1dc..5e0d65e 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,3 +1,3 @@ from . import _vmray -__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator'] +__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator'] diff --git a/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py b/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py new file mode 100644 index 0000000..92e48c5 --- /dev/null +++ b/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py @@ -0,0 +1,39 @@ +import json +from stix2patterns.validator import run_validator + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['stix2-pattern'], 'output': ['text']} +moduleinfo = {'version': '0.1', 'author': 'Christian Studer', 'module-type': ['expansion', 'hover'], + 'description': 'An expansion hover module to perform a syntax check on stix2 patterns.'} +moduleconfig = [] + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + if not request.get('stix2-pattern'): + misperrors['error'] = 'STIX2 pattern missing' + return misperrors + pattern = request.get('stix2-pattern') + syntax_errors = [] + for p in pattern[2:-2].split(' AND '): + syntax_validator = run_validator("[{}]".format(p)) + if syntax_validator: + for error in syntax_validator: + syntax_errors.append(error) + if syntax_errors: + s = 's' if len(syntax_errors) > 1 else '' + s_errors = "" + for error in syntax_errors: + s_errors += "{}\n".format(error[6:]) + result = "Syntax error{}: \n{}".format(s, s_errors[:-1]) + else: + result = "Syntax valid" + return {'results': [{'types': mispattributes['output'], 'values': result}]} + +def introspection(): + return mispattributes + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 5ff8bad85b58e2adcd0b9a151fbd52d9d1d849ec Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 2 Jul 2018 12:07:21 +0200 Subject: [PATCH 034/153] add: stix2 pattern validator requirements --- REQUIREMENTS | 1 + 1 file changed, 1 insertion(+) diff --git a/REQUIREMENTS b/REQUIREMENTS index 1365ec2..6f68e4b 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -22,3 +22,4 @@ bs4 oauth2 yara sigmatools +stix2-patterns From 90e42c03058c83f40b41734df6cb8c858e58c9c7 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 2 Jul 2018 12:14:21 +0200 Subject: [PATCH 035/153] fix: Put the stix2-pattern library import in a try statement --> Error more easily caught --- .../modules/expansion/stix2_pattern_syntax_validator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py b/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py index 92e48c5..bf5d408 100644 --- a/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py +++ b/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py @@ -1,5 +1,8 @@ import json -from stix2patterns.validator import run_validator +try: + from stix2patterns.validator import run_validator +except ModuleNotFoundError: + print("stix2 patterns python library is missing, use 'pip3 install stix2-patterns' to install it.") misperrors = {'error': 'Error'} mispattributes = {'input': ['stix2-pattern'], 'output': ['text']} From 562a6b13084b68938a23d2dd01886d2aad9fda7b Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Tue, 3 Jul 2018 08:27:54 +0200 Subject: [PATCH 036/153] - Removed test modules from view - Moved skeleton expansion module to it's proper place --- {modules => misp_modules/modules}/expansion/module.py.skeleton | 0 misp_modules/modules/export_mod/__init__.py | 2 +- misp_modules/modules/import_mod/__init__.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename {modules => misp_modules/modules}/expansion/module.py.skeleton (100%) diff --git a/modules/expansion/module.py.skeleton b/misp_modules/modules/expansion/module.py.skeleton similarity index 100% rename from modules/expansion/module.py.skeleton rename to misp_modules/modules/expansion/module.py.skeleton diff --git a/misp_modules/modules/export_mod/__init__.py b/misp_modules/modules/export_mod/__init__.py index 0034f5d..7c8d18b 100644 --- a/misp_modules/modules/export_mod/__init__.py +++ b/misp_modules/modules/export_mod/__init__.py @@ -1 +1 @@ -__all__ = ['testexport','cef_export','liteexport','goamlexport','threat_connect_export','pdfexport','threatStream_misp_export'] +__all__ = ['cef_export','liteexport','goamlexport','threat_connect_export','pdfexport','threatStream_misp_export'] diff --git a/misp_modules/modules/import_mod/__init__.py b/misp_modules/modules/import_mod/__init__.py index 0a732e2..5190abb 100644 --- a/misp_modules/modules/import_mod/__init__.py +++ b/misp_modules/modules/import_mod/__init__.py @@ -1,3 +1,3 @@ from . import _vmray -__all__ = ['vmray_import', 'testimport', 'ocr', 'cuckooimport', 'goamlimport', 'email_import', 'mispjson', 'openiocimport', 'threatanalyzer_import', 'csvimport'] +__all__ = ['vmray_import', 'ocr', 'cuckooimport', 'goamlimport', 'email_import', 'mispjson', 'openiocimport', 'threatanalyzer_import', 'csvimport'] From 2a8fb76e8483ee4df174b94275b0b3c883583aaa Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 14:56:20 +0200 Subject: [PATCH 037/153] add logs --- misp_modules/modules/expansion/dnstrails.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index d1c276a..cb00262 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -236,6 +236,9 @@ def expand_whois(api, domain): } ) + # TODO File "modules/expansion/dnstrails.py", line 230, in expand_whois + # 'values': [item_registrant['email'], + # TypeError: 'NoneType' object is not subscriptable except APIError as e: misperrors['error'] = e @@ -254,5 +257,6 @@ def version(): def __select_registrant_item(entry): if 'contacts' in entry: for c in entry['contacts']: + print(c) if c['type'] == 'registrant': return entry From f710162beda5cee2005cff6df50d30a09c1af25d Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 14:59:39 +0200 Subject: [PATCH 038/153] change errors --- misp_modules/modules/expansion/dnstrails.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index cb00262..bf16601 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -83,7 +83,7 @@ def handle_domain(api, domain, misperrors): if status_ok: result_filtered['results'].extend(r) else: - misperrors['error'] = 'Error dns result' + misperrors['error'] = 'Error subdomains result' return misperrors r, status_ok = expand_whois(api, domain) @@ -91,7 +91,7 @@ def handle_domain(api, domain, misperrors): if status_ok: result_filtered['results'].extend(r) else: - misperrors['error'] = 'Error dns result' + misperrors['error'] = 'Error whois result' return misperrors return result_filtered From e1a1648f14c1834892af9b036be494bf251b2b18 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 15:01:04 +0200 Subject: [PATCH 039/153] add logs --- misp_modules/modules/expansion/dnstrails.py | 1 + 1 file changed, 1 insertion(+) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index bf16601..9787a15 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -242,6 +242,7 @@ def expand_whois(api, domain): except APIError as e: misperrors['error'] = e + print(e) return r, status_ok From 714c15f079c91b82c796239238859501c84f3ea8 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 15:05:10 +0200 Subject: [PATCH 040/153] change return value --- misp_modules/modules/expansion/dnstrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 9787a15..5f2cc63 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -260,4 +260,4 @@ def __select_registrant_item(entry): for c in entry['contacts']: print(c) if c['type'] == 'registrant': - return entry + return c From 1223d93d52faea1105e8bd289691e33bc12e31a8 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 15:07:54 +0200 Subject: [PATCH 041/153] change name keys --- misp_modules/modules/expansion/dnstrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 5f2cc63..5a0cfcc 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -230,7 +230,7 @@ def expand_whois(api, domain): 'values': [item_registrant['email'], item_registrant['telephone'], item_registrant['name'], results['registrarName'], - results['creationDate']], + results['createdDate']], 'categories': ['attribution'], 'comment': 'whois information of %s by securitytrails' % domain } From 1d100833a4cade1d942e91c9f176ef0aea6aded7 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 15:12:27 +0200 Subject: [PATCH 042/153] concat results --- misp_modules/modules/expansion/dnstrails.py | 29 +++++++++++---------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 5a0cfcc..6f52dff 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -222,27 +222,28 @@ def expand_whois(api, domain): if results: status_ok = True item_registrant = __select_registrant_item(results) - - r.append({ - 'types': ['whois-registrant-email', 'whois-registrant-phone', + types = ['whois-registrant-email', 'whois-registrant-phone', 'whois-registrant-name', 'whois-registrar', - 'whois-creation-date'], - 'values': [item_registrant['email'], - item_registrant['telephone'], - item_registrant['name'], results['registrarName'], - results['createdDate']], - 'categories': ['attribution'], - 'comment': 'whois information of %s by securitytrails' % domain - } + 'whois-creation-date'] + values = [item_registrant['email'], + item_registrant['telephone'], + item_registrant['name'], results['registrarName'], + results['createdDate']] + for t, v in zip(types, values): + r.append({ + 'types': t, + 'values': v, + 'categories': ['attribution'], + 'comment': 'whois information of %s by securitytrails' % domain + } - ) + ) # TODO File "modules/expansion/dnstrails.py", line 230, in expand_whois # 'values': [item_registrant['email'], # TypeError: 'NoneType' object is not subscriptable except APIError as e: misperrors['error'] = e - print(e) return r, status_ok @@ -258,6 +259,6 @@ def version(): def __select_registrant_item(entry): if 'contacts' in entry: for c in entry['contacts']: - print(c) + if c['type'] == 'registrant': return c From b677cd5fc7ef1a69b938ff8020e15c42142b5dfd Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 15:16:02 +0200 Subject: [PATCH 043/153] change categories --- misp_modules/modules/expansion/dnstrails.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 6f52dff..8d17fa5 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -229,15 +229,14 @@ def expand_whois(api, domain): item_registrant['telephone'], item_registrant['name'], results['registrarName'], results['createdDate']] - for t, v in zip(types, values): - r.append({ + + r = [{ 'types': t, 'values': v, - 'categories': ['attribution'], + 'categories': ['Attribution'], 'comment': 'whois information of %s by securitytrails' % domain - } + } for t, v in zip(types, values)] - ) # TODO File "modules/expansion/dnstrails.py", line 230, in expand_whois # 'values': [item_registrant['email'], # TypeError: 'NoneType' object is not subscriptable From 21794249d0339b5910e6db09292119b51066b488 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 15:17:37 +0200 Subject: [PATCH 044/153] add logs --- misp_modules/modules/expansion/dnstrails.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 8d17fa5..8ebecda 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -209,6 +209,7 @@ def expand_subdomains(api, domain): ) except APIError as e: misperrors['error'] = e + return r, status_ok @@ -243,6 +244,7 @@ def expand_whois(api, domain): except APIError as e: misperrors['error'] = e + print(e) return r, status_ok From 495c720d0fecb6552757d026dc175598814c5e4b Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 16:31:39 +0200 Subject: [PATCH 045/153] add history ipv4 --- misp_modules/modules/expansion/dnstrails.py | 75 ++++++++++++++++----- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 8ebecda..e0b78fa 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -9,7 +9,8 @@ log = logging.getLogger('dnstrails') log.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.DEBUG) -formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') ch.setFormatter(formatter) log.addHandler(ch) @@ -94,13 +95,22 @@ def handle_domain(api, domain, misperrors): misperrors['error'] = 'Error whois result' return misperrors + r, status_ok = expand_history_ipv4(api, domain) + + if status_ok: + result_filtered['results'].extend(r) + else: + misperrors['error'] = 'Error history ipv4' + return misperrors + return result_filtered + def handle_ip(api, ip, misperrors): pass -def expand_domain_info(api, misperror,domain): +def expand_domain_info(api, misperror, domain): r = [] status_ok = False ns_servers = [] @@ -130,31 +140,33 @@ def expand_domain_info(api, misperror,domain): if 'values' in results['current_dns']['mx']: servers_mx = [mx_entry['hostname'] for mx_entry in - results['current_dns']['mx']['values'] if - 'hostname' in mx_entry] + results['current_dns']['mx']['values'] if + 'hostname' in mx_entry] if 'values' in results['current_dns']['soa']: soa_hostnames = [soa_entry['email'] for soa_entry in - results['current_dns']['soa']['values'] if - 'email' in soa_entry] + results['current_dns']['soa']['values'] if + 'email' in soa_entry] if ns_servers: r.append({'types': ['domain'], 'values': ns_servers, 'categories': ['Network activity'], 'comment': 'List of name servers of %s first seen %s ' % - (domain, results['current_dns']['ns']['first_seen']) - }) + (domain, + results['current_dns']['ns']['first_seen']) + }) if list_ipv4: r.append({'types': ['domain|ip'], - 'values': ['%s|%s' % (domain, ipv4) for ipv4 in list_ipv4], + 'values': ['%s|%s' % (domain, ipv4) for ipv4 in + list_ipv4], 'categories': ['Network activity'], 'comment': ' List ipv4 of %s first seen %s' % (domain, results['current_dns']['a']['first_seen']) - }) + }) if list_ipv6: r.append({'types': ['domain|ip'], 'values': ['%s|%s' % (domain, ipv6) for ipv6 in @@ -188,11 +200,9 @@ def expand_domain_info(api, misperror,domain): def expand_subdomains(api, domain): - r = [] status_ok = False - try: results = api.subdomains(domain) @@ -201,7 +211,8 @@ def expand_subdomains(api, domain): if 'subdomains' in results: r.append({ 'types': ['domain'], - 'values': ['%s.%s' % (sub,domain) for sub in results['subdomains']], + 'values': ['%s.%s' % (sub, domain) + for sub in results['subdomains']], 'categories': ['Network activity'], 'comment': 'subdomains of %s' % domain } @@ -224,7 +235,7 @@ def expand_whois(api, domain): status_ok = True item_registrant = __select_registrant_item(results) types = ['whois-registrant-email', 'whois-registrant-phone', - 'whois-registrant-name', 'whois-registrar', + 'whois-registrant-name', 'whois-registrar', 'whois-creation-date'] values = [item_registrant['email'], item_registrant['telephone'], @@ -232,10 +243,10 @@ def expand_whois(api, domain): results['createdDate']] r = [{ - 'types': t, - 'values': v, + 'types': t, + 'values': v, 'categories': ['Attribution'], - 'comment': 'whois information of %s by securitytrails' % domain + 'comment': 'whois information of %s by securitytrails' % domain } for t, v in zip(types, values)] # TODO File "modules/expansion/dnstrails.py", line 230, in expand_whois @@ -248,6 +259,36 @@ def expand_whois(api, domain): return r, status_ok + +def expand_history_ipv4(api, domain): + r = [] + status_ok = False + + try: + results = api.history_dns_ipv4(domain) + + if results: + status_ok = True + if 'records' in results: + for record in results['records']: + if 'values' in record: + r.append( + {'type': ['domain|ip'], + 'values': ['%s|%s' % (domain, record['ip'])], + 'categories': ['Newtwork activity'], + 'comment': 'last seen: %s first seen: %s' % + (record['last_seen'], + record['first_seen']) + } + ) + + except APIError as e: + misperrors['error'] = e + print(e) + + return r, status_ok + + def introspection(): return mispattributes From 602da3d1a3baa52005678025b2a320d87ada5286 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 16:35:01 +0200 Subject: [PATCH 046/153] control return of records --- misp_modules/modules/expansion/dnstrails.py | 27 +++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index e0b78fa..d8d9a53 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -234,20 +234,21 @@ def expand_whois(api, domain): if results: status_ok = True item_registrant = __select_registrant_item(results) - types = ['whois-registrant-email', 'whois-registrant-phone', - 'whois-registrant-name', 'whois-registrar', - 'whois-creation-date'] - values = [item_registrant['email'], - item_registrant['telephone'], - item_registrant['name'], results['registrarName'], - results['createdDate']] + if item_registrant: + types = ['whois-registrant-email', 'whois-registrant-phone', + 'whois-registrant-name', 'whois-registrar', + 'whois-creation-date'] + values = [item_registrant['email'], + item_registrant['telephone'], + item_registrant['name'], results['registrarName'], + results['createdDate']] - r = [{ - 'types': t, - 'values': v, - 'categories': ['Attribution'], - 'comment': 'whois information of %s by securitytrails' % domain - } for t, v in zip(types, values)] + r = [{ + 'types': t, + 'values': v, + 'categories': ['Attribution'], + 'comment': 'whois information of %s by securitytrails' % domain + } for t, v in zip(types, values)] # TODO File "modules/expansion/dnstrails.py", line 230, in expand_whois # 'values': [item_registrant['email'], From e9747a3379dbaaf225eec6411b8d2ca9fa7ad936 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 16:41:44 +0200 Subject: [PATCH 047/153] add time sleep in each request --- misp_modules/modules/expansion/dnstrails.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index d8d9a53..b352bd7 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -1,6 +1,7 @@ import json import logging import sys +import time from dnstrails import APIError from dnstrails import DnsTrails @@ -79,6 +80,7 @@ def handle_domain(api, domain, misperrors): misperrors['error'] = 'Error dns result' return misperrors + time.sleep(1) r, status_ok = expand_subdomains(api, domain) if status_ok: @@ -87,6 +89,7 @@ def handle_domain(api, domain, misperrors): misperrors['error'] = 'Error subdomains result' return misperrors + time.sleep(1) r, status_ok = expand_whois(api, domain) if status_ok: @@ -95,6 +98,7 @@ def handle_domain(api, domain, misperrors): misperrors['error'] = 'Error whois result' return misperrors + time.sleep(1) r, status_ok = expand_history_ipv4(api, domain) if status_ok: From 26950ea7de576906ae4ab5ae38b3f1cf4874d23e Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 16:51:31 +0200 Subject: [PATCH 048/153] change loop --- misp_modules/modules/expansion/dnstrails.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index b352bd7..f950500 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -277,15 +277,16 @@ def expand_history_ipv4(api, domain): if 'records' in results: for record in results['records']: if 'values' in record: - r.append( - {'type': ['domain|ip'], - 'values': ['%s|%s' % (domain, record['ip'])], - 'categories': ['Newtwork activity'], - 'comment': 'last seen: %s first seen: %s' % - (record['last_seen'], - record['first_seen']) - } - ) + for item in record['values']: + r.append( + {'type': ['domain|ip'], + 'values': ['%s|%s' % (domain, item['ip'])], + 'categories': ['Newtwork activity'], + 'comment': 'last seen: %s first seen: %s' % + (record['last_seen'], + record['first_seen']) + } + ) except APIError as e: misperrors['error'] = e From 9e6162a4349a14eb66cc83ad57eadb5f6ee456d9 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 16:53:06 +0200 Subject: [PATCH 049/153] change type --- misp_modules/modules/expansion/dnstrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index f950500..c30c747 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -281,7 +281,7 @@ def expand_history_ipv4(api, domain): r.append( {'type': ['domain|ip'], 'values': ['%s|%s' % (domain, item['ip'])], - 'categories': ['Newtwork activity'], + 'categories': ['Network activity'], 'comment': 'last seen: %s first seen: %s' % (record['last_seen'], record['first_seen']) From f2333a4978e016e2a870e3cf1c7b1e8fcf0e25e0 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 10 Jul 2018 16:55:13 +0200 Subject: [PATCH 050/153] change type --- misp_modules/modules/expansion/dnstrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index c30c747..a176489 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -279,7 +279,7 @@ def expand_history_ipv4(api, domain): if 'values' in record: for item in record['values']: r.append( - {'type': ['domain|ip'], + {'types': ['domain|ip'], 'values': ['%s|%s' % (domain, item['ip'])], 'categories': ['Network activity'], 'comment': 'last seen: %s first seen: %s' % From 3a96e189ed5f45780056226566da0fcdef0ab096 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 08:43:23 +0200 Subject: [PATCH 051/153] add ipv6 and ipv4 --- misp_modules/modules/expansion/dnstrails.py | 41 ++++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index a176489..02e1346 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -265,7 +265,7 @@ def expand_whois(api, domain): return r, status_ok -def expand_history_ipv4(api, domain): +def expand_history_ipv4_ipv6(api, domain): r = [] status_ok = False @@ -274,19 +274,14 @@ def expand_history_ipv4(api, domain): if results: status_ok = True - if 'records' in results: - for record in results['records']: - if 'values' in record: - for item in record['values']: - r.append( - {'types': ['domain|ip'], - 'values': ['%s|%s' % (domain, item['ip'])], - 'categories': ['Network activity'], - 'comment': 'last seen: %s first seen: %s' % - (record['last_seen'], - record['first_seen']) - } - ) + r.extend(__history_ip(results, domain)) + + time.sleep(1) + results = api.history_dns_aaaa(domain) + + if results: + status_ok = True + r.extend(__history_ip(results, domain)) except APIError as e: misperrors['error'] = e @@ -295,6 +290,24 @@ def expand_history_ipv4(api, domain): return r, status_ok +def __history_ip(results, domain): + r = [] + if 'records' in results: + for record in results['records']: + if 'values' in record: + for item in record['values']: + r.append( + {'types': ['domain|ip'], + 'values': ['%s|%s' % (domain, item['ip'])], + 'categories': ['Network activity'], + 'comment': 'last seen: %s first seen: %s' % + (record['last_seen'], + record['first_seen']) + } + ) + + return r + def introspection(): return mispattributes From 41635d43c772f15bb693260504b67df2eb88727a Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 08:49:59 +0200 Subject: [PATCH 052/153] correct typo --- misp_modules/modules/expansion/dnstrails.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 02e1346..c601481 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -99,7 +99,7 @@ def handle_domain(api, domain, misperrors): return misperrors time.sleep(1) - r, status_ok = expand_history_ipv4(api, domain) + r, status_ok = expand_history_ipv4_ipv6(api, domain) if status_ok: result_filtered['results'].extend(r) @@ -139,7 +139,7 @@ def expand_domain_info(api, misperror, domain): if 'values' in results['current_dns']['aaaa']: list_ipv6 = [ipv6_entry['ipv6'] for ipv6_entry in - results['current_ns']['aaaa']['values'] if + results['current_dns']['aaaa']['values'] if 'ipv6' in ipv6_entry] if 'values' in results['current_dns']['mx']: From 42c362d2fd6a629184b8887ff7876b4f7a8f52de Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 09:00:23 +0200 Subject: [PATCH 053/153] refactoring expand_whois --- misp_modules/modules/expansion/dnstrails.py | 68 +++++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index c601481..7d18042 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -239,20 +239,62 @@ def expand_whois(api, domain): status_ok = True item_registrant = __select_registrant_item(results) if item_registrant: - types = ['whois-registrant-email', 'whois-registrant-phone', - 'whois-registrant-name', 'whois-registrar', - 'whois-creation-date'] - values = [item_registrant['email'], - item_registrant['telephone'], - item_registrant['name'], results['registrarName'], - results['createdDate']] - r = [{ - 'types': t, - 'values': v, - 'categories': ['Attribution'], - 'comment': 'whois information of %s by securitytrails' % domain - } for t, v in zip(types, values)] + if 'email' in item_registrant: + r.append( + { + 'types': ['whois-registrant-email'], + 'values': [item_registrant['email']], + 'categories': ['Attribution'], + 'comment': 'Whois information of %s by securitytrails' + % domain + } + ) + + if 'telephone' in item_registrant: + r.append( + { + 'types': ['whois-registrant-phone'], + 'values': [item_registrant['telephone']], + 'categories': ['Attribution'], + 'comment': 'Whois information of %s by securitytrails' + % domain + } + ) + + if 'name' in item_registrant: + r.append( + { + 'types': ['whois-registrant-name'], + 'values': [item_registrant['name']], + 'categories': ['Attribution'], + 'comment': 'Whois information of %s by securitytrails' + % domain + } + ) + + if 'registrarName' in item_registrant: + r.append( + { + 'types': ['whois-registrar'], + 'values': [item_registrant['registrarName']], + 'categories': ['Attribution'], + 'comment': 'Whois information of %s by securitytrails' + % domain + } + ) + + if 'createdDate' in item_registrant: + r.append( + { + 'types': ['whois-creation-date'], + 'values': [item_registrant['createdDate']], + 'categories': ['Attribution'], + 'comment': 'Whois information of %s by securitytrails' + % domain + } + ) + # TODO File "modules/expansion/dnstrails.py", line 230, in expand_whois # 'values': [item_registrant['email'], From dcdb6e589556a09a562f78a9ceb6684078eef0b0 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 09:02:47 +0200 Subject: [PATCH 054/153] switch type ip --- misp_modules/modules/expansion/dnstrails.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 7d18042..ee9f1ad 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -323,7 +323,7 @@ def expand_history_ipv4_ipv6(api, domain): if results: status_ok = True - r.extend(__history_ip(results, domain)) + r.extend(__history_ip(results, domain, type_ip='ipv6')) except APIError as e: misperrors['error'] = e @@ -332,7 +332,7 @@ def expand_history_ipv4_ipv6(api, domain): return r, status_ok -def __history_ip(results, domain): +def __history_ip(results, domain, type_ip='ip'): r = [] if 'records' in results: for record in results['records']: @@ -340,7 +340,7 @@ def __history_ip(results, domain): for item in record['values']: r.append( {'types': ['domain|ip'], - 'values': ['%s|%s' % (domain, item['ip'])], + 'values': ['%s|%s' % (domain, item[type_ip])], 'categories': ['Network activity'], 'comment': 'last seen: %s first seen: %s' % (record['last_seen'], From 54d996cb00b47046558c5ad30c12bda5066fe9aa Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 09:39:09 +0200 Subject: [PATCH 055/153] add history dns --- misp_modules/modules/expansion/dnstrails.py | 42 +++++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index ee9f1ad..24e252b 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -295,11 +295,6 @@ def expand_whois(api, domain): } ) - - # TODO File "modules/expansion/dnstrails.py", line 230, in expand_whois - # 'values': [item_registrant['email'], - # TypeError: 'NoneType' object is not subscriptable - except APIError as e: misperrors['error'] = e print(e) @@ -332,6 +327,37 @@ def expand_history_ipv4_ipv6(api, domain): return r, status_ok +def expand_history_dns(api, domain): + r = [] + status_ok = False + + try: + + results = api.history_dns_ns(domain) + if results: + status_ok = True + + if 'records' in results: + for record in results['records']: + if 'values' in record: + for item in record['values']: + r.append( + {'types': ['domain|ip'], + 'values': [ + '%s|%s' % (domain, item['nameserver'])], + 'categories': ['Network activity'], + 'comment': 'history DNS of %s last seen: %s first seen: %s' % + (domain, record['last_seen'], + record['first_seen']) + } + ) + + except APIError as e: + misperrors['error'] = e + + return r, status_ok + + def __history_ip(results, domain, type_ip='ip'): r = [] if 'records' in results: @@ -342,14 +368,16 @@ def __history_ip(results, domain, type_ip='ip'): {'types': ['domain|ip'], 'values': ['%s|%s' % (domain, item[type_ip])], 'categories': ['Network activity'], - 'comment': 'last seen: %s first seen: %s' % - (record['last_seen'], + 'comment': 'History IP on securitytrails %s ' + 'last seen: %s first seen: %s' % + (domain, record['last_seen'], record['first_seen']) } ) return r + def introspection(): return mispattributes From 43a49dafc6d993b7a9142e7f16993eb209c5038a Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 09:48:14 +0200 Subject: [PATCH 056/153] add history dns and handler exception --- misp_modules/modules/expansion/dnstrails.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 24e252b..89e45ec 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -77,7 +77,7 @@ def handle_domain(api, domain, misperrors): if status_ok: result_filtered['results'].extend(r) else: - misperrors['error'] = 'Error dns result' + misperrors['error'] = misperrors['error'] + ' Error DNS result' return misperrors time.sleep(1) @@ -86,7 +86,7 @@ def handle_domain(api, domain, misperrors): if status_ok: result_filtered['results'].extend(r) else: - misperrors['error'] = 'Error subdomains result' + misperrors['error'] = misperrors['error'] + ' Error subdomains result' return misperrors time.sleep(1) @@ -95,7 +95,7 @@ def handle_domain(api, domain, misperrors): if status_ok: result_filtered['results'].extend(r) else: - misperrors['error'] = 'Error whois result' + misperrors['error'] = misperrors['error'] + ' Error whois result' return misperrors time.sleep(1) @@ -104,9 +104,18 @@ def handle_domain(api, domain, misperrors): if status_ok: result_filtered['results'].extend(r) else: - misperrors['error'] = 'Error history ipv4' + misperrors['error'] = misperrors['error'] + ' Error history ipv4' return misperrors + time.sleep(1) + r, status_ok = expand_history_dns(api, domain) + + if status_ok: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors[ + 'error'] + ' Error in expand History DNS' + return misperrors return result_filtered From f47a64b3647fde0cf70d582dac831beada066779 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 11:24:49 +0200 Subject: [PATCH 057/153] add history mx and soa --- misp_modules/modules/expansion/dnstrails.py | 49 +++++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 89e45ec..0e79ad0 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -346,20 +346,23 @@ def expand_history_dns(api, domain): if results: status_ok = True - if 'records' in results: - for record in results['records']: - if 'values' in record: - for item in record['values']: - r.append( - {'types': ['domain|ip'], - 'values': [ - '%s|%s' % (domain, item['nameserver'])], - 'categories': ['Network activity'], - 'comment': 'history DNS of %s last seen: %s first seen: %s' % - (domain, record['last_seen'], - record['first_seen']) - } - ) + r.extend(__history_dns(results, domain, 'nameserver', 'ns')) + + time.sleep(1) + + results = api.history_dns_soa(results, domain) + + if results: + status_ok = True + r.extend(__history_dns(results, domain, 'email', 'soa')) + + time.sleep(1) + + results = api.history_dns_mx(domain) + + if results: + status_ok = True + r.extend(__history_dns(results, domain, 'host', 'mx')) except APIError as e: misperrors['error'] = e @@ -387,6 +390,24 @@ def __history_ip(results, domain, type_ip='ip'): return r +def __history_dns(results, domain, type_serv, service): + r = [] + + if 'records' in results: + for record in results['records']: + if 'values' in record: + for item in record['values']: + r.append( + {'types': ['domain|ip'], + 'values': [item[type_serv]], + 'categories': ['Network activity'], + 'comment': 'history %s of %s last seen: %s first seen: %s' % + (service, domain, record['last_seen'], + record['first_seen']) + } + ) + return r + def introspection(): return mispattributes From 74c611d2fb38c084084c0aca2a59cfb5189b5379 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 11:37:07 +0200 Subject: [PATCH 058/153] correct call function --- misp_modules/modules/expansion/dnstrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 0e79ad0..86a01a7 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -350,7 +350,7 @@ def expand_history_dns(api, domain): time.sleep(1) - results = api.history_dns_soa(results, domain) + results = api.history_dns_soa(domain) if results: status_ok = True From 560dacbf7e7491ac5bd75d62f91aa1d3d1aacf3f Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 11:40:22 +0200 Subject: [PATCH 059/153] add logs to debug --- misp_modules/modules/expansion/dnstrails.py | 1 + 1 file changed, 1 insertion(+) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 86a01a7..1a3f720 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -397,6 +397,7 @@ def __history_dns(results, domain, type_serv, service): for record in results['records']: if 'values' in record: for item in record['values']: + print(item) r.append( {'types': ['domain|ip'], 'values': [item[type_serv]], From 64e7f9c8b628115339d848b1ed57918167dc7a4b Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 11:47:10 +0200 Subject: [PATCH 060/153] change history dns --- misp_modules/modules/expansion/dnstrails.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 1a3f720..3343fa4 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -396,11 +396,23 @@ def __history_dns(results, domain, type_serv, service): if 'records' in results: for record in results['records']: if 'values' in record: - for item in record['values']: - print(item) + values = record['values'] + if type(values) is list: + + for item in record['values']: + r.append( + {'types': ['domain|ip'], + 'values': [item[type_serv]], + 'categories': ['Network activity'], + 'comment': 'history %s of %s last seen: %s first seen: %s' % + (service, domain, record['last_seen'], + record['first_seen']) + } + ) + else: r.append( {'types': ['domain|ip'], - 'values': [item[type_serv]], + 'values': [values[type_serv]], 'categories': ['Network activity'], 'comment': 'history %s of %s last seen: %s first seen: %s' % (service, domain, record['last_seen'], From 45c473aef504562b6bea2b37d8f671f827338ddf Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 11:52:10 +0200 Subject: [PATCH 061/153] change status --- misp_modules/modules/expansion/dnstrails.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 3343fa4..cab4778 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -344,7 +344,7 @@ def expand_history_dns(api, domain): results = api.history_dns_ns(domain) if results: - status_ok = True + r.extend(__history_dns(results, domain, 'nameserver', 'ns')) @@ -353,7 +353,6 @@ def expand_history_dns(api, domain): results = api.history_dns_soa(domain) if results: - status_ok = True r.extend(__history_dns(results, domain, 'email', 'soa')) time.sleep(1) @@ -367,6 +366,8 @@ def expand_history_dns(api, domain): except APIError as e: misperrors['error'] = e + status_ok = True + return r, status_ok From 45decc728dcd0391f0b1dfad08684e9844757b7f Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 11:55:31 +0200 Subject: [PATCH 062/153] debug --- misp_modules/modules/expansion/dnstrails.py | 71 +++++++++++---------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index cab4778..5ff9e25 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -72,42 +72,43 @@ def handler(q=False): def handle_domain(api, domain, misperrors): result_filtered = {"results": []} - r, status_ok = expand_domain_info(api, misperrors, domain) + # r, status_ok = expand_domain_info(api, misperrors, domain) + # + # if status_ok: + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors['error'] + ' Error DNS result' + # return misperrors + # + # time.sleep(1) + # r, status_ok = expand_subdomains(api, domain) + # + # if status_ok: + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors['error'] + ' Error subdomains result' + # return misperrors + # + # time.sleep(1) + # r, status_ok = expand_whois(api, domain) + # + # if status_ok: + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors['error'] + ' Error whois result' + # return misperrors + # + # time.sleep(1) + # r, status_ok = expand_history_ipv4_ipv6(api, domain) + # + # if status_ok: + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors['error'] + ' Error history ipv4' + # return misperrors + # + # time.sleep(1) - if status_ok: - result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors['error'] + ' Error DNS result' - return misperrors - - time.sleep(1) - r, status_ok = expand_subdomains(api, domain) - - if status_ok: - result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors['error'] + ' Error subdomains result' - return misperrors - - time.sleep(1) - r, status_ok = expand_whois(api, domain) - - if status_ok: - result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors['error'] + ' Error whois result' - return misperrors - - time.sleep(1) - r, status_ok = expand_history_ipv4_ipv6(api, domain) - - if status_ok: - result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors['error'] + ' Error history ipv4' - return misperrors - - time.sleep(1) r, status_ok = expand_history_dns(api, domain) if status_ok: From 386d38c88f7bacda2f905a8ff6f4a60c861074d1 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 11:55:59 +0200 Subject: [PATCH 063/153] add debug --- misp_modules/modules/expansion/dnstrails.py | 1 + 1 file changed, 1 insertion(+) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 5ff9e25..b97997a 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -112,6 +112,7 @@ def handle_domain(api, domain, misperrors): r, status_ok = expand_history_dns(api, domain) if status_ok: + print(r) result_filtered['results'].extend(r) else: misperrors['error'] = misperrors[ From 80e71f582c901bd866cc321f2cbe3f6bcfdd0935 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 11:58:42 +0200 Subject: [PATCH 064/153] debug ipv4 or ipv6 --- misp_modules/modules/expansion/dnstrails.py | 26 +++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index b97997a..9e3a80f 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -99,25 +99,27 @@ def handle_domain(api, domain, misperrors): # return misperrors # # time.sleep(1) - # r, status_ok = expand_history_ipv4_ipv6(api, domain) + r, status_ok = expand_history_ipv4_ipv6(api, domain) # - # if status_ok: - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors['error'] + ' Error history ipv4' - # return misperrors - # - # time.sleep(1) - - r, status_ok = expand_history_dns(api, domain) if status_ok: print(r) result_filtered['results'].extend(r) else: - misperrors['error'] = misperrors[ - 'error'] + ' Error in expand History DNS' + misperrors['error'] = misperrors['error'] + ' Error history ipv4' return misperrors + + # time.sleep(1) + + # r, status_ok = expand_history_dns(api, domain) + # + # if status_ok: + # print(r) + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors[ + # 'error'] + ' Error in expand History DNS' + # return misperrors return result_filtered From fb262b451fe970a6e1b86fd191c8db2d459dd450 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 12:00:59 +0200 Subject: [PATCH 065/153] debug whois --- misp_modules/modules/expansion/dnstrails.py | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 9e3a80f..5ceb3e7 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -90,24 +90,24 @@ def handle_domain(api, domain, misperrors): # return misperrors # # time.sleep(1) - # r, status_ok = expand_whois(api, domain) - # - # if status_ok: - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors['error'] + ' Error whois result' - # return misperrors - # - # time.sleep(1) - r, status_ok = expand_history_ipv4_ipv6(api, domain) - # + r, status_ok = expand_whois(api, domain) if status_ok: - print(r) result_filtered['results'].extend(r) else: - misperrors['error'] = misperrors['error'] + ' Error history ipv4' + misperrors['error'] = misperrors['error'] + ' Error whois result' return misperrors + # + # time.sleep(1) + # r, status_ok = expand_history_ipv4_ipv6(api, domain) + # # + # + # if status_ok: + # print(r) + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors['error'] + ' Error history ipv4' + # return misperrors # time.sleep(1) From dbeec4682e24482d319097d647cc20cddca9f320 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 12:02:31 +0200 Subject: [PATCH 066/153] add logs --- misp_modules/modules/expansion/dnstrails.py | 1 + 1 file changed, 1 insertion(+) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index 5ceb3e7..d1e6eaa 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -93,6 +93,7 @@ def handle_domain(api, domain, misperrors): r, status_ok = expand_whois(api, domain) if status_ok: + print(r) result_filtered['results'].extend(r) else: misperrors['error'] = misperrors['error'] + ' Error whois result' From f0a4c7190889192ca5723443218a248a758ab9be Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 12:08:01 +0200 Subject: [PATCH 067/153] add a test to check if the list is not empty --- misp_modules/modules/expansion/dnstrails.py | 82 +++++++++++---------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index d1e6eaa..eab1ae7 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -72,55 +72,57 @@ def handler(q=False): def handle_domain(api, domain, misperrors): result_filtered = {"results": []} - # r, status_ok = expand_domain_info(api, misperrors, domain) + r, status_ok = expand_domain_info(api, misperrors, domain) # - # if status_ok: - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors['error'] + ' Error DNS result' - # return misperrors - # - # time.sleep(1) - # r, status_ok = expand_subdomains(api, domain) - # - # if status_ok: - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors['error'] + ' Error subdomains result' - # return misperrors - # - # time.sleep(1) + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors['error'] + ' Error DNS result' + return misperrors + + time.sleep(1) + r, status_ok = expand_subdomains(api, domain) + + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors['error'] + ' Error subdomains result' + return misperrors + + time.sleep(1) r, status_ok = expand_whois(api, domain) if status_ok: - print(r) - result_filtered['results'].extend(r) + if r: + result_filtered['results'].extend(r) else: misperrors['error'] = misperrors['error'] + ' Error whois result' return misperrors - # - # time.sleep(1) - # r, status_ok = expand_history_ipv4_ipv6(api, domain) - # # - # - # if status_ok: - # print(r) - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors['error'] + ' Error history ipv4' - # return misperrors - # time.sleep(1) - - # r, status_ok = expand_history_dns(api, domain) + time.sleep(1) + r, status_ok = expand_history_ipv4_ipv6(api, domain) # - # if status_ok: - # print(r) - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors[ - # 'error'] + ' Error in expand History DNS' - # return misperrors + + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors['error'] + ' Error history ipv4' + return misperrors + + time.sleep(1) + + r, status_ok = expand_history_dns(api, domain) + + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors[ + 'error'] + ' Error in expand History DNS' + return misperrors return result_filtered From a8ae6e06e9d23b835c724fa6099290fb09415534 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 12:09:34 +0200 Subject: [PATCH 068/153] add a test to check if the list is not empty --- misp_modules/modules/expansion/dnstrails.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/dnstrails.py index eab1ae7..5e85db5 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/dnstrails.py @@ -123,6 +123,8 @@ def handle_domain(api, domain, misperrors): misperrors['error'] = misperrors[ 'error'] + ' Error in expand History DNS' return misperrors + print(result_filtered) + print(misperrors) return result_filtered From 3a2aab6d7150f2ece96aad73b4b929996bdd8fa7 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 12:41:54 +0200 Subject: [PATCH 069/153] rename misp modules --- .../modules/expansion/{dnstrails.py => securitytrails.py} | 2 -- 1 file changed, 2 deletions(-) rename misp_modules/modules/expansion/{dnstrails.py => securitytrails.py} (99%) diff --git a/misp_modules/modules/expansion/dnstrails.py b/misp_modules/modules/expansion/securitytrails.py similarity index 99% rename from misp_modules/modules/expansion/dnstrails.py rename to misp_modules/modules/expansion/securitytrails.py index 5e85db5..6940907 100644 --- a/misp_modules/modules/expansion/dnstrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -353,8 +353,6 @@ def expand_history_dns(api, domain): results = api.history_dns_ns(domain) if results: - - r.extend(__history_dns(results, domain, 'nameserver', 'ns')) time.sleep(1) From 51067039daf8255e2a874d1bae7bcda43107b369 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 11 Jul 2018 13:03:47 +0200 Subject: [PATCH 070/153] correct typo --- misp_modules/modules/expansion/securitytrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 6940907..e97b493 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -26,7 +26,7 @@ mispattributes = { moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', 'description': 'Query on securitytrails.com', - 'module-types': ['expansion', 'hover']} + 'module-type': ['expansion', 'hover']} # config fields that your code expects from the site admin moduleconfig = ['apikey'] From a62078aad1681ea63b07e7c7a8020508b1133189 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Wed, 11 Jul 2018 23:43:42 +0200 Subject: [PATCH 071/153] add: Experimental expansion module to display the SIEM signatures from a sigma rule --- README.md | 1 + misp_modules/modules/expansion/__init__.py | 2 +- .../modules/expansion/sigma_queries.py | 50 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 misp_modules/modules/expansion/sigma_queries.py diff --git a/README.md b/README.md index 95019da..c1bc7a0 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [rbl](misp_modules/modules/expansion/rbl.py) - a module to get RBL (Real-Time Blackhost List) values from an attribute. * [reversedns](misp_modules/modules/expansion/reversedns.py) - Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes. * [shodan](misp_modules/modules/expansion/shodan.py) - a minimal [shodan](https://www.shodan.io/) expansion module. +* [Sigma queries](misp_modules/modules/expansion/sigma_queries.py) - Experimental expansion module querying a sigma rule to convert it into all the available SIEM signatures. * [Sigma syntax validator](misp_modules/modules/expansion/sigma_syntax_validator.py) - Sigma syntax validator. * [sourcecache](misp_modules/modules/expansion/sourcecache.py) - a module to cache a specific link from a MISP instance. * [STIX2 pattern syntax validator](misp_modules/modules/expansion/stix2_pattern_syntax_validator.py) - a module to check a STIX2 pattern syntax. diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index 5e0d65e..cda3af5 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,3 +1,3 @@ from . import _vmray -__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator'] +__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries'] diff --git a/misp_modules/modules/expansion/sigma_queries.py b/misp_modules/modules/expansion/sigma_queries.py new file mode 100644 index 0000000..e37df23 --- /dev/null +++ b/misp_modules/modules/expansion/sigma_queries.py @@ -0,0 +1,50 @@ +import sys, os, io, json +try: + from sigma.parser import SigmaCollectionParser + from sigma.config import SigmaConfiguration + from sigma.backends import getBackend, BackendOptions +except ModuleNotFoundError: + print("sigma or yaml is missing, use 'pip3 install sigmatools' to install it.") + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['sigma'], 'output': ['text']} +moduleinfo = {'version': '0.1', 'author': 'Christian Studer', 'module-type': ['expansion', 'hover'], + 'description': 'An expansion hover module to display the result of sigma queries.'} +moduleconfig = [] +sigma_targets = ('es-dsl', 'es-qs', 'graylog', 'kibana', 'xpack-watcher', 'logpoint', 'splunk', 'grep', 'wdatp', 'splunkxml', 'arcsight', 'qualys') + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + if not request.get('sigma'): + misperrors['error'] = 'Sigma rule missing' + return misperrors + config = SigmaConfiguration() + backend_options = BackendOptions(None) + f = io.TextIOWrapper(io.BytesIO(request.get('sigma').encode()), encoding='utf-8') + parser = SigmaCollectionParser(f, config, None) + targets = [] + old_stdout = sys.stdout + result = io.StringIO() + sys.stdout = result + for t in sigma_targets: + backend = getBackend(t)(config, backend_options, None) + try: + parser.generate(backend) + backend.finalize() + print("#NEXT") + targets.append(t) + except: + continue + sys.stdout = old_stdout + results = result.getvalue()[:-5].split('#NEXT') + d_result = {t: r.strip() for t,r in zip(targets, results)} + return {'results': [{'types': mispattributes['output'], 'values': d_result}]} + +def introspection(): + return mispattributes + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 3fd58537f66cbf3145e2fe335561c8a31c874c67 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Thu, 12 Jul 2018 11:43:24 +0200 Subject: [PATCH 072/153] remove Python 3.4 and Python 3.7 added --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0a3a912..a998141 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,12 @@ services: cache: pip python: - - "3.4" - "3.5" - "3.5-dev" - "3.6" - "3.6-dev" + - "3.7" + - "3.7-dev" install: - pip install -U nose codecov pytest From 576b3c9b9bdb2c8c66268c44b041646a014bcbac Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 13:40:51 +0200 Subject: [PATCH 073/153] history whois dns --- .../modules/expansion/securitytrails.py | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index e97b493..e23184d 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -340,7 +340,7 @@ def expand_history_ipv4_ipv6(api, domain): except APIError as e: misperrors['error'] = e - print(e) + return [], False return r, status_ok @@ -372,12 +372,71 @@ def expand_history_dns(api, domain): except APIError as e: misperrors['error'] = e + return [], False status_ok = True return r, status_ok +def expand_history_whois(api, domain): + r = [] + status_ok = False + try: + results = api.history_whois(domain) + + if results: + + if 'items' in results['results']: + for item in results['results']['items']: + item_registrant = __select_registrant_item(item) + + r.extend( + { + 'type': ['domain'], + 'values': item['nameServers'], + 'categories': ['Network activity'], + 'comment': 'Whois history Name Servers of %s ' + 'Status: %s ' % (domain, item['status']) + + } + ) + if 'email' in item_registrant: + r.append( + { + 'types': ['whois-registrant-email'], + 'values': [item_registrant['email']], + 'categories': ['Attribution'], + 'comment': 'Whois history registrant email of %s' + 'Status: %s' % ( + domain, item['status']) + } + ) + + if 'telephone' in item_registrant: + r.append( + { + 'types': ['whois-registrant-phone'], + 'values': [item_registrant['telephone']], + 'categories': ['Attribution'], + 'comment': 'Whois history registrant phone of %s' + 'Status: %s' % ( + domain, item['status']) + } + ) + + + + + except APIError as e: + misperrors['error'] = e + return [], False + + + + return r, status_ok + + def __history_ip(results, domain, type_ip='ip'): r = [] if 'records' in results: From 4b0daee6f1389467d5bbae26242e71e281d84d28 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:04:37 +0200 Subject: [PATCH 074/153] test whois history --- .../modules/expansion/securitytrails.py | 98 ++++++++++--------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index e23184d..eb93756 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -72,59 +72,63 @@ def handler(q=False): def handle_domain(api, domain, misperrors): result_filtered = {"results": []} - r, status_ok = expand_domain_info(api, misperrors, domain) + # r, status_ok = expand_domain_info(api, misperrors, domain) + # # + # if status_ok: + # if r: + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors['error'] + ' Error DNS result' + # return misperrors # - if status_ok: - if r: - result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors['error'] + ' Error DNS result' - return misperrors - - time.sleep(1) - r, status_ok = expand_subdomains(api, domain) - - if status_ok: - if r: - result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors['error'] + ' Error subdomains result' - return misperrors - - time.sleep(1) - r, status_ok = expand_whois(api, domain) - - if status_ok: - if r: - result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors['error'] + ' Error whois result' - return misperrors - - time.sleep(1) - r, status_ok = expand_history_ipv4_ipv6(api, domain) + # time.sleep(1) + # r, status_ok = expand_subdomains(api, domain) # + # if status_ok: + # if r: + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors['error'] + ' Error subdomains result' + # return misperrors + # + # time.sleep(1) + # r, status_ok = expand_whois(api, domain) + # + # if status_ok: + # if r: + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors['error'] + ' Error whois result' + # return misperrors + # + # time.sleep(1) + # r, status_ok = expand_history_ipv4_ipv6(api, domain) + # # + # + # if status_ok: + # if r: + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors['error'] + ' Error history ipv4' + # return misperrors + # + # time.sleep(1) + # + # r, status_ok = expand_history_dns(api, domain) + # + # if status_ok: + # if r: + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors[ + # 'error'] + ' Error in expand History DNS' + # return misperrors + + r, status_ok = expand_history_whois(api, domain) if status_ok: if r: result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors['error'] + ' Error history ipv4' - return misperrors - - time.sleep(1) - - r, status_ok = expand_history_dns(api, domain) - - if status_ok: - if r: - result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors[ - 'error'] + ' Error in expand History DNS' - return misperrors - print(result_filtered) - print(misperrors) return result_filtered From 41587bd56823c2799dce55c68f982acc2f567c6f Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:14:43 +0200 Subject: [PATCH 075/153] correct typo --- misp_modules/modules/expansion/securitytrails.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index eb93756..043ded3 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -391,8 +391,8 @@ def expand_history_whois(api, domain): if results: - if 'items' in results['results']: - for item in results['results']['items']: + if 'items' in results['result']: + for item in results['result']['items']: item_registrant = __select_registrant_item(item) r.extend( From fb595c08aac4d7297fc169183879324bee1890b7 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:16:19 +0200 Subject: [PATCH 076/153] add logs --- misp_modules/modules/expansion/securitytrails.py | 1 + 1 file changed, 1 insertion(+) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 043ded3..bf35c7b 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -506,4 +506,5 @@ def __select_registrant_item(entry): for c in entry['contacts']: if c['type'] == 'registrant': + print(c) return c From 731c06a939778b3beeb188375073bbe765503797 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:17:16 +0200 Subject: [PATCH 077/153] add logs --- misp_modules/modules/expansion/securitytrails.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index bf35c7b..8ae5dc1 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -502,9 +502,10 @@ def version(): def __select_registrant_item(entry): + print(entry) if 'contacts' in entry: for c in entry['contacts']: - + print(c) if c['type'] == 'registrant': print(c) return c From 9063da88cdc6fad8b1ab2e21a9522eac87189118 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:27:59 +0200 Subject: [PATCH 078/153] correct key and return of functions --- .../modules/expansion/securitytrails.py | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 8ae5dc1..845646a 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -394,40 +394,41 @@ def expand_history_whois(api, domain): if 'items' in results['result']: for item in results['result']['items']: item_registrant = __select_registrant_item(item) - - r.extend( - { - 'type': ['domain'], - 'values': item['nameServers'], - 'categories': ['Network activity'], - 'comment': 'Whois history Name Servers of %s ' - 'Status: %s ' % (domain, item['status']) - - } - ) - if 'email' in item_registrant: - r.append( + if item_registrant: + r.extend( { - 'types': ['whois-registrant-email'], - 'values': [item_registrant['email']], - 'categories': ['Attribution'], - 'comment': 'Whois history registrant email of %s' - 'Status: %s' % ( - domain, item['status']) + 'type': ['domain'], + 'values': item['nameServers'], + 'categories': ['Network activity'], + 'comment': 'Whois history Name Servers of %s ' + 'Status: %s ' % ( + domain, item['status']) + } ) + if 'email' in item_registrant: + r.append( + { + 'types': ['whois-registrant-email'], + 'values': [item_registrant['email']], + 'categories': ['Attribution'], + 'comment': 'Whois history registrant email of %s' + 'Status: %s' % ( + domain, item['status']) + } + ) - if 'telephone' in item_registrant: - r.append( - { - 'types': ['whois-registrant-phone'], - 'values': [item_registrant['telephone']], - 'categories': ['Attribution'], - 'comment': 'Whois history registrant phone of %s' - 'Status: %s' % ( - domain, item['status']) - } - ) + if 'telephone' in item_registrant: + r.append( + { + 'types': ['whois-registrant-phone'], + 'values': [item_registrant['telephone']], + 'categories': ['Attribution'], + 'comment': 'Whois history registrant phone of %s' + 'Status: %s' % ( + domain, item['status']) + } + ) @@ -502,10 +503,11 @@ def version(): def __select_registrant_item(entry): - print(entry) + if 'contacts' in entry: - for c in entry['contacts']: - print(c) - if c['type'] == 'registrant': - print(c) - return c + return list(filter(lambda x: x['type'] == 'registrant', + entry['contacts']))[0] + + if 'contact' in entry: + return list(filter(lambda x: x['type'] == 'registrant', + entry['contact']))[0] From 28f45ce94e9124e331700392a614373a83190e8a Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Thu, 12 Jul 2018 14:29:04 +0200 Subject: [PATCH 079/153] remove the never release Python code in Travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a998141..d7f452e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ python: - "3.5-dev" - "3.6" - "3.6-dev" - - "3.7" - "3.7-dev" install: From 844b25b4cdcaa9f293bb7bb8db9aea4082219631 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:32:56 +0200 Subject: [PATCH 080/153] correct out of bound returns --- .../modules/expansion/securitytrails.py | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 845646a..87ea1cd 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -266,7 +266,7 @@ def expand_whois(api, domain): r.append( { 'types': ['whois-registrant-email'], - 'values': [item_registrant['email']], + 'values': [item_registrant[0]['email']], 'categories': ['Attribution'], 'comment': 'Whois information of %s by securitytrails' % domain @@ -277,7 +277,7 @@ def expand_whois(api, domain): r.append( { 'types': ['whois-registrant-phone'], - 'values': [item_registrant['telephone']], + 'values': [item_registrant[0]['telephone']], 'categories': ['Attribution'], 'comment': 'Whois information of %s by securitytrails' % domain @@ -288,7 +288,7 @@ def expand_whois(api, domain): r.append( { 'types': ['whois-registrant-name'], - 'values': [item_registrant['name']], + 'values': [item_registrant[0]['name']], 'categories': ['Attribution'], 'comment': 'Whois information of %s by securitytrails' % domain @@ -299,7 +299,7 @@ def expand_whois(api, domain): r.append( { 'types': ['whois-registrar'], - 'values': [item_registrant['registrarName']], + 'values': [item_registrant[0]['registrarName']], 'categories': ['Attribution'], 'comment': 'Whois information of %s by securitytrails' % domain @@ -310,7 +310,7 @@ def expand_whois(api, domain): r.append( { 'types': ['whois-creation-date'], - 'values': [item_registrant['createdDate']], + 'values': [item_registrant[0]['createdDate']], 'categories': ['Attribution'], 'comment': 'Whois information of %s by securitytrails' % domain @@ -394,23 +394,24 @@ def expand_history_whois(api, domain): if 'items' in results['result']: for item in results['result']['items']: item_registrant = __select_registrant_item(item) - if item_registrant: - r.extend( - { - 'type': ['domain'], - 'values': item['nameServers'], - 'categories': ['Network activity'], - 'comment': 'Whois history Name Servers of %s ' - 'Status: %s ' % ( + r.extend( + { + 'type': ['domain'], + 'values': item['nameServers'], + 'categories': ['Network activity'], + 'comment': 'Whois history Name Servers of %s ' + 'Status: %s ' % ( domain, item['status']) - } - ) - if 'email' in item_registrant: + } + ) + if item_registrant: + + if 'email' in item_registrant[0]: r.append( { 'types': ['whois-registrant-email'], - 'values': [item_registrant['email']], + 'values': [item_registrant[0]['email']], 'categories': ['Attribution'], 'comment': 'Whois history registrant email of %s' 'Status: %s' % ( @@ -422,7 +423,7 @@ def expand_history_whois(api, domain): r.append( { 'types': ['whois-registrant-phone'], - 'values': [item_registrant['telephone']], + 'values': [item_registrant[0]['telephone']], 'categories': ['Attribution'], 'comment': 'Whois history registrant phone of %s' 'Status: %s' % ( @@ -430,9 +431,6 @@ def expand_history_whois(api, domain): } ) - - - except APIError as e: misperrors['error'] = e return [], False @@ -506,8 +504,8 @@ def __select_registrant_item(entry): if 'contacts' in entry: return list(filter(lambda x: x['type'] == 'registrant', - entry['contacts']))[0] + entry['contacts'])) if 'contact' in entry: return list(filter(lambda x: x['type'] == 'registrant', - entry['contact']))[0] + entry['contact'])) From d56bf550389fc23b44980e6335cba63c30238a94 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:33:52 +0200 Subject: [PATCH 081/153] add logs --- misp_modules/modules/expansion/securitytrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 87ea1cd..bb1c1b3 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -501,7 +501,7 @@ def version(): def __select_registrant_item(entry): - + print(entry) if 'contacts' in entry: return list(filter(lambda x: x['type'] == 'registrant', entry['contacts'])) From 9de201375b8221015fabdada7c511cf3cf42d11d Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:37:09 +0200 Subject: [PATCH 082/153] add logs --- misp_modules/modules/expansion/securitytrails.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index bb1c1b3..b113f1f 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -501,11 +501,13 @@ def version(): def __select_registrant_item(entry): - print(entry) + if 'contacts' in entry: return list(filter(lambda x: x['type'] == 'registrant', entry['contacts'])) if 'contact' in entry: + print(entry) + print('\r\n') return list(filter(lambda x: x['type'] == 'registrant', entry['contact'])) From a0cf9de590c5e126219877959c51f04621f2bd0d Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:38:38 +0200 Subject: [PATCH 083/153] add logs --- misp_modules/modules/expansion/securitytrails.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index b113f1f..0cbd6f8 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -509,5 +509,7 @@ def __select_registrant_item(entry): if 'contact' in entry: print(entry) print('\r\n') - return list(filter(lambda x: x['type'] == 'registrant', - entry['contact'])) + res = list(filter(lambda x: x['type'] == 'registrant', + entry['contact'])) + print(res) + return res From 86d94278162f0397fe0b092661555d1629cd31b1 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:42:33 +0200 Subject: [PATCH 084/153] add logs --- misp_modules/modules/expansion/securitytrails.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 0cbd6f8..77bdd2c 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -394,6 +394,7 @@ def expand_history_whois(api, domain): if 'items' in results['result']: for item in results['result']['items']: item_registrant = __select_registrant_item(item) + print(item_registrant) r.extend( { 'type': ['domain'], @@ -435,8 +436,6 @@ def expand_history_whois(api, domain): misperrors['error'] = e return [], False - - return r, status_ok From aa89a7fc4d2b69ca9e0b738aa890402e72c20bae Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:44:19 +0200 Subject: [PATCH 085/153] add logs --- misp_modules/modules/expansion/securitytrails.py | 1 + 1 file changed, 1 insertion(+) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 77bdd2c..68a7206 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -128,6 +128,7 @@ def handle_domain(api, domain, misperrors): if status_ok: if r: + print(r) result_filtered['results'].extend(r) return result_filtered From 86d236f859c8d6065d078853d512eb243927fddc Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:47:34 +0200 Subject: [PATCH 086/153] add status_ok to true --- misp_modules/modules/expansion/securitytrails.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 68a7206..8395c13 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -128,8 +128,12 @@ def handle_domain(api, domain, misperrors): if status_ok: if r: - print(r) + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors['error'] + \ + ' Error in expand History Whois' + return result_filtered @@ -395,7 +399,7 @@ def expand_history_whois(api, domain): if 'items' in results['result']: for item in results['result']['items']: item_registrant = __select_registrant_item(item) - print(item_registrant) + r.extend( { 'type': ['domain'], @@ -436,7 +440,7 @@ def expand_history_whois(api, domain): except APIError as e: misperrors['error'] = e return [], False - + status_ok = True return r, status_ok @@ -507,9 +511,7 @@ def __select_registrant_item(entry): entry['contacts'])) if 'contact' in entry: - print(entry) - print('\r\n') res = list(filter(lambda x: x['type'] == 'registrant', entry['contact'])) - print(res) + return res From 0b0137829a03629b8972e6fd3c3a6d00bfb5b756 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:48:15 +0200 Subject: [PATCH 087/153] add logs --- misp_modules/modules/expansion/securitytrails.py | 1 + 1 file changed, 1 insertion(+) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 8395c13..c3d82a2 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -441,6 +441,7 @@ def expand_history_whois(api, domain): misperrors['error'] = e return [], False status_ok = True + print(r) return r, status_ok From 2f5381d7b2f38b6488dfbac004ea364a6d74b724 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:49:51 +0200 Subject: [PATCH 088/153] add logs --- misp_modules/modules/expansion/securitytrails.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index c3d82a2..36703f5 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -130,9 +130,10 @@ def handle_domain(api, domain, misperrors): if r: result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors['error'] + \ - ' Error in expand History Whois' + else: + misperrors['error'] = misperrors['error'] + \ + ' Error in expand History Whois' + return misperrors return result_filtered From 0341bdc398b978a031b60ac6c32a7550f93a3efd Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:52:01 +0200 Subject: [PATCH 089/153] error call functions --- misp_modules/modules/expansion/securitytrails.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 36703f5..4b2cac6 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -401,9 +401,9 @@ def expand_history_whois(api, domain): for item in results['result']['items']: item_registrant = __select_registrant_item(item) - r.extend( + r.append( { - 'type': ['domain'], + 'types': ['domain'], 'values': item['nameServers'], 'categories': ['Network activity'], 'comment': 'Whois history Name Servers of %s ' From db35c9b0917c2bae5491a3a927943c69bde944fd Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:55:56 +0200 Subject: [PATCH 090/153] correct index error --- misp_modules/modules/expansion/securitytrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 4b2cac6..14cb8b8 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -426,7 +426,7 @@ def expand_history_whois(api, domain): } ) - if 'telephone' in item_registrant: + if 'telephone' in item_registrant[0]: r.append( { 'types': ['whois-registrant-phone'], From 5a422c2e5b370a23f1fa9668837a3e4c6f59626f Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:57:37 +0200 Subject: [PATCH 091/153] add whois expand to test --- .../modules/expansion/securitytrails.py | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 14cb8b8..d83eeed 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -91,17 +91,17 @@ def handle_domain(api, domain, misperrors): # misperrors['error'] = misperrors['error'] + ' Error subdomains result' # return misperrors # - # time.sleep(1) - # r, status_ok = expand_whois(api, domain) - # - # if status_ok: - # if r: - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors['error'] + ' Error whois result' - # return misperrors - # - # time.sleep(1) + time.sleep(1) + r, status_ok = expand_whois(api, domain) + + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors['error'] + ' Error whois result' + return misperrors + + time.sleep(1) # r, status_ok = expand_history_ipv4_ipv6(api, domain) # # # @@ -268,7 +268,7 @@ def expand_whois(api, domain): item_registrant = __select_registrant_item(results) if item_registrant: - if 'email' in item_registrant: + if 'email' in item_registrant[0]: r.append( { 'types': ['whois-registrant-email'], @@ -279,7 +279,7 @@ def expand_whois(api, domain): } ) - if 'telephone' in item_registrant: + if 'telephone' in item_registrant[0]: r.append( { 'types': ['whois-registrant-phone'], @@ -290,7 +290,7 @@ def expand_whois(api, domain): } ) - if 'name' in item_registrant: + if 'name' in item_registrant[0]: r.append( { 'types': ['whois-registrant-name'], @@ -301,7 +301,7 @@ def expand_whois(api, domain): } ) - if 'registrarName' in item_registrant: + if 'registrarName' in item_registrant[0]: r.append( { 'types': ['whois-registrar'], @@ -312,7 +312,7 @@ def expand_whois(api, domain): } ) - if 'createdDate' in item_registrant: + if 'createdDate' in item_registrant[0]: r.append( { 'types': ['whois-creation-date'], From 3eda71219365a6ae489cd39cf7c7d084f07effcd Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:58:48 +0200 Subject: [PATCH 092/153] add whois expand to test --- .../modules/expansion/securitytrails.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index d83eeed..ab8087c 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -124,16 +124,16 @@ def handle_domain(api, domain, misperrors): # 'error'] + ' Error in expand History DNS' # return misperrors - r, status_ok = expand_history_whois(api, domain) - - if status_ok: - if r: - - result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors['error'] + \ - ' Error in expand History Whois' - return misperrors + # r, status_ok = expand_history_whois(api, domain) + # + # if status_ok: + # if r: + # + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors['error'] + \ + # ' Error in expand History Whois' + # return misperrors return result_filtered From 7f52a15d16c3ead17734180b55a078b452ce9122 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 14:59:50 +0200 Subject: [PATCH 093/153] add logs --- misp_modules/modules/expansion/securitytrails.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index ab8087c..3332026 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -509,9 +509,9 @@ def version(): def __select_registrant_item(entry): if 'contacts' in entry: - return list(filter(lambda x: x['type'] == 'registrant', - entry['contacts'])) - + res = list(filter(lambda x: x['type'] == 'registrant', + entry['contacts'])) + print(res) if 'contact' in entry: res = list(filter(lambda x: x['type'] == 'registrant', entry['contact'])) From 966f9603a9ec20ffa6055bb12cbf2a46a4793347 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 12 Jul 2018 15:02:46 +0200 Subject: [PATCH 094/153] add return --- misp_modules/modules/expansion/securitytrails.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 3332026..4b276da 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -267,7 +267,7 @@ def expand_whois(api, domain): status_ok = True item_registrant = __select_registrant_item(results) if item_registrant: - + print(item_registrant) if 'email' in item_registrant[0]: r.append( { @@ -511,7 +511,7 @@ def __select_registrant_item(entry): if 'contacts' in entry: res = list(filter(lambda x: x['type'] == 'registrant', entry['contacts'])) - print(res) + return res if 'contact' in entry: res = list(filter(lambda x: x['type'] == 'registrant', entry['contact'])) From a41cf59e0c28141fda01efa6aee840cd5fade869 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 17 Jul 2018 15:05:15 +0200 Subject: [PATCH 095/153] add searching domains --- .../modules/expansion/securitytrails.py | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 4b276da..b4d9609 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -442,7 +442,7 @@ def expand_history_whois(api, domain): misperrors['error'] = e return [], False status_ok = True - print(r) + return r, status_ok @@ -497,6 +497,40 @@ def __history_dns(results, domain, type_serv, service): ) return r + +def expand_searching_domain(api, ip): + r = [] + status_ok = False + + try: + results = api.searching_domains(ip) + + if results: + if 'records' in results: + res = [(r['host_provider'], r['hostname'], r['whois']) + for r in results['records']] + + for host_provider, hostname, whois in res: + comment = 'domain for %s by %s' % (ip, host_provider[0]) + if whois['registrant']: + comment = comment + ' registrar %s' % whois['registrar'] + + r.append( + { + 'types': ['domain'], + 'category': ['Network activity'], + 'values': [hostname], + 'comment': comment + + } + ) + status_ok = True + except APIError as e: + misperrors['error'] = e + return [], False + + return r, status_ok + def introspection(): return mispattributes @@ -507,13 +541,13 @@ def version(): def __select_registrant_item(entry): - + res = None if 'contacts' in entry: res = list(filter(lambda x: x['type'] == 'registrant', entry['contacts'])) - return res + if 'contact' in entry: res = list(filter(lambda x: x['type'] == 'registrant', entry['contact'])) - return res + return res From 999ae1f6f0ec2264a38ee557f9a4a7e4a533d188 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 17 Jul 2018 17:09:01 +0200 Subject: [PATCH 096/153] add searching domains --- .../modules/expansion/securitytrails.py | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index b4d9609..4aaca61 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -91,17 +91,17 @@ def handle_domain(api, domain, misperrors): # misperrors['error'] = misperrors['error'] + ' Error subdomains result' # return misperrors # - time.sleep(1) - r, status_ok = expand_whois(api, domain) - - if status_ok: - if r: - result_filtered['results'].extend(r) - else: - misperrors['error'] = misperrors['error'] + ' Error whois result' - return misperrors - - time.sleep(1) + # time.sleep(1) + # r, status_ok = expand_whois(api, domain) + # + # if status_ok: + # if r: + # result_filtered['results'].extend(r) + # else: + # misperrors['error'] = misperrors['error'] + ' Error whois result' + # return misperrors + # + # time.sleep(1) # r, status_ok = expand_history_ipv4_ipv6(api, domain) # # # @@ -135,11 +135,23 @@ def handle_domain(api, domain, misperrors): # ' Error in expand History Whois' # return misperrors + return result_filtered def handle_ip(api, ip, misperrors): - pass + result_filtered = {"results": []} + + r, status_ok = expand_searching_domain(api, ip) + + if status_ok: + if r: + result_filtered['result'].extend(r) + else: + misperrors['error'] += ' Error in expand searching domain' + return misperrors + + return result_filtered def expand_domain_info(api, misperror, domain): From 431c1511a37d700fd6cd1e70f23fb18493bd6b4e Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 17 Jul 2018 17:20:30 +0200 Subject: [PATCH 097/153] correct param --- misp_modules/modules/expansion/securitytrails.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 4aaca61..53a0111 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -150,7 +150,7 @@ def handle_ip(api, ip, misperrors): else: misperrors['error'] += ' Error in expand searching domain' return misperrors - + return result_filtered @@ -515,7 +515,7 @@ def expand_searching_domain(api, ip): status_ok = False try: - results = api.searching_domains(ip) + results = api.searching_domains(ipv4=ip) if results: if 'records' in results: From 2706c4a82a6294341d276eeb151428ad4c5fe047 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 17 Jul 2018 17:21:38 +0200 Subject: [PATCH 098/153] correct key --- misp_modules/modules/expansion/securitytrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 53a0111..b6b3e65 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -524,7 +524,7 @@ def expand_searching_domain(api, ip): for host_provider, hostname, whois in res: comment = 'domain for %s by %s' % (ip, host_provider[0]) - if whois['registrant']: + if whois['registrar']: comment = comment + ' registrar %s' % whois['registrar'] r.append( From c785cae89b409a174714e8d012b4b6cccd062567 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 17 Jul 2018 17:22:48 +0200 Subject: [PATCH 099/153] correct key --- misp_modules/modules/expansion/securitytrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index b6b3e65..b2790fa 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -146,7 +146,7 @@ def handle_ip(api, ip, misperrors): if status_ok: if r: - result_filtered['result'].extend(r) + result_filtered['results'].extend(r) else: misperrors['error'] += ' Error in expand searching domain' return misperrors From 9d603344c2cb0c0560995252f8a9d3bdd50c51f4 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 17 Jul 2018 18:32:50 +0200 Subject: [PATCH 100/153] add searching_stats --- .../modules/expansion/securitytrails.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index b2790fa..5294a62 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -543,6 +543,31 @@ def expand_searching_domain(api, ip): return r, status_ok + +def expand_search_stats(api, ip, misperror): + r = [] + status_ok = False + + try: + result = api.searching_stats(ipv4=ip) + if result and 'top_organizations' in result: + comment = '' + for reg in result['top_organizations']: + comment += 'Organization %s used %s count: %s' % (reg['key'], + ip, + reg['count']) + r.append({'types': ['comment'], + 'categories': ['Other'], + 'values': comment, + }) + status_ok = True + except APIError as e: + misperrors['error'] = e + return [], False + + return r, status_ok + + def introspection(): return mispattributes From 8cbeda40a5043eb2494d1d41c9431dabfce9d067 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 17 Jul 2018 18:42:01 +0200 Subject: [PATCH 101/153] add searching_stats --- misp_modules/modules/expansion/securitytrails.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 5294a62..f8f822e 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -151,6 +151,17 @@ def handle_ip(api, ip, misperrors): misperrors['error'] += ' Error in expand searching domain' return misperrors + time.sleep(1) + + r, status_ok = expand_search_stats(api, ip, misperrors) + + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] += ' Error in expand searching stats' + return misperrors + return result_filtered @@ -549,7 +560,7 @@ def expand_search_stats(api, ip, misperror): status_ok = False try: - result = api.searching_stats(ipv4=ip) + result = api.search_stats(ipv4=ip) if result and 'top_organizations' in result: comment = '' for reg in result['top_organizations']: From 88859a0ba7123bd9683c9bdededb1a769a0546b7 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Tue, 17 Jul 2018 18:43:52 +0200 Subject: [PATCH 102/153] add logs --- misp_modules/modules/expansion/securitytrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index f8f822e..63f08c9 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -573,7 +573,7 @@ def expand_search_stats(api, ip, misperror): }) status_ok = True except APIError as e: - misperrors['error'] = e + misperrors['error'] = e.value return [], False return r, status_ok From f2df6dc538244210715c4ae45202871d8f3987ee Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 18 Jul 2018 10:47:42 +0200 Subject: [PATCH 103/153] last commit for release --- .../modules/expansion/securitytrails.py | 167 +++++++----------- 1 file changed, 65 insertions(+), 102 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 63f08c9..8c96f76 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -72,69 +72,66 @@ def handler(q=False): def handle_domain(api, domain, misperrors): result_filtered = {"results": []} - # r, status_ok = expand_domain_info(api, misperrors, domain) - # # - # if status_ok: - # if r: - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors['error'] + ' Error DNS result' - # return misperrors - # - # time.sleep(1) - # r, status_ok = expand_subdomains(api, domain) - # - # if status_ok: - # if r: - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors['error'] + ' Error subdomains result' - # return misperrors - # - # time.sleep(1) - # r, status_ok = expand_whois(api, domain) - # - # if status_ok: - # if r: - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors['error'] + ' Error whois result' - # return misperrors - # - # time.sleep(1) - # r, status_ok = expand_history_ipv4_ipv6(api, domain) - # # - # - # if status_ok: - # if r: - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors['error'] + ' Error history ipv4' - # return misperrors - # - # time.sleep(1) - # - # r, status_ok = expand_history_dns(api, domain) - # - # if status_ok: - # if r: - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors[ - # 'error'] + ' Error in expand History DNS' - # return misperrors + r, status_ok = expand_domain_info(api, misperrors, domain) - # r, status_ok = expand_history_whois(api, domain) - # - # if status_ok: - # if r: - # - # result_filtered['results'].extend(r) - # else: - # misperrors['error'] = misperrors['error'] + \ - # ' Error in expand History Whois' - # return misperrors + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors['error'] + ' Error DNS result' + return misperrors + time.sleep(1) + r, status_ok = expand_subdomains(api, domain) + + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors['error'] + ' Error subdomains result' + return misperrors + + time.sleep(1) + r, status_ok = expand_whois(api, domain) + + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors['error'] + ' Error whois result' + return misperrors + + time.sleep(1) + r, status_ok = expand_history_ipv4_ipv6(api, domain) + + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors['error'] + ' Error history ipv4' + return misperrors + + time.sleep(1) + + r, status_ok = expand_history_dns(api, domain) + + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors[ + 'error'] + ' Error in expand History DNS' + return misperrors + + r, status_ok = expand_history_whois(api, domain) + + if status_ok: + if r: + result_filtered['results'].extend(r) + else: + misperrors['error'] = misperrors['error'] + \ + ' Error in expand History Whois' + return misperrors return result_filtered @@ -151,17 +148,6 @@ def handle_ip(api, ip, misperrors): misperrors['error'] += ' Error in expand searching domain' return misperrors - time.sleep(1) - - r, status_ok = expand_search_stats(api, ip, misperrors) - - if status_ok: - if r: - result_filtered['results'].extend(r) - else: - misperrors['error'] += ' Error in expand searching stats' - return misperrors - return result_filtered @@ -274,7 +260,8 @@ def expand_subdomains(api, domain): ) except APIError as e: - misperrors['error'] = e + misperrors['error'] = e.value + return [], False return r, status_ok @@ -347,8 +334,8 @@ def expand_whois(api, domain): ) except APIError as e: - misperrors['error'] = e - print(e) + misperrors['error'] = e.value + return [], False return r, status_ok @@ -372,7 +359,7 @@ def expand_history_ipv4_ipv6(api, domain): r.extend(__history_ip(results, domain, type_ip='ipv6')) except APIError as e: - misperrors['error'] = e + misperrors['error'] = e.value return [], False return r, status_ok @@ -404,7 +391,7 @@ def expand_history_dns(api, domain): r.extend(__history_dns(results, domain, 'host', 'mx')) except APIError as e: - misperrors['error'] = e + misperrors['error'] = e.value return [], False status_ok = True @@ -462,7 +449,7 @@ def expand_history_whois(api, domain): ) except APIError as e: - misperrors['error'] = e + misperrors['error'] = e.value return [], False status_ok = True @@ -548,30 +535,6 @@ def expand_searching_domain(api, ip): } ) status_ok = True - except APIError as e: - misperrors['error'] = e - return [], False - - return r, status_ok - - -def expand_search_stats(api, ip, misperror): - r = [] - status_ok = False - - try: - result = api.search_stats(ipv4=ip) - if result and 'top_organizations' in result: - comment = '' - for reg in result['top_organizations']: - comment += 'Organization %s used %s count: %s' % (reg['key'], - ip, - reg['count']) - r.append({'types': ['comment'], - 'categories': ['Other'], - 'values': comment, - }) - status_ok = True except APIError as e: misperrors['error'] = e.value return [], False From c8e20d90879b024eb068cc74a21c848a8324e024 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 18 Jul 2018 10:51:47 +0200 Subject: [PATCH 104/153] remove print --- misp_modules/modules/expansion/securitytrails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 8c96f76..bf1c5b1 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -277,7 +277,7 @@ def expand_whois(api, domain): status_ok = True item_registrant = __select_registrant_item(results) if item_registrant: - print(item_registrant) + if 'email' in item_registrant[0]: r.append( { From 804e59ed8d6c48b7383de4327d45fae352197cbe Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 18 Jul 2018 10:58:51 +0200 Subject: [PATCH 105/153] change type of status --- misp_modules/modules/expansion/securitytrails.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index bf1c5b1..325fa13 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -418,7 +418,7 @@ def expand_history_whois(api, domain): 'categories': ['Network activity'], 'comment': 'Whois history Name Servers of %s ' 'Status: %s ' % ( - domain, item['status']) + domain, ' '.join(item['status'])) } ) @@ -432,7 +432,8 @@ def expand_history_whois(api, domain): 'categories': ['Attribution'], 'comment': 'Whois history registrant email of %s' 'Status: %s' % ( - domain, item['status']) + domain, + ' '.join(item['status'])) } ) @@ -444,7 +445,8 @@ def expand_history_whois(api, domain): 'categories': ['Attribution'], 'comment': 'Whois history registrant phone of %s' 'Status: %s' % ( - domain, item['status']) + domain, + ' '.join(item['status'])) } ) From 1fcc16efb7113acf532ba42f09444f7ff1c16b57 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Wed, 18 Jul 2018 22:19:52 +0200 Subject: [PATCH 106/153] securitytrails.com expansion module added --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c1bc7a0..cbf9550 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [passivetotal](misp_modules/modules/expansion/passivetotal.py) - a [passivetotal](https://www.passivetotal.org/) module that queries a number of different PassiveTotal datasets. * [rbl](misp_modules/modules/expansion/rbl.py) - a module to get RBL (Real-Time Blackhost List) values from an attribute. * [reversedns](misp_modules/modules/expansion/reversedns.py) - Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes. +* [securitytrails](misp_modules/modules/expansion/securitytrails.py) - an expansion module for [securitytrails](https://securitytrails.com/). * [shodan](misp_modules/modules/expansion/shodan.py) - a minimal [shodan](https://www.shodan.io/) expansion module. * [Sigma queries](misp_modules/modules/expansion/sigma_queries.py) - Experimental expansion module querying a sigma rule to convert it into all the available SIEM signatures. * [Sigma syntax validator](misp_modules/modules/expansion/sigma_syntax_validator.py) - Sigma syntax validator. From 2f27ff12446d36ebd6353029dcc17805ee64865e Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Fri, 27 Jul 2018 14:44:06 +0200 Subject: [PATCH 107/153] ta_import - support for TheatAnalyzer 6.1 --- .../import_mod/threatanalyzer_import.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/misp_modules/modules/import_mod/threatanalyzer_import.py b/misp_modules/modules/import_mod/threatanalyzer_import.py index 83d8291..a6358ab 100755 --- a/misp_modules/modules/import_mod/threatanalyzer_import.py +++ b/misp_modules/modules/import_mod/threatanalyzer_import.py @@ -46,15 +46,19 @@ def handler(q=False): with zf.open(zip_file_name, mode='r', pwd=None) as fp: file_data = fp.read() for line in file_data.decode().split('\n'): - if line: + if not line: + continue + if line.count('|') == 3: l_fname, l_size, l_md5, l_created = line.split('|') - l_fname = cleanup_filepath(l_fname) - if l_fname: - if l_size == 0: - pass # FIXME create an attribute for the filename/path - else: - # file is a non empty sample, upload the sample later - modified_files_mapping[l_md5] = l_fname + if line.count('|') == 4: + l_fname, l_size, l_md5, l_sha256, l_created = line.split('|') + l_fname = cleanup_filepath(l_fname) + if l_fname: + if l_size == 0: + pass # FIXME create an attribute for the filename/path + else: + # file is a non empty sample, upload the sample later + modified_files_mapping[l_md5] = l_fname # now really process the data for zip_file_name in zf.namelist(): # Get all files in the zip file From 63c32520623a35ff40de12bdd40b033a9bfd1edc Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 30 Jul 2018 14:22:40 +0200 Subject: [PATCH 108/153] fix: Put the report location parsing in a try/catch statement as it is an optional field --- misp_modules/modules/export_mod/goamlexport.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/export_mod/goamlexport.py b/misp_modules/modules/export_mod/goamlexport.py index c277640..961a11b 100644 --- a/misp_modules/modules/export_mod/goamlexport.py +++ b/misp_modules/modules/export_mod/goamlexport.py @@ -87,9 +87,12 @@ class GoAmlGeneration(object): person_to_parse = [person_uuid for person_uuid in self.uuids.get('person') if person_uuid not in self.parsed_uuids.get('person')] if len(person_to_parse) == 1: self.itterate('person', 'reporting_person', person_to_parse[0], 'header') - location_to_parse = [location_uuid for location_uuid in self.uuids.get('geolocation') if location_uuid not in self.parsed_uuids.get('geolocation')] - if len(location_to_parse) == 1: - self.itterate('geolocation', 'location', location_to_parse[0], 'header') + try: + location_to_parse = [location_uuid for location_uuid in self.uuids.get('geolocation') if location_uuid not in self.parsed_uuids.get('geolocation')] + if len(location_to_parse) == 1: + self.itterate('geolocation', 'location', location_to_parse[0], 'header') + except TypeError: + pass self.xml['data'] += "" def itterate(self, object_type, aml_type, uuid, xml_part): From 8d4e2025f760279eca24a640cd732291bb0c88af Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Fri, 3 Aug 2018 13:58:53 +0200 Subject: [PATCH 109/153] ta_import - bugfixes for TA 6.1 --- .../import_mod/threatanalyzer_import.py | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/misp_modules/modules/import_mod/threatanalyzer_import.py b/misp_modules/modules/import_mod/threatanalyzer_import.py index a6358ab..916628e 100755 --- a/misp_modules/modules/import_mod/threatanalyzer_import.py +++ b/misp_modules/modules/import_mod/threatanalyzer_import.py @@ -15,7 +15,7 @@ misperrors = {'error': 'Error'} userConfig = {} inputSource = ['file'] -moduleinfo = {'version': '0.7', 'author': 'Christophe Vandeplas', +moduleinfo = {'version': '0.8', 'author': 'Christophe Vandeplas', 'description': 'Import for ThreatAnalyzer archive.zip/analysis.json files', 'module-type': ['import']} @@ -73,7 +73,7 @@ def handler(q=False): results.append({ 'values': current_sample_filename, 'data': base64.b64encode(file_data).decode(), - 'type': 'malware-sample', 'categories': ['Payload delivery', 'Artifacts dropped'], 'to_ids': True, 'comment': ''}) + 'type': 'malware-sample', 'categories': ['Artifacts dropped', 'Payload delivery'], 'to_ids': True, 'comment': ''}) if 'Analysis/analysis.json' in zip_file_name: with zf.open(zip_file_name, mode='r', pwd=None) as fp: @@ -88,7 +88,7 @@ def handler(q=False): results.append({ 'values': sample_filename, 'data': base64.b64encode(file_data).decode(), - 'type': 'malware-sample', 'categories': ['Artifacts dropped', 'Payload delivery'], 'to_ids': True, 'comment': ''}) + 'type': 'malware-sample', 'categories': ['Payload delivery', 'Artifacts dropped'], 'to_ids': True, 'comment': ''}) except Exception as e: # no 'sample' in archive, might be an url analysis, just ignore pass @@ -113,7 +113,15 @@ def process_analysis_json(analysis_json): for process in analysis_json['analysis']['processes']['process']: # print_json(process) if 'connection_section' in process and 'connection' in process['connection_section']: + # compensate for absurd behavior of the data format: if one entry = immediately the dict, if multiple entries = list containing dicts + # this will always create a list, even with only one item + if isinstance(process['connection_section']['connection'], dict): + process['connection_section']['connection'] = [process['connection_section']['connection']] + # iterate over each entry for connection_section_connection in process['connection_section']['connection']: + if 'name_to_ip' in connection_section_connection: # TA 6.1 data format + connection_section_connection['@remote_ip'] = connection_section_connection['name_to_ip']['@result_addresses'] + connection_section_connection['@remote_hostname'] = connection_section_connection['name_to_ip']['@request_name'] connection_section_connection['@remote_ip'] = cleanup_ip(connection_section_connection['@remote_ip']) connection_section_connection['@remote_hostname'] = cleanup_hostname(connection_section_connection['@remote_hostname']) @@ -124,7 +132,7 @@ def process_analysis_json(analysis_json): # connection_section_connection['@remote_hostname'], # connection_section_connection['@remote_ip']) # ) - yield({'values': val, 'type': 'domain|ip', 'categories': 'Network activity', 'to_ids': True, 'comment': ''}) + yield({'values': val, 'type': 'domain|ip', 'categories': ['Network activity'], 'to_ids': True, 'comment': ''}) elif connection_section_connection['@remote_ip']: # print("connection_section_connection ip-dst: {} IDS:yes".format( # connection_section_connection['@remote_ip']) @@ -136,20 +144,19 @@ def process_analysis_json(analysis_json): # ) yield({'values': connection_section_connection['@remote_hostname'], 'type': 'hostname', 'to_ids': True, 'comment': ''}) if 'http_command' in connection_section_connection: - for http_command in connection_section_connection['http_command']: - # print('connection_section_connection HTTP COMMAND: {}\t{}'.format( - # http_command['@method'], # comment - # http_command['@url']) # url - # ) - val = cleanup_url(http_command['@url']) - if val: - yield({'values': val, 'type': 'url', 'categories': 'Network activity', 'to_ids': True, 'comment': http_command['@method']}) + # print('connection_section_connection HTTP COMMAND: {}\t{}'.format( + # connection_section_connection['http_command']['@method'], # comment + # connection_section_connection['http_command']['@url']) # url + # ) + val = cleanup_url(connection_section_connection['http_command']['@url']) + if val: + yield({'values': val, 'type': 'url', 'categories': ['Network activity'], 'to_ids': True, 'comment': connection_section_connection['http_command']['@method']}) if 'http_header' in connection_section_connection: for http_header in connection_section_connection['http_header']: if 'User-Agent:' in http_header['@header']: val = http_header['@header'][len('User-Agent: '):] - yield({'values': val, 'type': 'user-agent', 'categories': 'Network activity', 'to_ids': False, 'comment': ''}) + yield({'values': val, 'type': 'user-agent', 'categories': ['Network activity'], 'to_ids': False, 'comment': ''}) elif 'Host:' in http_header['@header']: val = http_header['@header'][len('Host: '):] if ':' in val: @@ -162,7 +169,7 @@ def process_analysis_json(analysis_json): if val_hostname and val_port: val_combined = '{}|{}'.format(val_hostname, val_port) # print({'values': val_combined, 'type': 'hostname|port', 'to_ids': True, 'comment': ''}) - yield({'values': val_combined, 'type': 'hostname|port', 'to_ids': True, 'comment': ''}) + yield({'values': val_combined, 'type': 'hostname|port', 'categories': ['Network activity'], 'to_ids': True, 'comment': ''}) elif val_ip and val_port: val_combined = '{}|{}'.format(val_ip, val_port) # print({'values': val_combined, 'type': 'ip-dst|port', 'to_ids': True, 'comment': ''}) @@ -207,7 +214,7 @@ def process_analysis_json(analysis_json): # networkoperation_section_dns_request_by_name['@request_name'], # networkoperation_section_dns_request_by_name['@result_addresses']) # ) - yield({'values': val, 'type': 'domain|ip', 'categories': 'Network activity', 'to_ids': True, 'comment': ''}) + yield({'values': val, 'type': 'domain|ip', 'categories': ['Network activity'], 'to_ids': True, 'comment': ''}) elif networkoperation_section_dns_request_by_name['@request_name']: # print("networkoperation_section_dns_request_by_name hostname: {} IDS:yes".format( # networkoperation_section_dns_request_by_name['@request_name']) @@ -231,14 +238,14 @@ def process_analysis_json(analysis_json): # networkpacket_section_connect_to_computer['@remote_port']) # ) val_combined = "{}|{}".format(networkpacket_section_connect_to_computer['@remote_hostname'], networkpacket_section_connect_to_computer['@remote_ip']) - yield({'values': val_combined, 'type': 'hostname|ip', 'to_ids': True, 'comment': ''}) + yield({'values': val_combined, 'type': 'domain|ip', 'to_ids': True, 'comment': ''}) elif networkpacket_section_connect_to_computer['@remote_hostname']: # print("networkpacket_section_connect_to_computer hostname: {} IDS:yes COMMENT:port {}".format( # networkpacket_section_connect_to_computer['@remote_hostname'], # networkpacket_section_connect_to_computer['@remote_port']) # ) val_combined = "{}|{}".format(networkpacket_section_connect_to_computer['@remote_hostname'], networkpacket_section_connect_to_computer['@remote_port']) - yield({'values': val_combined, 'type': 'hostname|port', 'to_ids': True, 'comment': ''}) + yield({'values': val_combined, 'type': 'hostname|port', 'categories': ['Network activity'], 'to_ids': True, 'comment': ''}) elif networkpacket_section_connect_to_computer['@remote_ip']: # print("networkpacket_section_connect_to_computer ip-dst: {} IDS:yes COMMENT:port {}".format( # networkpacket_section_connect_to_computer['@remote_ip'], @@ -524,3 +531,4 @@ def introspection(): def version(): moduleinfo['config'] = moduleconfig return moduleinfo + From 57af98720d950d4880436fb888dca702bb7aba76 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 7 Aug 2018 18:13:25 +0200 Subject: [PATCH 110/153] fix: [cleanup] Quick clean up on exception type --- misp_modules/modules/expansion/rbl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/rbl.py b/misp_modules/modules/expansion/rbl.py index da8c5fb..49de421 100644 --- a/misp_modules/modules/expansion/rbl.py +++ b/misp_modules/modules/expansion/rbl.py @@ -6,7 +6,7 @@ try: resolver = dns.resolver.Resolver() resolver.timeout = 0.2 resolver.lifetime = 0.2 -except: +except ModuleNotFoundError: print("dnspython3 is missing, use 'pip install dnspython3' to install it.") sys.exit(0) From bb6002a3ff48432915d96e97351e092b78a3c149 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 7 Aug 2018 18:14:29 +0200 Subject: [PATCH 111/153] fix: [cleanup] Quick clean up on yaml load function --- misp_modules/modules/expansion/sigma_syntax_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/sigma_syntax_validator.py b/misp_modules/modules/expansion/sigma_syntax_validator.py index 0d5226f..6452654 100644 --- a/misp_modules/modules/expansion/sigma_syntax_validator.py +++ b/misp_modules/modules/expansion/sigma_syntax_validator.py @@ -21,7 +21,7 @@ def handler(q=False): return misperrors config = SigmaConfiguration() try: - parser = SigmaParser(yaml.load(request.get('sigma')), config) + parser = SigmaParser(yaml.safe_load(request.get('sigma')), config) result = ("Syntax valid: {}".format(parser.values)) except Exception as e: result = ("Syntax error: {}".format(str(e))) From 0666a60b3dbc1a5f2b54e546ff70b08e7f359fba Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 7 Aug 2018 18:15:15 +0200 Subject: [PATCH 112/153] fix: [cleanup] Quick clean up on exception type --- misp_modules/modules/expansion/yara_syntax_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/yara_syntax_validator.py b/misp_modules/modules/expansion/yara_syntax_validator.py index 4953c41..57c71ea 100644 --- a/misp_modules/modules/expansion/yara_syntax_validator.py +++ b/misp_modules/modules/expansion/yara_syntax_validator.py @@ -2,7 +2,7 @@ import json import requests try: import yara -except: +except ModuleNotFoundError: print("yara is missing, use 'pip3 install yara' to install it.") misperrors = {'error': 'Error'} From 61232ad93e5bda5dd98796a964c74b8a8d179d2d Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Wed, 8 Aug 2018 17:00:10 +0200 Subject: [PATCH 113/153] new: Expansion hover module to check spamhaus DBL for a domain name --- .../modules/expansion/dbl_spamhaus.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 misp_modules/modules/expansion/dbl_spamhaus.py diff --git a/misp_modules/modules/expansion/dbl_spamhaus.py b/misp_modules/modules/expansion/dbl_spamhaus.py new file mode 100644 index 0000000..f78cb74 --- /dev/null +++ b/misp_modules/modules/expansion/dbl_spamhaus.py @@ -0,0 +1,60 @@ +import json +import datetime +from collections import defaultdict + +try: + import dns.resolver + resolver = dns.resolver.Resolver() + resolver.timeout = 0.2 + resolver.lifetime = 0.2 +except ModuleNotFoundError: + print("dnspython3 is missing, use 'pip install dnspython3' to install it.") + sys.exit(0) + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['domain', 'domain|ip', 'hostname', 'hostname|port'], 'output': ['text']} +moduleinfo = {'version': '0.1', 'author': 'Christian Studer', + 'description': 'Checks Spamhaus DBL for a domain name.', + 'module-type': ['expansion', 'hover']} +moduleconfig = [] + +dbl = 'dbl.spamhaus.org' +dbl_mapping = {'127.0.1.2': 'spam domain', + '127.0.1.4': 'phish domain', + '127.0.1.5': 'malware domain', + '127.0.1.6': 'botnet C&C domain', + '127.0.1.102': 'abused legit spam', + '127.0.1.103': 'abused spammed redirector domain', + '127.0.1.104': 'abused legit phish', + '127.0.1.105': 'abused legit malware', + '127.0.1.106': 'abused legit botnet C&C', + '127.0.1.255': 'IP queries prohibited!'} + +def fetch_requested_value(request): + for attribute_type in mispattributes['input']: + if request.get(attribute_type): + return request[attribute_type].split('|')[0] + return None + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + requested_value = fetch_requested_value(request) + if requested_value is None: + misperrors['error'] = "Unsupported attributes type" + return misperrors + query = "{}.{}".format(requested_value, dbl) + try: + query_result = resolver.query(query, 'A')[0] + result = "{} - {}".format(requested_value, dbl_mapping[str(query_result)]) + except Exception as e: + result = e + return {'results': [{'types': mispattributes.get('output'), 'values': result}]} + +def introspection(): + return mispattributes + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 90baa1dd5a1c931e02a1a91dda351dde7983c3b6 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Wed, 8 Aug 2018 17:05:22 +0200 Subject: [PATCH 114/153] add: Added DBL spamhaus module documentation and in expansion init file --- README.md | 1 + misp_modules/modules/expansion/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cbf9550..ad4b098 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [countrycode](misp_modules/modules/expansion/countrycode.py) - a hover module to tell you what country a URL belongs to. * [CrowdStrike Falcon](misp_modules/modules/expansion/crowdstrike_falcon.py) - an expansion module to expand using CrowdStrike Falcon Intel Indicator API. * [CVE](misp_modules/modules/expansion/cve.py) - a hover module to give more information about a vulnerability (CVE). +* [DBL Spamhaus](misp_modules/modules/expansion/dbl_spamhaus.py) - a hover module to check Spamhaus DBL for a domain name. * [DNS](misp_modules/modules/expansion/dns.py) - a simple module to resolve MISP attributes like hostname and domain to expand IP addresses attributes. * [DomainTools](misp_modules/modules/expansion/domaintools.py) - a hover and expansion module to get information from [DomainTools](http://www.domaintools.com/) whois. * [EUPI](misp_modules/modules/expansion/eupi.py) - a hover and expansion module to get information about an URL from the [Phishing Initiative project](https://phishing-initiative.eu/?lang=en). diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index cda3af5..c6e81a7 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,3 +1,3 @@ from . import _vmray -__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries'] +__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus'] From bdbf5388934478d79fee2c5c3ae21b0642d3cd78 Mon Sep 17 00:00:00 2001 From: David J Date: Fri, 10 Aug 2018 16:00:01 -0500 Subject: [PATCH 115/153] Create urlscan.py --- misp_modules/modules/expansion/urlscan.py | 269 ++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 misp_modules/modules/expansion/urlscan.py diff --git a/misp_modules/modules/expansion/urlscan.py b/misp_modules/modules/expansion/urlscan.py new file mode 100644 index 0000000..8f4067c --- /dev/null +++ b/misp_modules/modules/expansion/urlscan.py @@ -0,0 +1,269 @@ +import json +import requests +import logging +import sys +import time +# Need base64 if encoding data for attachments, but disabled for now +# import base64 + +log = logging.getLogger('urlscan') +log.setLevel(logging.DEBUG) +ch = logging.StreamHandler(sys.stdout) +ch.setLevel(logging.DEBUG) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +ch.setFormatter(formatter) +log.addHandler(ch) + +moduleinfo = { + 'version': '0.1', + 'author': 'Dave Johnson', + 'description': 'Module to query urlscan.io', + 'module-type': ['expansion'] + } + +moduleconfig = ['apikey'] +misperrors = {'error': 'Error'} +mispattributes = { + 'input': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url'], + 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url', 'text', 'link'] + } + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + if (request.get('config')): + if (request['config'].get('apikey') is None): + misperrors['error'] = 'urlscan apikey is missing' + return misperrors + client = urlscanAPI(request['config']['apikey']) + + r = {'results': []} + + if 'ip-src' in request: + r['results'] += lookup_indicator(client, request['ip-src']) + if 'ip-dst' in request: + r['results'] += lookup_indicator(client, request['ip-dst']) + if 'domain' in request: + r['results'] += lookup_indicator(client, request['domain']) + if 'hostname' in request: + r['results'] += lookup_indicator(client, request['hostname']) + if 'url' in request: + r['results'] += lookup_indicator(client, request['url']) + + uniq = [] + for item in r['results']: + if item not in uniq: + uniq.append(item) + r['results'] = uniq + return r + + +def lookup_indicator(client, query): + result = client.search_url(query) + log.debug('RESULTS: ' + json.dumps(result)) + r = [] + if result.get('page'): + if result['page'].get('domain'): + misp_val = result['page']['domain'] + misp_comment = "Domain associated with {} (source: urlscan.io)".format(query) + r.append({'types': 'domain', + 'categories': ['Network activity'], + 'values': misp_val, + 'comment': misp_comment}) + + if result['page'].get('ip'): + misp_val = result['page']['ip'] + misp_comment = "IP associated with {} (source: urlscan.io)".format(query) + r.append({'types': 'ip-dst', + 'categories': ['Network activity'], + 'values': misp_val, + 'comment': misp_comment}) + + if result['page'].get('country'): + misp_val = 'Country: ' + result['page']['country'] + if result['page'].get('city'): + misp_val += ', City: ' + result['page']['city'] + misp_comment = "Location associated with {} (source: urlscan.io)".format(query) + r.append({'types': 'text', + 'categories': ['External analysis'], + 'values': misp_val, + 'comment': misp_comment}) + + if result['page'].get('asn'): + misp_val = result['page']['asn'] + misp_comment = "ASN associated with {} (source: urlscan.io)".format(query) + r.append({'types': 'AS', 'categories': ['Network activity'], 'values': misp_val, 'comment': misp_comment}) + + if result['page'].get('asnname'): + misp_val = result['page']['asnname'] + misp_comment = "ASN name associated with {} (source: urlscan.io)".format(query) + r.append({'types': 'text', + 'categories': ['External analysis'], + 'values': misp_val, + 'comment': misp_comment}) + + if result.get('stats'): + if result['stats'].get('malicious'): + log.debug('There is something in results > stats > malicious') + threat_list = set() + + if 'matches' in result['meta']['processors']['gsb']['data']: + for item in result['meta']['processors']['gsb']['data']['matches']: + if item['threatType']: + threat_list.add(item['threatType']) + + threat_list = ', '.join(threat_list) + log.debug('threat_list values are: \'' + threat_list + '\'') + + if threat_list: + misp_val = '{} threat(s) detected'.format(threat_list) + misp_comment = '{} malicious indicator(s) were present on ' \ + '{} (source: urlscan.io)'.format(result['stats']['malicious'], query, threat_list) + r.append({'types': 'text', + 'categories': ['External analysis'], + 'values': misp_val, + 'comment': misp_comment}) + + if result.get('lists'): + if result['lists'].get('urls'): + for url in result['lists']['urls']: + url = url.lower() + if 'office' in url: + misp_val = 'Possible Microsoft Office themed phishing page' + misp_comment = 'There was resource containing an \'Office\' string in the URL.' + elif 'o365' in url or '0365' in url: + misp_val = 'Possible Microsoft O365 themed phishing page' + misp_comment = 'There was resource containing an \'O365\' string in the URL.' + elif 'microsoft' in url: + misp_val = 'Possible Microsoft themed phishing page' + misp_comment = 'There was resource containing an \'Office\' string in the URL.' + elif 'paypal' in url: + misp_val = 'Possible PayPal themed phishing page' + misp_comment = 'There was resource containing a \'PayPal\' string in the URL.' + elif 'onedrive' in url: + misp_val = 'Possible OneDrive themed phishing page' + misp_comment = 'There was resource containing a \'OneDrive\' string in the URL.' + elif 'docusign' in url: + misp_val = 'Possible DocuSign themed phishing page' + misp_comment = 'There was resource containing a \'DocuSign\' string in the URL' + r.append({'types': 'text', + 'categories': ['External analysis'], + 'values': misp_val, + 'comment': misp_comment}) + + if result.get('task'): + if result['task'].get('reportURL'): + misp_val = result['task']['reportURL'] + misp_comment = 'Link to full report (source: urlscan.io)' + r.append({'types': 'link', + 'categories': ['External analysis'], + 'values': misp_val, + 'comment': misp_comment}) + + if result['task'].get('screenshotURL'): + image_url = result['task']['screenshotURL'] + misp_comment = 'Link to screenshot (source: urlscan.io)' + r.append({'types': 'link', + 'categories': ['External analysis'], + 'values': image_url, + 'comment': misp_comment}) + ### TO DO ### + ### Add ability to add an in-line screenshot of the target website into an attribute + # screenshot = requests.get(image_url).content + # r.append({'types': ['attachment'], + # 'categories': ['External analysis'], + # 'values': image_url, + # 'image': str(base64.b64encode(screenshot), 'utf-8'), + # 'comment': 'Screenshot of website'}) + + if result['task'].get('domURL'): + misp_val = result['task']['domURL'] + misp_comment = 'Link to DOM (source: urlscan.io)' + r.append({'types': 'link', + 'categories': ['External analysis'], + 'values': misp_val, + 'comment': misp_comment}) + + return r + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo + + +class urlscanAPI(): + def __init__(self, apikey=None, uuid=None): + self.key = apikey + self.uuid = uuid + + def request(self, query): + log.debug('From request function with the parameter: ' + query) + payload = {'url': query} + headers = {'API-Key': self.key, + 'Content-Type': "application/json", + 'Cache-Control': "no-cache"} + + # Troubleshooting problems with initial search request + log.debug('PAYLOAD: ' + json.dumps(payload)) + log.debug('HEADERS: ' + json.dumps(headers)) + + search_url_string = "https://urlscan.io/api/v1/scan/" + response = requests.request("POST", + search_url_string, + data=json.dumps(payload), + headers=headers) + + # HTTP 400 - Bad Request + if response.status_code == 400: + raise Exception('HTTP Error 400 - Bad Request') + + # HTTP 404 - Not found + if response.status_code == 404: + raise Exception('HTTP Error 404 - These are not the droids you\'re looking for') + + # Any other status code + if response.status_code != 200: + raise Exception('HTTP Error ' + str(response.status_code)) + + if response.text: + response = json.loads(response.content.decode("utf-8")) + time.sleep(3) + self.uuid = response['uuid'] + + # Strings for to check for errors on the results page + # Null response string for any unavailable resources + null_response_string = '"status": 404' + # Redirect string accounting for 301/302/303/307/308 status codes + redirect_string = '"status": 30' + # Normal response string with 200 status code + normal_response_string = '"status": 200' + + results_url_string = "https://urlscan.io/api/v1/result/" + self.uuid + log.debug('Results URL: ' + results_url_string) + + # Need to wait for results to process and check if they are valid + tries = 10 + while tries >= 0: + results = requests.request("GET", results_url_string) + log.debug('Made a GET request') + results = results.content.decode("utf-8") + # checking if there is a 404 status code and no available resources + if null_response_string in results and \ + redirect_string not in results and \ + normal_response_string not in results: + log.debug('Results not processed. Please check again later.') + time.sleep(3) + tries -= 1 + else: + return json.loads(results) + raise Exception('Results contained a 404 status error and could not be processed.') + + def search_url(self, query): + log.debug('From search_url with parameter: ' + query) + return self.request(query) From a697f653822b893d95a4142a4d88cbf66821f208 Mon Sep 17 00:00:00 2001 From: David J Date: Tue, 14 Aug 2018 10:51:15 -0500 Subject: [PATCH 116/153] Add error handling for DNS failures, reduce imports, and simplify misp_comments --- misp_modules/modules/expansion/urlscan.py | 78 +++++++++++------------ 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/misp_modules/modules/expansion/urlscan.py b/misp_modules/modules/expansion/urlscan.py index 8f4067c..a0adc25 100644 --- a/misp_modules/modules/expansion/urlscan.py +++ b/misp_modules/modules/expansion/urlscan.py @@ -3,8 +3,6 @@ import requests import logging import sys import time -# Need base64 if encoding data for attachments, but disabled for now -# import base64 log = logging.getLogger('urlscan') log.setLevel(logging.DEBUG) @@ -15,18 +13,19 @@ ch.setFormatter(formatter) log.addHandler(ch) moduleinfo = { - 'version': '0.1', - 'author': 'Dave Johnson', - 'description': 'Module to query urlscan.io', - 'module-type': ['expansion'] - } + 'version': '0.1', + 'author': 'Dave Johnson', + 'description': 'Module to query urlscan.io', + 'module-type': ['expansion'] +} moduleconfig = ['apikey'] misperrors = {'error': 'Error'} mispattributes = { - 'input': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url'], - 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url', 'text', 'link'] - } + 'input': ['hostname', 'domain', 'url'], + 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url', 'text', 'link'] +} + def handler(q=False): if q is False: @@ -51,8 +50,15 @@ def handler(q=False): if 'url' in request: r['results'] += lookup_indicator(client, request['url']) + # Return any errors generated from lookup to the UI and remove duplicates + uniq = [] + log.debug(r['results']) for item in r['results']: + log.debug(item) + if 'error' in item: + misperrors['error'] = item['error'] + return misperrors if item not in uniq: uniq.append(item) r['results'] = uniq @@ -63,10 +69,19 @@ def lookup_indicator(client, query): result = client.search_url(query) log.debug('RESULTS: ' + json.dumps(result)) r = [] + misp_comment = "{}: Enriched via the urlscan module".format(query) + + # Determine if the page is reachable + for request in result['data']['requests']: + if request['response'].get('failed'): + if request['response']['failed']['errorText']: + log.debug('The page could not load') + r.append( + {'error': 'Domain could not be resolved: {}'.format(request['response']['failed']['errorText'])}) + if result.get('page'): if result['page'].get('domain'): misp_val = result['page']['domain'] - misp_comment = "Domain associated with {} (source: urlscan.io)".format(query) r.append({'types': 'domain', 'categories': ['Network activity'], 'values': misp_val, @@ -74,17 +89,15 @@ def lookup_indicator(client, query): if result['page'].get('ip'): misp_val = result['page']['ip'] - misp_comment = "IP associated with {} (source: urlscan.io)".format(query) r.append({'types': 'ip-dst', 'categories': ['Network activity'], 'values': misp_val, 'comment': misp_comment}) if result['page'].get('country'): - misp_val = 'Country: ' + result['page']['country'] + misp_val = 'country: ' + result['page']['country'] if result['page'].get('city'): - misp_val += ', City: ' + result['page']['city'] - misp_comment = "Location associated with {} (source: urlscan.io)".format(query) + misp_val += ', city: ' + result['page']['city'] r.append({'types': 'text', 'categories': ['External analysis'], 'values': misp_val, @@ -92,12 +105,10 @@ def lookup_indicator(client, query): if result['page'].get('asn'): misp_val = result['page']['asn'] - misp_comment = "ASN associated with {} (source: urlscan.io)".format(query) - r.append({'types': 'AS', 'categories': ['Network activity'], 'values': misp_val, 'comment': misp_comment}) + r.append({'types': 'AS', 'categories': ['External analysis'], 'values': misp_val, 'comment': misp_comment}) if result['page'].get('asnname'): misp_val = result['page']['asnname'] - misp_comment = "ASN name associated with {} (source: urlscan.io)".format(query) r.append({'types': 'text', 'categories': ['External analysis'], 'values': misp_val, @@ -118,8 +129,6 @@ def lookup_indicator(client, query): if threat_list: misp_val = '{} threat(s) detected'.format(threat_list) - misp_comment = '{} malicious indicator(s) were present on ' \ - '{} (source: urlscan.io)'.format(result['stats']['malicious'], query, threat_list) r.append({'types': 'text', 'categories': ['External analysis'], 'values': misp_val, @@ -130,23 +139,17 @@ def lookup_indicator(client, query): for url in result['lists']['urls']: url = url.lower() if 'office' in url: - misp_val = 'Possible Microsoft Office themed phishing page' - misp_comment = 'There was resource containing an \'Office\' string in the URL.' + misp_val = "Possible Office-themed phishing" elif 'o365' in url or '0365' in url: - misp_val = 'Possible Microsoft O365 themed phishing page' - misp_comment = 'There was resource containing an \'O365\' string in the URL.' + misp_val = "Possible O365-themed phishing" elif 'microsoft' in url: - misp_val = 'Possible Microsoft themed phishing page' - misp_comment = 'There was resource containing an \'Office\' string in the URL.' + misp_val = "Possible Microsoft-themed phishing" elif 'paypal' in url: - misp_val = 'Possible PayPal themed phishing page' - misp_comment = 'There was resource containing a \'PayPal\' string in the URL.' + misp_val = "Possible PayPal-themed phishing" elif 'onedrive' in url: - misp_val = 'Possible OneDrive themed phishing page' - misp_comment = 'There was resource containing a \'OneDrive\' string in the URL.' + misp_val = "Possible OneDrive-themed phishing" elif 'docusign' in url: - misp_val = 'Possible DocuSign themed phishing page' - misp_comment = 'There was resource containing a \'DocuSign\' string in the URL' + misp_val = "Possible DocuSign-themed phishing" r.append({'types': 'text', 'categories': ['External analysis'], 'values': misp_val, @@ -155,7 +158,6 @@ def lookup_indicator(client, query): if result.get('task'): if result['task'].get('reportURL'): misp_val = result['task']['reportURL'] - misp_comment = 'Link to full report (source: urlscan.io)' r.append({'types': 'link', 'categories': ['External analysis'], 'values': misp_val, @@ -163,7 +165,6 @@ def lookup_indicator(client, query): if result['task'].get('screenshotURL'): image_url = result['task']['screenshotURL'] - misp_comment = 'Link to screenshot (source: urlscan.io)' r.append({'types': 'link', 'categories': ['External analysis'], 'values': image_url, @@ -177,14 +178,6 @@ def lookup_indicator(client, query): # 'image': str(base64.b64encode(screenshot), 'utf-8'), # 'comment': 'Screenshot of website'}) - if result['task'].get('domURL'): - misp_val = result['task']['domURL'] - misp_comment = 'Link to DOM (source: urlscan.io)' - r.append({'types': 'link', - 'categories': ['External analysis'], - 'values': misp_val, - 'comment': misp_comment}) - return r @@ -262,6 +255,7 @@ class urlscanAPI(): tries -= 1 else: return json.loads(results) + raise Exception('Results contained a 404 status error and could not be processed.') def search_url(self, query): From 7deeb95820e1e4099208496025807e6ae6ef1164 Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Tue, 21 Aug 2018 11:13:08 +0200 Subject: [PATCH 117/153] fix: ta_import - bugfixes --- .../import_mod/threatanalyzer_import.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/misp_modules/modules/import_mod/threatanalyzer_import.py b/misp_modules/modules/import_mod/threatanalyzer_import.py index 916628e..2e3a507 100755 --- a/misp_modules/modules/import_mod/threatanalyzer_import.py +++ b/misp_modules/modules/import_mod/threatanalyzer_import.py @@ -15,7 +15,7 @@ misperrors = {'error': 'Error'} userConfig = {} inputSource = ['file'] -moduleinfo = {'version': '0.8', 'author': 'Christophe Vandeplas', +moduleinfo = {'version': '0.9', 'author': 'Christophe Vandeplas', 'description': 'Import for ThreatAnalyzer archive.zip/analysis.json files', 'module-type': ['import']} @@ -45,7 +45,7 @@ def handler(q=False): if re.match(r"Analysis/proc_\d+/modified_files/mapping\.log", zip_file_name): with zf.open(zip_file_name, mode='r', pwd=None) as fp: file_data = fp.read() - for line in file_data.decode().split('\n'): + for line in file_data.decode("utf-8", 'ignore').split('\n'): if not line: continue if line.count('|') == 3: @@ -55,7 +55,8 @@ def handler(q=False): l_fname = cleanup_filepath(l_fname) if l_fname: if l_size == 0: - pass # FIXME create an attribute for the filename/path + results.append({'values': l_fname, 'type': 'filename', 'to_ids': True, + 'categories': ['Artifacts dropped', 'Payload delivery'], 'comment': ''}) else: # file is a non empty sample, upload the sample later modified_files_mapping[l_md5] = l_fname @@ -144,13 +145,14 @@ def process_analysis_json(analysis_json): # ) yield({'values': connection_section_connection['@remote_hostname'], 'type': 'hostname', 'to_ids': True, 'comment': ''}) if 'http_command' in connection_section_connection: - # print('connection_section_connection HTTP COMMAND: {}\t{}'.format( - # connection_section_connection['http_command']['@method'], # comment - # connection_section_connection['http_command']['@url']) # url - # ) - val = cleanup_url(connection_section_connection['http_command']['@url']) - if val: - yield({'values': val, 'type': 'url', 'categories': ['Network activity'], 'to_ids': True, 'comment': connection_section_connection['http_command']['@method']}) + for http_command in connection_section_connection['http_command']: + # print('connection_section_connection HTTP COMMAND: {}\t{}'.format( + # connection_section_connection['http_command']['@method'], # comment + # connection_section_connection['http_command']['@url']) # url + # ) + val = cleanup_url(http_command['@url']) + if val: + yield({'values': val, 'type': 'url', 'categories': ['Network activity'], 'to_ids': True, 'comment': http_command['@method']}) if 'http_header' in connection_section_connection: for http_header in connection_section_connection['http_header']: @@ -453,9 +455,9 @@ def cleanup_filepath(item): '\\AppData\\Roaming\\Adobe\\Acrobat\\9.0\\UserCache.bin', '\\AppData\\Roaming\\Macromedia\\Flash Player\\macromedia.com\\support\\flashplayer\\sys\\settings.sol', - '\\AppData\\Roaming\Adobe\\Flash Player\\NativeCache\\', + '\\AppData\\Roaming\\Adobe\\Flash Player\\NativeCache\\', 'C:\\Windows\\AppCompat\\Programs\\', - 'C:\~' # caused by temp file created by MS Office when opening malicious doc/xls/... + 'C:\\~' # caused by temp file created by MS Office when opening malicious doc/xls/... } if list_in_string(noise_substrings, item): return None @@ -531,4 +533,3 @@ def introspection(): def version(): moduleinfo['config'] = moduleconfig return moduleinfo - From d15cbe58fe47d137554883084e02540b7f2ab9a8 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 30 Aug 2018 20:41:49 +0200 Subject: [PATCH 118/153] fix: Quick cleanup --- misp_modules/modules/export_mod/goamlexport.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/export_mod/goamlexport.py b/misp_modules/modules/export_mod/goamlexport.py index 961a11b..f09f2e6 100644 --- a/misp_modules/modules/export_mod/goamlexport.py +++ b/misp_modules/modules/export_mod/goamlexport.py @@ -1,4 +1,5 @@ -import json, datetime, base64 +import json +import base64 from pymisp import MISPEvent from collections import defaultdict, Counter @@ -67,7 +68,7 @@ class GoAmlGeneration(object): try: report_code.append(obj.get_attributes_by_relation('report-code')[0].value.split(' ')[0]) currency_code.append(obj.get_attributes_by_relation('currency-code')[0].value) - except: + except IndexError: print('report_code or currency_code error') self.uuids, self.report_codes, self.currency_codes = uuids, report_code, currency_code From 35f3a5e43f92eff195f98fc312fa53f922155090 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 30 Aug 2018 20:45:29 +0200 Subject: [PATCH 119/153] fix: Quick cleanup --- misp_modules/modules/expansion/rbl.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/rbl.py b/misp_modules/modules/expansion/rbl.py index 49de421..7aac103 100644 --- a/misp_modules/modules/expansion/rbl.py +++ b/misp_modules/modules/expansion/rbl.py @@ -96,13 +96,12 @@ def handler(q=False): txt = resolver.query(query,'TXT') listed.append(query) info.append(str(txt[0])) - except: + except Exception: continue result = {} for l, i in zip(listed, info): result[l] = i - r = {'results': [{'types': mispattributes.get('output'), 'values': json.dumps(result)}]} - return r + return {'results': [{'types': mispattributes.get('output'), 'values': json.dumps(result)}]} def introspection(): return mispattributes From b0be965e576a1fb8e02d36bc4a34ecde5d967e4b Mon Sep 17 00:00:00 2001 From: SuRb0 <1809870+surbo@users.noreply.github.com> Date: Thu, 30 Aug 2018 19:41:34 -0500 Subject: [PATCH 120/153] Update urlscan.py Added hash to the search so you can take advantage of the new file down load function on urlscan.io. You can use this to pivot on file hashes and find out domains that hosting the same malicious file. --- misp_modules/modules/expansion/urlscan.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/urlscan.py b/misp_modules/modules/expansion/urlscan.py index a0adc25..ecd1a50 100644 --- a/misp_modules/modules/expansion/urlscan.py +++ b/misp_modules/modules/expansion/urlscan.py @@ -22,8 +22,8 @@ moduleinfo = { moduleconfig = ['apikey'] misperrors = {'error': 'Error'} mispattributes = { - 'input': ['hostname', 'domain', 'url'], - 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url', 'text', 'link'] + 'input': ['hostname', 'domain', 'url', 'hash'], + 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url', 'text', 'link', 'hash'] } @@ -49,6 +49,8 @@ def handler(q=False): r['results'] += lookup_indicator(client, request['hostname']) if 'url' in request: r['results'] += lookup_indicator(client, request['url']) + f 'hash' in request: + r['results'] += lookup_indicator(client, request['hash']) # Return any errors generated from lookup to the UI and remove duplicates From 179430d69db5e45fa0c0e815a934fd87b69135c1 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Fri, 31 Aug 2018 21:38:53 +0200 Subject: [PATCH 121/153] fix: Some cleanup and output types fixed - hashes types specified in output --- misp_modules/modules/expansion/virustotal.py | 304 ++++++++----------- 1 file changed, 133 insertions(+), 171 deletions(-) mode change 100755 => 100644 misp_modules/modules/expansion/virustotal.py diff --git a/misp_modules/modules/expansion/virustotal.py b/misp_modules/modules/expansion/virustotal.py old mode 100755 new mode 100644 index 3997ee6..404f621 --- a/misp_modules/modules/expansion/virustotal.py +++ b/misp_modules/modules/expansion/virustotal.py @@ -2,201 +2,163 @@ import json import requests from requests import HTTPError import base64 +from collections import defaultdict misperrors = {'error': 'Error'} mispattributes = {'input': ['hostname', 'domain', "ip-src", "ip-dst", "md5", "sha1", "sha256", "sha512"], 'output': ['domain', "ip-src", "ip-dst", "text", "md5", "sha1", "sha256", "sha512", "ssdeep", - "authentihash", "filename"] - } + "authentihash", "filename"]} # possible module-types: 'expansion', 'hover' or both -moduleinfo = {'version': '2', 'author': 'Hannah Ward', +moduleinfo = {'version': '3', 'author': 'Hannah Ward', 'description': 'Get information from virustotal', 'module-type': ['expansion']} # config fields that your code expects from the site admin moduleconfig = ["apikey", "event_limit"] -limit = 5 # Default -comment = '%s: Enriched via VT' +comment = '{}: Enriched via VirusTotal' +hash_types = ["md5", "sha1", "sha256", "sha512"] +class VirusTotalRequest(object): + def __init__(self, config): + self.apikey = config['apikey'] + self.limit = int(config.get('event_limit', 5)) + self.base_url = "https://www.virustotal.com/vtapi/v2/{}/report" + self.results = defaultdict(set) + self.to_return = [] + self.input_types_mapping = {'ip-src': self.get_ip, 'ip-dst': self.get_ip, + 'domain': self.get_domain, 'hostname': self.get_domain, + 'md5': self.get_hash, 'sha1': self.get_hash, + 'sha256': self.get_hash, 'sha512': self.get_hash} + self.output_types_mapping = {'submission_names': 'filename', 'ssdeep': 'ssdeep', + 'authentihash': 'authentihash', 'ITW_urls': 'url'} + + def parse_request(self, attribute_type, attribute_value): + error = self.input_types_mapping[attribute_type](attribute_value) + if error is not None: + return error + for key, values in self.results.items(): + if isinstance(key, tuple): + types, comment = key + self.to_return.append({'types': list(types), 'values': list(values), 'comment': comment}) + else: + self.to_return.append({'types': key, 'values': list(values)}) + return self.to_return + + def get_domain(self, domain, do_not_recurse=False): + req = requests.get(self.base_url.format('domain'), params={'domain': domain, 'apikey': self.apikey}) + try: + req.raise_for_status() + req = req.json() + except HTTPError as e: + return str(e) + if req["response_code"] == 0: + # Nothing found + return [] + if "resolutions" in req: + for res in req["resolutions"][:self.limit]: + ip_address = res["ip_address"] + self.results[(("ip-dst", "ip-src"), comment.format(domain))].add(ip_address) + # Pivot from here to find all domain info + if not do_not_recurse: + error = self.get_ip(ip_address, True) + if error is not None: + return error + self.get_more_info(req) + + def get_hash(self, _hash): + req = requests.get(self.base_url.format('file'), params={'resource': _hash, 'apikey': self.apikey, 'allinfo': 1}) + try: + req.raise_for_status() + req = req.json() + except HTTPError as e: + return str(e) + if req["response_code"] == 0: + # Nothing found + return [] + self.get_more_info(req) + + def get_ip(self, ip, do_not_recurse=False): + req = requests.get(self.base_url.format('ip-address'), params={'ip': ip, 'apikey': self.apikey}) + try: + req.raise_for_status() + req = req.json() + except HTTPError as e: + return str(e) + if req["response_code"] == 0: + # Nothing found + return [] + if "resolutions" in req: + for res in req["resolutions"][:self.limit]: + hostname = res["hostname"] + self.results[(("domain",), comment.format(ip))].add(hostname) + # Pivot from here to find all domain info + if not do_not_recurse: + error = self.get_domain(hostname, True) + if error is not None: + return error + self.get_more_info(req) + + def find_all(self, data): + hashes = [] + if isinstance(data, dict): + for key, value in data.items(): + if key in hash_types: + print(key) + self.results[key].add(value) + hashes.append(value) + else: + if isinstance(value, (dict, list)): + hashes.extend(self.find_all(value)) + elif isinstance(data, list): + for d in data: + hashes.extend(self.find_all(d)) + return hashes + + def get_more_info(self, req): + # Get all hashes first + hashes = self.find_all(req) + for h in hashes[:self.limit]: + # Search VT for some juicy info + try: + data = requests.get(self.base_url.format('file'), params={'resource': h, 'apikey': apikey, 'allinfo': 1}).json() + except Exception: + continue + # Go through euch key and check if it exists + for VT_type, MISP_type in self.output_types_mapping.items(): + if VT_type in data: + self.results[((MISP_type,), comment.format(h))].add(data[VT_type]) + # Get the malware sample + sample = requests.get(self.base_url[:-6].format('file/download'), params={'hash': h, 'apikey': apikey}) + malsample = sample.content + # It is possible for VT to not give us any submission names + if "submission_names" in data: + self.to_return.append({"types": ["malware-sample"], "categories": ["Payload delivery"], + "values": data["submimssion_names"], "data": str(base64.b64encore(malsample), 'utf-8')}) def handler(q=False): - global limit if q is False: return False - q = json.loads(q) - - key = q["config"]["apikey"] - limit = int(q["config"].get("event_limit", 5)) - - r = {"results": []} - - if "ip-src" in q: - r["results"] += getIP(q["ip-src"], key) - if "ip-dst" in q: - r["results"] += getIP(q["ip-dst"], key) - if "domain" in q: - r["results"] += getDomain(q["domain"], key) - if 'hostname' in q: - r["results"] += getDomain(q['hostname'], key) - if 'md5' in q: - r["results"] += getHash(q['md5'], key) - if 'sha1' in q: - r["results"] += getHash(q['sha1'], key) - if 'sha256' in q: - r["results"] += getHash(q['sha256'], key) - if 'sha512' in q: - r["results"] += getHash(q['sha512'], key) - - uniq = [] - for res in r["results"]: - if res not in uniq: - uniq.append(res) - r["results"] = uniq - return r - - -def getHash(hash, key, do_not_recurse=False): - req = requests.get("https://www.virustotal.com/vtapi/v2/file/report", - params={"allinfo": 1, "apikey": key, 'resource': hash}) - try: - req.raise_for_status() - req = req.json() - except HTTPError as e: - misperrors['error'] = str(e) + if not q.get('config') or not q['config'].get('apikey'): + misperrors['error']: "A VirusTotal api key is required for this module." return misperrors - - if req["response_code"] == 0: - # Nothing found - return [] - - return getMoreInfo(req, key) - - -def getIP(ip, key, do_not_recurse=False): - global limit - toReturn = [] - req = requests.get("https://www.virustotal.com/vtapi/v2/ip-address/report", - params={"ip": ip, "apikey": key}) - try: - req.raise_for_status() - req = req.json() - except HTTPError as e: - misperrors['error'] = str(e) + del q['module'] + query = VirusTotalRequest(q.pop('config')) + r = query.parse_request(*list(q.items())[0]) + if isinstance(r, str): + misperrors['error'] = r return misperrors + return {'results': r} - if req["response_code"] == 0: - # Nothing found - return [] - - if "resolutions" in req: - for res in req["resolutions"][:limit]: - toReturn.append({"types": ["domain"], "values": [res["hostname"]], "comment": comment % ip}) - # Pivot from here to find all domain info - if not do_not_recurse: - toReturn += getDomain(res["hostname"], key, True) - - toReturn += getMoreInfo(req, key) - return toReturn - - -def getDomain(domain, key, do_not_recurse=False): - global limit - toReturn = [] - req = requests.get("https://www.virustotal.com/vtapi/v2/domain/report", - params={"domain": domain, "apikey": key}) - try: - req.raise_for_status() - req = req.json() - except HTTPError as e: - misperrors['error'] = str(e) - return misperrors - - if req["response_code"] == 0: - # Nothing found - return [] - - if "resolutions" in req: - for res in req["resolutions"][:limit]: - toReturn.append({"types": ["ip-dst", "ip-src"], "values": [res["ip_address"]], "comment": comment % domain}) - # Pivot from here to find all info on IPs - if not do_not_recurse: - toReturn += getIP(res["ip_address"], key, True) - if "subdomains" in req: - for subd in req["subdomains"]: - toReturn.append({"types": ["domain"], "values": [subd], "comment": comment % domain}) - toReturn += getMoreInfo(req, key) - return toReturn - - -def findAll(data, keys): - a = [] - if isinstance(data, dict): - for key in data.keys(): - if key in keys: - a.append(data[key]) - else: - if isinstance(data[key], (dict, list)): - a += findAll(data[key], keys) - if isinstance(data, list): - for i in data: - a += findAll(i, keys) - - return a - - -def getMoreInfo(req, key): - global limit - r = [] - # Get all hashes first - hashes = [] - hashes = findAll(req, ["md5", "sha1", "sha256", "sha512"]) - r.append({"types": ["freetext"], "values": hashes}) - for hsh in hashes[:limit]: - # Search VT for some juicy info - try: - data = requests.get("http://www.virustotal.com/vtapi/v2/file/report", - params={"allinfo": 1, "apikey": key, "resource": hsh} - ).json() - except: - continue - - # Go through each key and check if it exists - if "submission_names" in data: - r.append({'types': ["filename"], "values": data["submission_names"], "comment": comment % hsh}) - - if "ssdeep" in data: - r.append({'types': ["ssdeep"], "values": [data["ssdeep"]], "comment": comment % hsh}) - - if "authentihash" in data: - r.append({"types": ["authentihash"], "values": [data["authentihash"]], "comment": comment % hsh}) - - if "ITW_urls" in data: - r.append({"types": ["url"], "values": data["ITW_urls"], "comment": comment % hsh}) - - # Get the malware sample - sample = requests.get("https://www.virustotal.com/vtapi/v2/file/download", - params={"hash": hsh, "apikey": key}) - - malsample = sample.content - - # It is possible for VT to not give us any submission names - if "submission_names" in data: - r.append({"types": ["malware-sample"], - "categories": ["Payload delivery"], - "values": data["submission_names"], - "data": str(base64.b64encode(malsample), 'utf-8') - } - ) - - return r - +def get_ip(ip, key): + params = {'ip': ip, 'apikey': key} + req = requests.get('https://www.virustotal.com/vtapi/v2/ip-address/report', params=params) + return json.dumps(req.json(), indent=2) def introspection(): return mispattributes - def version(): moduleinfo['config'] = moduleconfig return moduleinfo From 2af947a2deba46ddf2e92cc8f1b0c08be07e1429 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 3 Sep 2018 10:23:05 +0200 Subject: [PATCH 122/153] fix: Removed print --- misp_modules/modules/expansion/virustotal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/misp_modules/modules/expansion/virustotal.py b/misp_modules/modules/expansion/virustotal.py index 404f621..de91e09 100644 --- a/misp_modules/modules/expansion/virustotal.py +++ b/misp_modules/modules/expansion/virustotal.py @@ -104,7 +104,6 @@ class VirusTotalRequest(object): if isinstance(data, dict): for key, value in data.items(): if key in hash_types: - print(key) self.results[key].add(value) hashes.append(value) else: From 936e30b15b97643828b95011bbf7e349acd9f146 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 3 Sep 2018 12:03:42 +0200 Subject: [PATCH 123/153] fix: Multiple attributes parsing support - Fixing one of my previous changes not processing multiple attributes parsing --- misp_modules/modules/expansion/virustotal.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/misp_modules/modules/expansion/virustotal.py b/misp_modules/modules/expansion/virustotal.py index de91e09..4ebbd43 100644 --- a/misp_modules/modules/expansion/virustotal.py +++ b/misp_modules/modules/expansion/virustotal.py @@ -33,10 +33,14 @@ class VirusTotalRequest(object): self.output_types_mapping = {'submission_names': 'filename', 'ssdeep': 'ssdeep', 'authentihash': 'authentihash', 'ITW_urls': 'url'} - def parse_request(self, attribute_type, attribute_value): - error = self.input_types_mapping[attribute_type](attribute_value) - if error is not None: - return error + def parse_request(self, q): + for attribute_type, attribute_value in q.items(): + try: + error = self.input_types_mapping[attribute_type](attribute_value) + except KeyError: + continue + if error is not None: + return error for key, values in self.results.items(): if isinstance(key, tuple): types, comment = key @@ -144,7 +148,7 @@ def handler(q=False): return misperrors del q['module'] query = VirusTotalRequest(q.pop('config')) - r = query.parse_request(*list(q.items())[0]) + r = query.parse_request(q) if isinstance(r, str): misperrors['error'] = r return misperrors From 0ab38feade0e2ead2628c19b12a7ba42709b5141 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 3 Sep 2018 13:17:48 +0200 Subject: [PATCH 124/153] fix: Cleaned up test function not used anymore --- misp_modules/modules/expansion/virustotal.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/misp_modules/modules/expansion/virustotal.py b/misp_modules/modules/expansion/virustotal.py index 4ebbd43..cd4efcd 100644 --- a/misp_modules/modules/expansion/virustotal.py +++ b/misp_modules/modules/expansion/virustotal.py @@ -154,11 +154,6 @@ def handler(q=False): return misperrors return {'results': r} -def get_ip(ip, key): - params = {'ip': ip, 'apikey': key} - req = requests.get('https://www.virustotal.com/vtapi/v2/ip-address/report', params=params) - return json.dumps(req.json(), indent=2) - def introspection(): return mispattributes From 33181bc52baee54433c2cf710ad5f4d6c38c4e61 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 3 Sep 2018 14:29:42 +0200 Subject: [PATCH 125/153] fix: Fixed quick variable issue --- misp_modules/modules/expansion/virustotal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/virustotal.py b/misp_modules/modules/expansion/virustotal.py index cd4efcd..a917f6f 100644 --- a/misp_modules/modules/expansion/virustotal.py +++ b/misp_modules/modules/expansion/virustotal.py @@ -124,7 +124,7 @@ class VirusTotalRequest(object): for h in hashes[:self.limit]: # Search VT for some juicy info try: - data = requests.get(self.base_url.format('file'), params={'resource': h, 'apikey': apikey, 'allinfo': 1}).json() + data = requests.get(self.base_url.format('file'), params={'resource': h, 'apikey': self.apikey, 'allinfo': 1}).json() except Exception: continue # Go through euch key and check if it exists @@ -132,7 +132,7 @@ class VirusTotalRequest(object): if VT_type in data: self.results[((MISP_type,), comment.format(h))].add(data[VT_type]) # Get the malware sample - sample = requests.get(self.base_url[:-6].format('file/download'), params={'hash': h, 'apikey': apikey}) + sample = requests.get(self.base_url[:-6].format('file/download'), params={'hash': h, 'apikey': self.apikey}) malsample = sample.content # It is possible for VT to not give us any submission names if "submission_names" in data: From cdf2f434ce66c2a1c21ecfceb13ac200eeaccf9c Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 3 Sep 2018 14:30:33 +0200 Subject: [PATCH 126/153] fix: Avoiding adding attributes that are already in the event --- misp_modules/modules/expansion/virustotal.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/misp_modules/modules/expansion/virustotal.py b/misp_modules/modules/expansion/virustotal.py index a917f6f..614b8d0 100644 --- a/misp_modules/modules/expansion/virustotal.py +++ b/misp_modules/modules/expansion/virustotal.py @@ -34,7 +34,9 @@ class VirusTotalRequest(object): 'authentihash': 'authentihash', 'ITW_urls': 'url'} def parse_request(self, q): + req_values = set() for attribute_type, attribute_value in q.items(): + req_values.add(attribute_value) try: error = self.input_types_mapping[attribute_type](attribute_value) except KeyError: @@ -42,11 +44,13 @@ class VirusTotalRequest(object): if error is not None: return error for key, values in self.results.items(): - if isinstance(key, tuple): - types, comment = key - self.to_return.append({'types': list(types), 'values': list(values), 'comment': comment}) - else: - self.to_return.append({'types': key, 'values': list(values)}) + values = values.difference(req_values) + if values: + if isinstance(key, tuple): + types, comment = key + self.to_return.append({'types': list(types), 'values': list(values), 'comment': comment}) + else: + self.to_return.append({'types': key, 'values': list(values)}) return self.to_return def get_domain(self, domain, do_not_recurse=False): From ba728f712076986d425c7dbcca0357b4488158d4 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 3 Sep 2018 14:43:51 +0200 Subject: [PATCH 127/153] fix: Fixed 1 variable misuse + cleaned up variable names - Fixed use of 'domain' variable instead of 'email' - Cleaned up variable names to avoid redefinition of built-in variables --- misp_modules/modules/expansion/otx.py | 35 +++++++++++++-------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/misp_modules/modules/expansion/otx.py b/misp_modules/modules/expansion/otx.py index 214e7f0..86685eb 100755 --- a/misp_modules/modules/expansion/otx.py +++ b/misp_modules/modules/expansion/otx.py @@ -32,16 +32,15 @@ def valid_ip(ip): def findAll(data, keys): a = [] if isinstance(data, dict): - for key in data.keys(): + for key, value in data.items(): if key == keys: - a.append(data[key]) + a.append(value) else: - if isinstance(data[key], (dict, list)): - a += findAll(data[key], keys) + if isinstance(value, (dict, list)): + a.extend(findAll(value, keys)) if isinstance(data, list): for i in data: - a += findAll(i, keys) - + a.extend(findAll(i, keys)) return a def valid_email(email): @@ -82,10 +81,10 @@ def handler(q=False): return r -def getHash(hash, key): +def getHash(_hash, key): ret = [] - req = json.loads(requests.get("https://otx.alienvault.com/otxapi/indicator/file/analysis/" + hash).text) + req = json.loads(requests.get("https://otx.alienvault.com/otxapi/indicator/file/analysis/" + _hash).text) for ip in findAll(req, "dst"): if not isBlacklisted(ip) and valid_ip(ip): @@ -102,8 +101,8 @@ def getIP(ip, key): ret = [] req = json.loads( requests.get("https://otx.alienvault.com/otxapi/indicator/ip/malware/" + ip + "?limit=1000").text ) - for hash in findAll(req, "hash"): - ret.append({"types": ["sha256"], "values": [hash]}) + for _hash in findAll(req, "hash"): + ret.append({"types": ["sha256"], "values": [_hash]}) req = json.loads( requests.get("https://otx.alienvault.com/otxapi/indicator/ip/passive_dns/" + ip).text ) @@ -122,21 +121,21 @@ def getDomain(domain, key): req = json.loads( requests.get("https://otx.alienvault.com/otxapi/indicator/domain/malware/" + domain + "?limit=1000").text ) - for hash in findAll(req, "hash"): - ret.append({"types": ["sha256"], "values": [hash]}) + for _hash in findAll(req, "hash"): + ret.append({"types": ["sha256"], "values": [_hash]}) req = json.loads(requests.get("https://otx.alienvault.com/otxapi/indicator/domain/whois/" + domain).text) - for domain in findAll(req, "domain"): - ret.append({"types": ["hostname"], "values": [domain]}) + for _domain in findAll(req, "domain"): + ret.append({"types": ["hostname"], "values": [_domain]}) for email in findAll(req, "value"): if valid_email(email): - ret.append({"types": ["email"], "values": [domain]}) + ret.append({"types": ["email"], "values": [email]}) - for domain in findAll(req, "hostname"): - if "." in domain and not isBlacklisted(domain): - ret.append({"types": ["hostname"], "values": [domain]}) + for _domain in findAll(req, "hostname"): + if "." in _domain and not isBlacklisted(_domain): + ret.append({"types": ["hostname"], "values": [_domain]}) req = json.loads(requests.get("https://otx.alienvault.com/otxapi/indicator/hostname/passive_dns/" + domain).text) for ip in findAll(req, "address"): From ef781f59f8e9b489695879b70b3f1eee477adb79 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Thu, 6 Sep 2018 14:05:55 +0200 Subject: [PATCH 128/153] fixed typo via #220 --- misp_modules/modules/expansion/urlscan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/urlscan.py b/misp_modules/modules/expansion/urlscan.py index ecd1a50..31c9230 100644 --- a/misp_modules/modules/expansion/urlscan.py +++ b/misp_modules/modules/expansion/urlscan.py @@ -49,7 +49,7 @@ def handler(q=False): r['results'] += lookup_indicator(client, request['hostname']) if 'url' in request: r['results'] += lookup_indicator(client, request['url']) - f 'hash' in request: + if 'hash' in request: r['results'] += lookup_indicator(client, request['hash']) # Return any errors generated from lookup to the UI and remove duplicates From 26647a164bba1bf7b14cca6e89108e84c1ca9955 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Fri, 7 Sep 2018 17:43:46 +0200 Subject: [PATCH 129/153] fix: Fixed indentation error --- misp_modules/modules/expansion/virustotal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/virustotal.py b/misp_modules/modules/expansion/virustotal.py index 614b8d0..2889d7b 100644 --- a/misp_modules/modules/expansion/virustotal.py +++ b/misp_modules/modules/expansion/virustotal.py @@ -128,7 +128,7 @@ class VirusTotalRequest(object): for h in hashes[:self.limit]: # Search VT for some juicy info try: - data = requests.get(self.base_url.format('file'), params={'resource': h, 'apikey': self.apikey, 'allinfo': 1}).json() + data = requests.get(self.base_url.format('file'), params={'resource': h, 'apikey': self.apikey, 'allinfo': 1}).json() except Exception: continue # Go through euch key and check if it exists From 48fcf9a85eae5ed2a52dfd65f79dddc0180435e9 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Fri, 7 Sep 2018 17:49:28 +0200 Subject: [PATCH 130/153] fix: Fixed syntax error --- misp_modules/modules/expansion/virustotal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/virustotal.py b/misp_modules/modules/expansion/virustotal.py index 2889d7b..524bc49 100644 --- a/misp_modules/modules/expansion/virustotal.py +++ b/misp_modules/modules/expansion/virustotal.py @@ -148,7 +148,7 @@ def handler(q=False): return False q = json.loads(q) if not q.get('config') or not q['config'].get('apikey'): - misperrors['error']: "A VirusTotal api key is required for this module." + misperrors['error'] = "A VirusTotal api key is required for this module." return misperrors del q['module'] query = VirusTotalRequest(q.pop('config')) From a18db2ed1d2985ef55a6fc4cf7161fbb74273ee9 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Fri, 7 Sep 2018 17:56:25 +0200 Subject: [PATCH 131/153] fix: Fixed exception type --- misp_modules/modules/expansion/yara_syntax_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/yara_syntax_validator.py b/misp_modules/modules/expansion/yara_syntax_validator.py index 57c71ea..18e2de0 100644 --- a/misp_modules/modules/expansion/yara_syntax_validator.py +++ b/misp_modules/modules/expansion/yara_syntax_validator.py @@ -2,7 +2,7 @@ import json import requests try: import yara -except ModuleNotFoundError: +except (OSError, ModuleNotFoundError): print("yara is missing, use 'pip3 install yara' to install it.") misperrors = {'error': 'Error'} From cfbd63f14ec8c348a749bb37f0ea543605535aa8 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Fri, 7 Sep 2018 18:06:01 +0200 Subject: [PATCH 132/153] fix: Fixed exception type for python 3.5 --- misp_modules/modules/expansion/yara_syntax_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/yara_syntax_validator.py b/misp_modules/modules/expansion/yara_syntax_validator.py index 18e2de0..c68d934 100644 --- a/misp_modules/modules/expansion/yara_syntax_validator.py +++ b/misp_modules/modules/expansion/yara_syntax_validator.py @@ -2,7 +2,7 @@ import json import requests try: import yara -except (OSError, ModuleNotFoundError): +except (OSError, ImportError): print("yara is missing, use 'pip3 install yara' to install it.") misperrors = {'error': 'Error'} From 5c718c5379ccb7bfa32bdd12a53b5bc9d9134106 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Sat, 8 Sep 2018 02:53:15 +0200 Subject: [PATCH 133/153] fix: Making python 3.5 happy with the exception type ImportError --- misp_modules/modules/expansion/dbl_spamhaus.py | 2 +- misp_modules/modules/expansion/rbl.py | 2 +- misp_modules/modules/expansion/sigma_queries.py | 2 +- misp_modules/modules/expansion/sigma_syntax_validator.py | 2 +- .../modules/expansion/stix2_pattern_syntax_validator.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/misp_modules/modules/expansion/dbl_spamhaus.py b/misp_modules/modules/expansion/dbl_spamhaus.py index f78cb74..306ea21 100644 --- a/misp_modules/modules/expansion/dbl_spamhaus.py +++ b/misp_modules/modules/expansion/dbl_spamhaus.py @@ -7,7 +7,7 @@ try: resolver = dns.resolver.Resolver() resolver.timeout = 0.2 resolver.lifetime = 0.2 -except ModuleNotFoundError: +except ImportError: print("dnspython3 is missing, use 'pip install dnspython3' to install it.") sys.exit(0) diff --git a/misp_modules/modules/expansion/rbl.py b/misp_modules/modules/expansion/rbl.py index 7aac103..6626760 100644 --- a/misp_modules/modules/expansion/rbl.py +++ b/misp_modules/modules/expansion/rbl.py @@ -6,7 +6,7 @@ try: resolver = dns.resolver.Resolver() resolver.timeout = 0.2 resolver.lifetime = 0.2 -except ModuleNotFoundError: +except ImportError: print("dnspython3 is missing, use 'pip install dnspython3' to install it.") sys.exit(0) diff --git a/misp_modules/modules/expansion/sigma_queries.py b/misp_modules/modules/expansion/sigma_queries.py index e37df23..d263245 100644 --- a/misp_modules/modules/expansion/sigma_queries.py +++ b/misp_modules/modules/expansion/sigma_queries.py @@ -3,7 +3,7 @@ try: from sigma.parser import SigmaCollectionParser from sigma.config import SigmaConfiguration from sigma.backends import getBackend, BackendOptions -except ModuleNotFoundError: +except ImportError: print("sigma or yaml is missing, use 'pip3 install sigmatools' to install it.") misperrors = {'error': 'Error'} diff --git a/misp_modules/modules/expansion/sigma_syntax_validator.py b/misp_modules/modules/expansion/sigma_syntax_validator.py index 6452654..e5cc335 100644 --- a/misp_modules/modules/expansion/sigma_syntax_validator.py +++ b/misp_modules/modules/expansion/sigma_syntax_validator.py @@ -3,7 +3,7 @@ try: import yaml from sigma.parser import SigmaParser from sigma.config import SigmaConfiguration -except ModuleNotFoundError: +except ImportError: print("sigma or yaml is missing, use 'pip3 install sigmatools' to install it.") misperrors = {'error': 'Error'} diff --git a/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py b/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py index bf5d408..78307a4 100644 --- a/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py +++ b/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py @@ -1,7 +1,7 @@ import json try: from stix2patterns.validator import run_validator -except ModuleNotFoundError: +except ImportError: print("stix2 patterns python library is missing, use 'pip3 install stix2-patterns' to install it.") misperrors = {'error': 'Error'} From 754321b4e232d67ffd9005ca252e4a5965d805f0 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 13 Sep 2018 10:37:46 +0200 Subject: [PATCH 134/153] Merging readme --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index b3b1919..a7f05c9 100644 --- a/README.md +++ b/README.md @@ -384,11 +384,7 @@ Recommended Plugin.Import_ocr_enabled true Enable or disable the ocr In this same menu set any other plugin settings that are required for testing. ## Install misp-module on an offline instance. -<<<<<<< HEAD -First, you need to grab all necessery packages for example like this : -======= First, you need to grab all necessary packages for example like this : ->>>>>>> 79633242c842a6ca7c90a4c4e6bc002e05403aef Use pip wheel to create an archive ~~~ From 8db47bd973ab98479a590c56c369eb436e800025 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 13 Sep 2018 10:42:46 +0200 Subject: [PATCH 135/153] Removed documentation about a module deleted from the repository --- doc/import_doc/stiximport.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 doc/import_doc/stiximport.json diff --git a/doc/import_doc/stiximport.json b/doc/import_doc/stiximport.json deleted file mode 100644 index 7b22029..0000000 --- a/doc/import_doc/stiximport.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "description": "Module to import some stix stuff.", - "requirements": ["stix"], - "features": "The module imports MISP Attributes from a STIX 1 file, using the stix library, there is thus no special feature for users to make it work.", - "references": ["https://oasis-open.github.io/cti-documentation/", "https://github.com/STIXProject/python-stix"], - "input": "STIX format file", - "output": "MISP Event attributes" -} From 77eed369ef0f02b027a5eb5f5aaa1e85078f1c30 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 13 Sep 2018 11:13:34 +0200 Subject: [PATCH 136/153] Renamed directory to have consistency in names --- doc/{import_doc => import_mod}/csvimport.json | 0 doc/{import_doc => import_mod}/cuckooimport.json | 0 doc/{import_doc => import_mod}/email_import.json | 0 doc/{import_doc => import_mod}/goamlimport.json | 0 doc/{import_doc => import_mod}/mispjson.json | 0 doc/{import_doc => import_mod}/ocr.json | 0 doc/{import_doc => import_mod}/openiocimport.json | 0 doc/{import_doc => import_mod}/threatanalyzer_import.json | 0 doc/{import_doc => import_mod}/vmray_import.json | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename doc/{import_doc => import_mod}/csvimport.json (100%) rename doc/{import_doc => import_mod}/cuckooimport.json (100%) rename doc/{import_doc => import_mod}/email_import.json (100%) rename doc/{import_doc => import_mod}/goamlimport.json (100%) rename doc/{import_doc => import_mod}/mispjson.json (100%) rename doc/{import_doc => import_mod}/ocr.json (100%) rename doc/{import_doc => import_mod}/openiocimport.json (100%) rename doc/{import_doc => import_mod}/threatanalyzer_import.json (100%) rename doc/{import_doc => import_mod}/vmray_import.json (100%) diff --git a/doc/import_doc/csvimport.json b/doc/import_mod/csvimport.json similarity index 100% rename from doc/import_doc/csvimport.json rename to doc/import_mod/csvimport.json diff --git a/doc/import_doc/cuckooimport.json b/doc/import_mod/cuckooimport.json similarity index 100% rename from doc/import_doc/cuckooimport.json rename to doc/import_mod/cuckooimport.json diff --git a/doc/import_doc/email_import.json b/doc/import_mod/email_import.json similarity index 100% rename from doc/import_doc/email_import.json rename to doc/import_mod/email_import.json diff --git a/doc/import_doc/goamlimport.json b/doc/import_mod/goamlimport.json similarity index 100% rename from doc/import_doc/goamlimport.json rename to doc/import_mod/goamlimport.json diff --git a/doc/import_doc/mispjson.json b/doc/import_mod/mispjson.json similarity index 100% rename from doc/import_doc/mispjson.json rename to doc/import_mod/mispjson.json diff --git a/doc/import_doc/ocr.json b/doc/import_mod/ocr.json similarity index 100% rename from doc/import_doc/ocr.json rename to doc/import_mod/ocr.json diff --git a/doc/import_doc/openiocimport.json b/doc/import_mod/openiocimport.json similarity index 100% rename from doc/import_doc/openiocimport.json rename to doc/import_mod/openiocimport.json diff --git a/doc/import_doc/threatanalyzer_import.json b/doc/import_mod/threatanalyzer_import.json similarity index 100% rename from doc/import_doc/threatanalyzer_import.json rename to doc/import_mod/threatanalyzer_import.json diff --git a/doc/import_doc/vmray_import.json b/doc/import_mod/vmray_import.json similarity index 100% rename from doc/import_doc/vmray_import.json rename to doc/import_mod/vmray_import.json From 7704591a5a610b835d88ca594212137102883c45 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 13 Sep 2018 14:58:50 +0200 Subject: [PATCH 137/153] new: Documentation concerning modules explained in markdown file --- doc/generate_documentation.py | 26 +++ doc/markdown.md | 348 ++++++++++++++++++++++++++++++++++ 2 files changed, 374 insertions(+) create mode 100644 doc/generate_documentation.py create mode 100644 doc/markdown.md diff --git a/doc/generate_documentation.py b/doc/generate_documentation.py new file mode 100644 index 0000000..3626fe2 --- /dev/null +++ b/doc/generate_documentation.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +import os +import json + +root_path = os.path.dirname(os.path.realpath(__file__)) +module_types = ['expansion', 'export_mod', 'import_mod'] +titles = ['Expansion Modules', 'Export Modules', 'Import Modules'] +markdown= ["# MISP modules documentation\n"] +for _path, title in zip(module_types, titles): + markdown.append('\n## {}\n'.format(title)) + current_path = os.path.join(root_path, _path) + files = os.listdir(current_path) + for _file in files: + markdown.append('\n### {}\n'.format(_file.split('.json')[0])) + filename = os.path.join(current_path, _file) + with open(filename, 'rt', encoding='utf-8') as f: + definition = json.loads(f.read()) + if 'description' in definition: + markdown.append('\n{}\n'.format(definition.pop('description'))) + for field, value in definition.items(): + if value: + value = ', '.join(value) if isinstance(value, list) else '{}'.format(value.replace('\n', '\n>')) + markdown.append('- **{}**:\n>{}\n'.format(field, value)) + # markdown.append('\n') +with open('markdown.md', 'w') as w: + w.write(''.join(markdown)) diff --git a/doc/markdown.md b/doc/markdown.md new file mode 100644 index 0000000..7d3e132 --- /dev/null +++ b/doc/markdown.md @@ -0,0 +1,348 @@ +# MISP modules documentation + +## Expansion Modules + +### geoip_country + +Module to query a local copy of Maxminds Geolite database. + +### iprep + +Module to query IPRep data for IP addresses. + +### crowdstrike_falcon + +Module to query Crowdstrike Falcon. + +### dns + +A simple DNS expansion service to resolve IP address from MISP attributes. + +### vulndb + +Module to query VulnDB (RiskBasedSecurity.com). + +### wiki + +An expansion hover module to extract information from Wikidata to have additional information about particular term for analysis. + +### rbl + +Module to check an IPv4 address against known RBLs. +- **requirements**: +>dnspython3 + +### asn_history + +Query an ASN description history service (https://github.com/CIRCL/ASN-Description-History.git). +- **requirements**: +>asnhistory + +### circl_passivedns + +Module to access CIRCL Passive DNS. + +### farsight_passivedns + +Module to access Farsight DNSDB Passive DNS. + +### whois + +Module to query a local instance of uwhois (https://github.com/rafiot/uwhoisd). +- **requirements**: +>uwhois + +### threatcrowd + +Module to get information from ThreatCrowd. + +### domaintools + +DomainTools MISP expansion module. + +### eupi + +A module to query the Phishing Initiative service (https://phishing-initiative.lu). + +### shodan + +Module to query on Shodan. + +### xforceexchange + +An expansion module for IBM X-Force Exchange. + +### circl_passivessl + +Modules to access CIRCL Passive SSL. + +### virustotal + +Module to get information from virustotal. + +### otx + +Module to get information from AlienVault OTX. + +### cve + +An expansion hover module to expand information about CVE id. + +### reversedns + +Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes. + +### threatminer + +Module to get information from ThreatMiner. + +### sourcecache + +Module to cache web pages of analysis reports, OSINT sources. The module returns a link of the cached page. + +### ipasn + +Module to query an IP ASN history service (https://github.com/CIRCL/IP-ASN-history.git). + +### intelmq_eventdb + +Module to access intelmqs eventdb. + +### vmray_submit + +Module to submit a sample to VMRay. + +### countrycode + +Module to expand country codes. + +### passivetotal + +The PassiveTotal MISP expansion module brings the datasets derived from Internet scanning directly into your MISP instance. This module supports passive DNS, historic SSL, WHOIS, and host attributes. In order to use the module, you must have a valid PassiveTotal account username and API key. Registration is free and can be done by visiting https://www.passivetotal.org/register + +### yara_syntax_validator + +An expansion hover module to perform a syntax check on if yara rules are valid or not. + +## Export Modules + +### testexport + +Skeleton export module. + +### goamlexport + +This module is used to export MISP events containing transaction objects into GoAML format. +- **requirements**: +>PyMISP, MISP objects +- **features**: +>The module works as long as there is at least one transaction object in the Event. +> +>Then in order to have a valid GoAML document, please follow these guidelines: +>- For each transaction object, use either a bank-account, person, or legal-entity object to describe the origin of the transaction, and again one of them to describe the target of the transaction. +>- Create an object reference for both origin and target objects of the transaction. +>- A bank-account object needs a signatory, which is a person object, put as object reference of the bank-account. +>- A person can have an address, which is a geolocation object, put as object reference of the person. +> +>Supported relation types for object references that are recommended for each object are the folowing: +>- transaction: +> - 'from', 'from_my_client': Origin of the transaction - at least one of them is required. +> - 'to', 'to_my_client': Target of the transaction - at least one of them is required. +> - 'address': Location of the transaction - optional. +>- bank-account: +> - 'signatory': Signatory of a bank-account - the reference from bank-account to a signatory is required, but the relation-type is optional at the moment since this reference will always describe a signatory. +> - 'entity': Entity owning the bank account - optional. +>- person: +> - 'address': Address of a person - optional. +- **references**: +>http://goaml.unodc.org/ +- **input**: +>MISP objects (transaction, bank-account, person, legal-entity, geolocation), with references, describing financial transactions and their origin and target. +- **output**: +>GoAML format file, describing financial transactions, with their origin and target (bank accounts, persons or entities). + +### threatStream_misp_export + +Module to export a structured CSV file for uploading to threatStream. +- **requirements**: +>csv +- **features**: +>The module takes a MISP event in input, to look every attribute. Each attribute matching with some predefined types is then exported in a CSV format recognized by ThreatStream. +- **references**: +>https://www.anomali.com/platform/threatstream, https://github.com/threatstream +- **input**: +>MISP Event attributes +- **output**: +>ThreatStream CSV format file + +### threat_connect_export + +Module to export a structured CSV file for uploading to ThreatConnect. +- **requirements**: +>csv +- **features**: +>The module takes a MISP event in input, to look every attribute. Each attribute matching with some predefined types is then exported in a CSV format recognized by ThreatConnect. +>Users should then provide, as module configuration, the source of data they export, because it is required by the output format. +- **references**: +>https://www.threatconnect.com +- **input**: +>MISP Event attributes +- **output**: +>ThreatConnect CSV format file + +### pdfexport + +Simple export of a MISP event to PDF. +- **requirements**: +>PyMISP, asciidoctor +- **features**: +>The module takes care of the PDF file building, and work with any MISP Event. Except the requirement of asciidoctor, used to create the file, there is no special feature concerning the Event. +- **references**: +>https://acrobat.adobe.com/us/en/acrobat/about-adobe-pdf.html +- **input**: +>MISP Event +- **output**: +>MISP Event in a PDF file. + +### cef_export + +Module to export a MISP event in CEF format. +- **features**: +>The module takes a MISP event in input, to look every attribute. Each attribute matching with some predefined types is then exported in Common Event Format. +>Thus, there is no particular feature concerning MISP Events since any event can be exported. However, 4 configuration parameters recognized by CEF format are required and should be provided by users before exporting data: the device vendor, product and version, as well as the default severity of data. +- **references**: +>https://community.softwaregrp.com/t5/ArcSight-Connectors/ArcSight-Common-Event-Format-CEF-Guide/ta-p/1589306?attachment-id=65537 +- **input**: +>MISP Event attributes +- **output**: +>Common Event Format file + +### liteexport + +Lite export of a MISP event. +- **features**: +>This module is simply producing a json MISP event format file, but exporting only Attributes from the Event. Thus, MISP Events exported with this module should have attributes that are not internal references, otherwise the resulting event would be empty. +- **input**: +>MISP Event attributes +- **output**: +>Lite MISP Event + +## Import Modules + +### vmray_import + +Module to import VMRay (VTI) results. +- **requirements**: +>vmray_rest_api +- **features**: +>The module imports MISP Attributes from VMRay format, using the VMRay api. +>Users should then provide as the module configuration the API Key as well as the server url in order to fetch their data to import. +- **references**: +>https://www.vmray.com/ +- **input**: +>VMRay format +- **output**: +>MISP Event attributes + +### threatanalyzer_import + +Module to import ThreatAnalyzer archive.zip / analysis.json files. +- **features**: +>The module imports MISP Attributes from a ThreatAnalyzer format file. This file can be either ZIP, or JSON format. +>There is by the way no special feature for users to make the module work. +- **references**: +>https://www.threattrack.com/malware-analysis.aspx +- **input**: +>ThreatAnalyzer format file +- **output**: +>MISP Event attributes + +### ocr + +Optical Character Recognition (OCR) module for MISP. +- **features**: +>The module tries to recognize some text from an image and import the result as a freetext attribute, there is then no special feature asked to users to make it work. +- **input**: +>Image +- **output**: +>freetext MISP attribute + +### csvimport + +Module to import MISP attributes from a csv file. +- **requirements**: +>PyMISP +- **features**: +>In order to parse data from a csv file, a header is required to let the module know which column is matching with known attribute fields / MISP types. +>This header is part of the configuration of the module and should be filled out in MISP plugin settings, each field separated by COMMAS. Fields that do not match with any type known in MISP can be ignored in import, using a space or simply nothing between two separators (example: 'ip-src, , comment, '). +>There is also one type that is confused and can be either a MISP attribute type or an attribute field: 'comment'. In this case, using 'attrComment' specifies that the attribute field 'comment' should be considered, otherwise it will be considered as the MISP attribute type. +> +>For each MISP attribute type, an attribute is created. +>Attribute fields that are imported are the following: value, type, category, to-ids, distribution, comment, tag. +- **references**: +>https://tools.ietf.org/html/rfc4180, https://tools.ietf.org/html/rfc7111 +- **input**: +>CSV format file. +- **output**: +>MISP Event attributes + +### goamlimport + +Module to import MISP objects about financial transactions from GoAML files. +- **requirements**: +>PyMISP +- **features**: +>Unlike the GoAML export module, there is here no special feature to import data from GoAML external files, since the module will import MISP Objects with their References on its own, as it is required for the export module to rebuild a valid GoAML document. +- **references**: +>http://goaml.unodc.org/ +- **input**: +>GoAML format file, describing financial transactions, with their origin and target (bank accounts, persons or entities). +- **output**: +>MISP objects (transaction, bank-account, person, legal-entity, geolocation), with references, describing financial transactions and their origin and target. + +### cuckooimport + +Module to import Cuckoo JSON. +- **features**: +>The module simply imports MISP Attributes from a Cuckoo JSON format file. There is thus no special feature to make it work. +- **references**: +>https://cuckoosandbox.org/, https://github.com/cuckoosandbox/cuckoo +- **input**: +>Cuckoo JSON file +- **output**: +>MISP Event attributes + +### email_import + +Module to import emails in MISP. +- **features**: +>This module can be used to import e-mail text as well as attachments and urls. +>3 configuration parameters are then used to unzip attachments, guess zip attachment passwords, and extract urls: set each one of them to True or False to process or not the respective corresponding actions. +- **input**: +>E-mail file +- **output**: +>MISP Event attributes + +### mispjson + +Module to import MISP JSON format for merging MISP events. +- **features**: +>The module simply imports MISP Attributes from an other MISP Event in order to merge events together. There is thus no special feature to make it work. +- **input**: +>MISP Event +- **output**: +>MISP Event attributes + +### openiocimport + +Module to import OpenIOC packages. +- **requirements**: +>PyMISP +- **features**: +>The module imports MISP Attributes from OpenIOC packages, there is then no special feature for users to make it work. +- **references**: +>https://www.fireeye.com/blog/threat-research/2013/10/openioc-basics.html +- **input**: +>OpenIOC packages +- **output**: +>MISP Event attributes From fc701363129da6588bb74015cb3e0bb5acf13fdb Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 13 Sep 2018 15:06:06 +0200 Subject: [PATCH 138/153] fix: Typo for separator between each explained module --- doc/generate_documentation.py | 2 +- doc/markdown.md | 90 +++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/doc/generate_documentation.py b/doc/generate_documentation.py index 3626fe2..6ecc507 100644 --- a/doc/generate_documentation.py +++ b/doc/generate_documentation.py @@ -21,6 +21,6 @@ for _path, title in zip(module_types, titles): if value: value = ', '.join(value) if isinstance(value, list) else '{}'.format(value.replace('\n', '\n>')) markdown.append('- **{}**:\n>{}\n'.format(field, value)) - # markdown.append('\n') + markdown.append('\n-----\n') with open('markdown.md', 'w') as w: w.write(''.join(markdown)) diff --git a/doc/markdown.md b/doc/markdown.md index 7d3e132..4973db8 100644 --- a/doc/markdown.md +++ b/doc/markdown.md @@ -6,130 +6,190 @@ Module to query a local copy of Maxminds Geolite database. +----- + ### iprep Module to query IPRep data for IP addresses. +----- + ### crowdstrike_falcon Module to query Crowdstrike Falcon. +----- + ### dns A simple DNS expansion service to resolve IP address from MISP attributes. +----- + ### vulndb Module to query VulnDB (RiskBasedSecurity.com). +----- + ### wiki An expansion hover module to extract information from Wikidata to have additional information about particular term for analysis. +----- + ### rbl Module to check an IPv4 address against known RBLs. - **requirements**: >dnspython3 +----- + ### asn_history Query an ASN description history service (https://github.com/CIRCL/ASN-Description-History.git). - **requirements**: >asnhistory +----- + ### circl_passivedns Module to access CIRCL Passive DNS. +----- + ### farsight_passivedns Module to access Farsight DNSDB Passive DNS. +----- + ### whois Module to query a local instance of uwhois (https://github.com/rafiot/uwhoisd). - **requirements**: >uwhois +----- + ### threatcrowd Module to get information from ThreatCrowd. +----- + ### domaintools DomainTools MISP expansion module. +----- + ### eupi A module to query the Phishing Initiative service (https://phishing-initiative.lu). +----- + ### shodan Module to query on Shodan. +----- + ### xforceexchange An expansion module for IBM X-Force Exchange. +----- + ### circl_passivessl Modules to access CIRCL Passive SSL. +----- + ### virustotal Module to get information from virustotal. +----- + ### otx Module to get information from AlienVault OTX. +----- + ### cve An expansion hover module to expand information about CVE id. +----- + ### reversedns Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes. +----- + ### threatminer Module to get information from ThreatMiner. +----- + ### sourcecache Module to cache web pages of analysis reports, OSINT sources. The module returns a link of the cached page. +----- + ### ipasn Module to query an IP ASN history service (https://github.com/CIRCL/IP-ASN-history.git). +----- + ### intelmq_eventdb Module to access intelmqs eventdb. +----- + ### vmray_submit Module to submit a sample to VMRay. +----- + ### countrycode Module to expand country codes. +----- + ### passivetotal The PassiveTotal MISP expansion module brings the datasets derived from Internet scanning directly into your MISP instance. This module supports passive DNS, historic SSL, WHOIS, and host attributes. In order to use the module, you must have a valid PassiveTotal account username and API key. Registration is free and can be done by visiting https://www.passivetotal.org/register +----- + ### yara_syntax_validator An expansion hover module to perform a syntax check on if yara rules are valid or not. +----- + ## Export Modules ### testexport Skeleton export module. +----- + ### goamlexport This module is used to export MISP events containing transaction objects into GoAML format. @@ -161,6 +221,8 @@ This module is used to export MISP events containing transaction objects into Go - **output**: >GoAML format file, describing financial transactions, with their origin and target (bank accounts, persons or entities). +----- + ### threatStream_misp_export Module to export a structured CSV file for uploading to threatStream. @@ -175,6 +237,8 @@ Module to export a structured CSV file for uploading to threatStream. - **output**: >ThreatStream CSV format file +----- + ### threat_connect_export Module to export a structured CSV file for uploading to ThreatConnect. @@ -190,6 +254,8 @@ Module to export a structured CSV file for uploading to ThreatConnect. - **output**: >ThreatConnect CSV format file +----- + ### pdfexport Simple export of a MISP event to PDF. @@ -204,6 +270,8 @@ Simple export of a MISP event to PDF. - **output**: >MISP Event in a PDF file. +----- + ### cef_export Module to export a MISP event in CEF format. @@ -217,6 +285,8 @@ Module to export a MISP event in CEF format. - **output**: >Common Event Format file +----- + ### liteexport Lite export of a MISP event. @@ -227,6 +297,8 @@ Lite export of a MISP event. - **output**: >Lite MISP Event +----- + ## Import Modules ### vmray_import @@ -244,6 +316,8 @@ Module to import VMRay (VTI) results. - **output**: >MISP Event attributes +----- + ### threatanalyzer_import Module to import ThreatAnalyzer archive.zip / analysis.json files. @@ -257,6 +331,8 @@ Module to import ThreatAnalyzer archive.zip / analysis.json files. - **output**: >MISP Event attributes +----- + ### ocr Optical Character Recognition (OCR) module for MISP. @@ -267,6 +343,8 @@ Optical Character Recognition (OCR) module for MISP. - **output**: >freetext MISP attribute +----- + ### csvimport Module to import MISP attributes from a csv file. @@ -286,6 +364,8 @@ Module to import MISP attributes from a csv file. - **output**: >MISP Event attributes +----- + ### goamlimport Module to import MISP objects about financial transactions from GoAML files. @@ -300,6 +380,8 @@ Module to import MISP objects about financial transactions from GoAML files. - **output**: >MISP objects (transaction, bank-account, person, legal-entity, geolocation), with references, describing financial transactions and their origin and target. +----- + ### cuckooimport Module to import Cuckoo JSON. @@ -312,6 +394,8 @@ Module to import Cuckoo JSON. - **output**: >MISP Event attributes +----- + ### email_import Module to import emails in MISP. @@ -323,6 +407,8 @@ Module to import emails in MISP. - **output**: >MISP Event attributes +----- + ### mispjson Module to import MISP JSON format for merging MISP events. @@ -333,6 +419,8 @@ Module to import MISP JSON format for merging MISP events. - **output**: >MISP Event attributes +----- + ### openiocimport Module to import OpenIOC packages. @@ -346,3 +434,5 @@ Module to import OpenIOC packages. >OpenIOC packages - **output**: >MISP Event attributes + +----- From db7dbd6ed550eb0f88934c3644cf7d8bdd28bc51 Mon Sep 17 00:00:00 2001 From: Codelinefi-admin Date: Thu, 13 Sep 2018 17:02:49 +0300 Subject: [PATCH 139/153] macaddress.io hover module added --- README.md | 1 + REQUIREMENTS | 1 + .../modules/expansion/macaddress_io.py | 119 ++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 misp_modules/modules/expansion/macaddress_io.py diff --git a/README.md b/README.md index ad4b098..af32bc0 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [hashdd](misp_modules/modules/expansion/hashdd.py) - a hover module to check file hashes against [hashdd.com](http://www.hashdd.com) including NSLR dataset. * [IPASN](misp_modules/modules/expansion/ipasn.py) - a hover and expansion to get the BGP ASN of an IP address. * [iprep](misp_modules/modules/expansion/iprep.py) - an expansion module to get IP reputation from packetmail.net. +* [macaddress.io](misp_modules/modules/expansion/macaddress_io.py) - a hover module to retrieve vendor details and other information regarding a given MAC address or an OUI. [MAC address Vendor Lookup](https://macaddress.io) * [onyphe](misp_modules/modules/expansion/onyphe.py) - a modules to process queries on Onyphe. * [onyphe_full](misp_modules/modules/expansion/onyphe_full.py) - a modules to process full queries on Onyphe. * [OTX](misp_modules/modules/expansion/otx.py) - an expansion module for [OTX](https://otx.alienvault.com/). diff --git a/REQUIREMENTS b/REQUIREMENTS index 29af57b..c004afe 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -24,3 +24,4 @@ oauth2 yara sigmatools stix2-patterns +maclookup diff --git a/misp_modules/modules/expansion/macaddress_io.py b/misp_modules/modules/expansion/macaddress_io.py new file mode 100644 index 0000000..14e1134 --- /dev/null +++ b/misp_modules/modules/expansion/macaddress_io.py @@ -0,0 +1,119 @@ +import json + +from maclookup import ApiClient, exceptions + +misperrors = { + 'error': 'Error' +} + +mispattributes = { + 'input': ['mac-address'], +} + +moduleinfo = { + 'version': '1.0', + 'author': 'CodeLine OY - macaddress.io', + 'description': 'MISP hover module for macaddress.io', + 'module-type': ['hover'] +} + +moduleconfig = ['api_key'] + + +def handler(q=False): + if q is False: + return False + + request = json.loads(q) + + if request.get('mac-address'): + mac_address = request['mac-address'] + else: + return False + + if request['config'].get('api_key'): + api_key = request['config'].get('api_key') + + else: + misperrors['error'] = 'Authorization required' + return misperrors + + api_client = ApiClient(api_key) + + try: + response = api_client.get(mac_address) + + except exceptions.EmptyResponseException: + misperrors['error'] = 'Empty response' + return misperrors + + except exceptions.UnparsableResponseException: + misperrors['error'] = 'Unparsable response' + return misperrors + + except exceptions.ServerErrorException: + misperrors['error'] = 'Internal server error' + return misperrors + + except exceptions.UnknownOutputFormatException: + misperrors['error'] = 'Unknown output' + return misperrors + + except exceptions.AuthorizationRequiredException: + misperrors['error'] = 'Authorization required' + return misperrors + + except exceptions.AccessDeniedException: + misperrors['error'] = 'Access denied' + return misperrors + + except exceptions.InvalidMacOrOuiException: + misperrors['error'] = 'Invalid MAC or OUI' + return misperrors + + except exceptions.NotEnoughCreditsException: + misperrors['error'] = 'Not enough credits' + return misperrors + + except Exception: + misperrors['error'] = 'Unknown error' + return misperrors + + results = { + 'results': [ + {'types': ['text'], 'values': + { + 'Valid MAC address': "True" if response.mac_address_details.is_valid else "False", + + 'Transmission type': response.mac_address_details.transmission_type, + 'Administration type': response.mac_address_details.administration_type, + + 'OUI': response.vendor_details.oui, + 'Vendor details are hidden': "True" if response.vendor_details.is_private else "False", + + 'Company name': response.vendor_details.company_name, + 'Company\'s address': response.vendor_details.company_address, + 'County code': response.vendor_details.country_code, + + 'Block found': "True" if response.block_details.block_found else "False", + 'The left border of the range': response.block_details.border_left, + 'The right border of the range': response.block_details.border_right, + 'The total number of MAC addresses in this range': response.block_details.block_size, + 'Assignment block size': response.block_details.assignment_block_size, + 'Date when the range was allocated': response.block_details.date_created, + 'Date when the range was last updated': response.block_details.date_updated + } + } + ] + } + + return results + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 782ef9f2e3c2e6ca1c1b2a406a6bac5120bc284e Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 13 Sep 2018 18:13:44 +0200 Subject: [PATCH 140/153] add: Started adding logos on documentation for each module --- doc/export_mod/goamlexport.json | 1 + doc/export_mod/threatStream_misp_export.json | 1 + doc/export_mod/threat_connect_export.json | 1 + doc/generate_documentation.py | 6 ++++-- doc/import_mod/cuckooimport.json | 1 + doc/import_mod/goamlimport.json | 1 + doc/import_mod/vmray_import.json | 1 + doc/logos/Sigma.png | Bin 0 -> 27681 bytes doc/logos/crowdstrike.png | Bin 0 -> 34911 bytes doc/logos/cuckoo.png | Bin 0 -> 11970 bytes doc/logos/domaintools.png | Bin 0 -> 4886 bytes doc/logos/eupi.png | Bin 0 -> 9621 bytes doc/logos/farsight.png | Bin 0 -> 12300 bytes doc/logos/goAML.jpg | Bin 0 -> 31790 bytes doc/logos/onyphe.jpg | Bin 0 -> 10439 bytes doc/logos/otx.png | Bin 0 -> 8752 bytes doc/logos/passivedns.png | Bin 0 -> 19024 bytes doc/logos/passivessl.png | Bin 0 -> 25737 bytes doc/logos/passivetotal.png | Bin 0 -> 36361 bytes doc/logos/securitytrails.png | Bin 0 -> 7947 bytes doc/logos/shodan.png | Bin 0 -> 33516 bytes doc/logos/spamhaus.jpg | Bin 0 -> 6002 bytes doc/logos/stix.png | Bin 0 -> 3730 bytes doc/logos/threatconnect.png | Bin 0 -> 14988 bytes doc/logos/threatcrowd.png | Bin 0 -> 3117 bytes doc/logos/threatminer.png | Bin 0 -> 6174 bytes doc/logos/threatstream.png | Bin 0 -> 3488 bytes doc/logos/virustotal.png | Bin 0 -> 2776 bytes doc/logos/vmray.png | Bin 0 -> 15325 bytes doc/logos/vulndb.png | Bin 0 -> 4801 bytes doc/logos/wikidata.png | Bin 0 -> 4385 bytes doc/logos/xforce.png | Bin 0 -> 8534 bytes doc/logos/yara.png | Bin 0 -> 62135 bytes 33 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 doc/logos/Sigma.png create mode 100644 doc/logos/crowdstrike.png create mode 100644 doc/logos/cuckoo.png create mode 100644 doc/logos/domaintools.png create mode 100644 doc/logos/eupi.png create mode 100644 doc/logos/farsight.png create mode 100644 doc/logos/goAML.jpg create mode 100644 doc/logos/onyphe.jpg create mode 100644 doc/logos/otx.png create mode 100644 doc/logos/passivedns.png create mode 100644 doc/logos/passivessl.png create mode 100644 doc/logos/passivetotal.png create mode 100644 doc/logos/securitytrails.png create mode 100644 doc/logos/shodan.png create mode 100644 doc/logos/spamhaus.jpg create mode 100644 doc/logos/stix.png create mode 100644 doc/logos/threatconnect.png create mode 100644 doc/logos/threatcrowd.png create mode 100644 doc/logos/threatminer.png create mode 100644 doc/logos/threatstream.png create mode 100644 doc/logos/virustotal.png create mode 100644 doc/logos/vmray.png create mode 100644 doc/logos/vulndb.png create mode 100644 doc/logos/wikidata.png create mode 100644 doc/logos/xforce.png create mode 100644 doc/logos/yara.png diff --git a/doc/export_mod/goamlexport.json b/doc/export_mod/goamlexport.json index 3a00724..57a1587 100644 --- a/doc/export_mod/goamlexport.json +++ b/doc/export_mod/goamlexport.json @@ -1,5 +1,6 @@ { "description": "This module is used to export MISP events containing transaction objects into GoAML format.", + "logo": "logos/goAML.jpg", "requirements": ["PyMISP","MISP objects"], "features": "The module works as long as there is at least one transaction object in the Event.\n\nThen in order to have a valid GoAML document, please follow these guidelines:\n- For each transaction object, use either a bank-account, person, or legal-entity object to describe the origin of the transaction, and again one of them to describe the target of the transaction.\n- Create an object reference for both origin and target objects of the transaction.\n- A bank-account object needs a signatory, which is a person object, put as object reference of the bank-account.\n- A person can have an address, which is a geolocation object, put as object reference of the person.\n\nSupported relation types for object references that are recommended for each object are the folowing:\n- transaction:\n\t- 'from', 'from_my_client': Origin of the transaction - at least one of them is required.\n\t- 'to', 'to_my_client': Target of the transaction - at least one of them is required.\n\t- 'address': Location of the transaction - optional.\n- bank-account:\n\t- 'signatory': Signatory of a bank-account - the reference from bank-account to a signatory is required, but the relation-type is optional at the moment since this reference will always describe a signatory.\n\t- 'entity': Entity owning the bank account - optional.\n- person:\n\t- 'address': Address of a person - optional.", "references": ["http://goaml.unodc.org/"], diff --git a/doc/export_mod/threatStream_misp_export.json b/doc/export_mod/threatStream_misp_export.json index a275032..3fdc50a 100644 --- a/doc/export_mod/threatStream_misp_export.json +++ b/doc/export_mod/threatStream_misp_export.json @@ -1,5 +1,6 @@ { "description": "Module to export a structured CSV file for uploading to threatStream.", + "logo": "logos/threatstream.png", "requirements": ["csv"], "features": "The module takes a MISP event in input, to look every attribute. Each attribute matching with some predefined types is then exported in a CSV format recognized by ThreatStream.", "references": ["https://www.anomali.com/platform/threatstream", "https://github.com/threatstream"], diff --git a/doc/export_mod/threat_connect_export.json b/doc/export_mod/threat_connect_export.json index abc4c65..8d19572 100644 --- a/doc/export_mod/threat_connect_export.json +++ b/doc/export_mod/threat_connect_export.json @@ -1,5 +1,6 @@ { "description": "Module to export a structured CSV file for uploading to ThreatConnect.", + "logo": "logos/threatconnect.png", "requirements": ["csv"], "features": "The module takes a MISP event in input, to look every attribute. Each attribute matching with some predefined types is then exported in a CSV format recognized by ThreatConnect.\nUsers should then provide, as module configuration, the source of data they export, because it is required by the output format.", "references": ["https://www.threatconnect.com"], diff --git a/doc/generate_documentation.py b/doc/generate_documentation.py index 6ecc507..af66b8a 100644 --- a/doc/generate_documentation.py +++ b/doc/generate_documentation.py @@ -9,12 +9,14 @@ markdown= ["# MISP modules documentation\n"] for _path, title in zip(module_types, titles): markdown.append('\n## {}\n'.format(title)) current_path = os.path.join(root_path, _path) - files = os.listdir(current_path) + files = sorted(os.listdir(current_path)) for _file in files: - markdown.append('\n### {}\n'.format(_file.split('.json')[0])) + markdown.append('\n#### {}\n'.format(_file.split('.json')[0])) filename = os.path.join(current_path, _file) with open(filename, 'rt', encoding='utf-8') as f: definition = json.loads(f.read()) + if 'logo' in definition: + markdown.append('\n\n'.format(definition.pop('logo'))) if 'description' in definition: markdown.append('\n{}\n'.format(definition.pop('description'))) for field, value in definition.items(): diff --git a/doc/import_mod/cuckooimport.json b/doc/import_mod/cuckooimport.json index d72469c..8091d07 100644 --- a/doc/import_mod/cuckooimport.json +++ b/doc/import_mod/cuckooimport.json @@ -1,5 +1,6 @@ { "description": "Module to import Cuckoo JSON.", + "logo": "logos/cuckoo.png", "requirements": [], "features": "The module simply imports MISP Attributes from a Cuckoo JSON format file. There is thus no special feature to make it work.", "references": ["https://cuckoosandbox.org/", "https://github.com/cuckoosandbox/cuckoo"], diff --git a/doc/import_mod/goamlimport.json b/doc/import_mod/goamlimport.json index 414a967..f2a1ec2 100644 --- a/doc/import_mod/goamlimport.json +++ b/doc/import_mod/goamlimport.json @@ -1,5 +1,6 @@ { "description": "Module to import MISP objects about financial transactions from GoAML files.", + "logo": "logos/goAML.jpg", "requirements": ["PyMISP"], "features": "Unlike the GoAML export module, there is here no special feature to import data from GoAML external files, since the module will import MISP Objects with their References on its own, as it is required for the export module to rebuild a valid GoAML document.", "references": "http://goaml.unodc.org/", diff --git a/doc/import_mod/vmray_import.json b/doc/import_mod/vmray_import.json index 719730c..b7c0dad 100644 --- a/doc/import_mod/vmray_import.json +++ b/doc/import_mod/vmray_import.json @@ -1,5 +1,6 @@ { "description": "Module to import VMRay (VTI) results.", + "logo": "logos/vmray.png", "requirements": ["vmray_rest_api"], "features": "The module imports MISP Attributes from VMRay format, using the VMRay api.\nUsers should then provide as the module configuration the API Key as well as the server url in order to fetch their data to import.", "references": ["https://www.vmray.com/"], diff --git a/doc/logos/Sigma.png b/doc/logos/Sigma.png new file mode 100644 index 0000000000000000000000000000000000000000..0bd0db1432a8091d6825d417ea0d2141271d08fd GIT binary patch literal 27681 zcmeHw2UJwc((VXIj!KXyIcH|bIU`Au1VJ2_0fsC&D1t0i zNdig`0fD#CbB=R7-k*El|G)LtdyBo6o3gvAy87$t>gw*b_T0a!p@fG`feiwI@KluL zwLzeBegOR#69sth>!WM{f3O^t4V^(CZibV8=RC5c+&~~~O@y4>)vHzxE)LFC4vw@c za&okeP7W3bTQ~^h@h(}%Qdei0RAP5(TUIs1_pz#jHVG!JwrrR$@k3^IIxNB~A#~|u zBwDYC6co@XU#EqjMn?LElW1|`hGLIm%+qB=`e%lOb{@=lXW9(c?SAM!su+=6EZj;i z@5N}u#En%J(1Q5l=E>3!z4vc#X`7knmiEJDbOaG%mc!_r4;jvZzWRuZbI>D6ERU;Nh_Z7O@6|x14=^!-Rv?q zn*&|t0^MY}`(Y5|oBTE2`yA-{V>*&^X%9iP7vUlDAS*FYVehq21&{$Rh}c54MFcd; z1>#lFw@?O^RDl|M2yx0ln7ANbt&l)A5SlmW=4%E94^Tinh*)t`U;HyiG2tRRfK+^$ zcr~Mlg5Pyau3H%T`rI_EJt|}z#DY*1Xo56%lV?0RzaN<3{ni^0DD5E$(A%v;j~@Ku zo*u!-N_j{f4V*OB-7t$X z0_h4++VVU7EyojV6w)75kBu$N&yQYdk-ZM>)W36N*<{+Ne`vesBmQl7=Y7L0qaVMC zpAy>F_f2oM)v~C1A7KTUO?-$_`c{Q={Ed2$zD32X+JK#SPV2(0Ncn{DE&eR}`?8PN zTA2opVYAbm2dI+M5KxJ=w$T)8IZs9#iB2n}2sXLmuM&02?7Z&@)>eXaawdfksV zqz;wRm_WW3Dvxe~KsV$Wxph0srP?t;Ao(;u_7WM&?N^sL8_}s=ou7Dx^X;Z!fGlH6 zlPsYuHq4jG=_Xs@Jz3VE?h=r&slkm}RzcB@_}oo;FYqNC3HG6wDX&=T{IJku zUSnQ_(!C2gXBKL}5Q#|^ANZB-l``6eFh<&r5MnKQ)rSyeKJ8FlT0LdzO$jFykzixR znulVqLHzf=1=T6>J@zZpl6Z?-Ay=I%L=m8vfzt{fF zWoe-_GRg*O1{$NvxwKS_V4`0BE)#%z8pVG^#c@@(xqQ_jL@B%vg3BQb9W`g!)Zn`cJB+5v(D7{kq znO1P_MX+mvNVh+!ifUF-M$90E6`9p8;@Of*4bfx_jYeFb{y^8V(o)b5&BUJ!|Y zdx#|~dEzCEOP1vBm!3UQ%O)QTBX8H`72;ccvKUVg@1SSEUdeu&KN%ghs57QBnS-4}sxBeAQ_P)jlSP?cEARf& zK4<+&O2ZA`6g7vODbM=FeX2V=hq{M$`#C#GxCZFuxQyuIxILG|TOqd`#Ho4O$3JVy znKVnwWkF-0aWerAtNX<>hZG|e3BnO=#&e8wc5QZTnat?Sbj+&iE9&bB+UnKWY}sOJ zVruQ(Z@M$OhvOlM5_}{4i;0VgBZ<>hH;p`uI4g52U%*w$=8duq*DLGn1C2xsvkdf% zOv)9Dl8cyzYl~`gh;phmqBROqhAo>*K38^B=2elGER3R$7gyR<-ho=0ky%!?7B{a) z*GJ!}xf5|Hfq@r1hF?H!MD64J+=*E9n&?zQPtv;7o!0HxWgbg|Uc%l{fw{_yU2H9g zWSzu8B}+2PYG?ngkg)rz_qw>_B)JnQ+a_Pv&tGxbxn(lrHL#PMSf1FlmOK7zU2q+- z9z2oy2As;4VkqF;*Zf-Uz1Bj%6i#Xkug!F!kEWcapJl}&1CKe+L;a$WlBz8C{9Wh< zndBEqr_?#gIdP#W{wYJ#H$sh7udBK=n;lY21E=NgG91H>y^i}qIlfHiEK%?K$@pDI zIr!>zo9<<#l;}A8OWD|(On6$g42ewCy`p;yuZrlbUVnU0M7O*6q~~D|0zdGo&(d%o z2J{0otl0;bB``QpKS(OlF~oqPpDzyL-ajGdJT1R3A1gnVXvwE3@{H>Wgk3V9+gkjc zPz|_7s*T&i@Xe4xvVn@NDwPN~d$FLsZNv1pwXNXU19M38 zxG!*u#!9P&Xb3<4BtyuaOB&9!dC{FI65F$hy2+BBr(du?WK_HwC!2X$(AYLkhgDWnN*w08UwqV;_Kj~+g_C7VyD9$jgY2UNZTxP$>b&==;+)MYjvn?x;GNZpku?)d10KWa3g~`NGNmNR{m^3Kak4m)yQ8cV z9t)Jg4xKOKCmuh~D`=^P^;!&AEPt5dO1X|7n;-Wf4&NSc?A>#`>1)r|cD^W#^Tc(t zj@SBE%2j8+6n{|%&*|vt7L(F_s=KYbuPeAdFj)Z|`D)+4PU&6u1^jVn{CeH)p=3wH zxZXKE^&yKP_<_nn`L0y){ru&xu?Kc0MH+Uu*4j}AP!BN}@GD2E-wsg^Na$)u4Rfq2 z;kRFII7;4d=fV@j(~C%In`Lm6_ncqe#hTuw!4rMw7Vcd*gE4s7joOl$$67XFGeI#? zC*k0_+4UODHb&ZXa&RkzvX>KF$4f)k+{?yX1V%3}>6wMLi_wzv~qR z=#yd|dfM+SE;bVMGA9kv8mM2Tm2+@{(?Ylfxy*S5g=mFDxcMMLLSP|IT7F(W2oJ9S zj{q+hzkn#1R}{=o`^Q5si49QVPB05mZF$8%ngjkPL2u>a;wZ|)Ux?JmIW;AF|e zCn6%k!^_XZ&(8&La5;P0yFfj-?422YDEU*5Jlxsb3E}91aImL6(F-+maCMQOr$1@v zujh|`**X5zkiGLC*a0Z=ct9O___%p_ekTMo|4YWv)yej|X)tphxGmfcZtvm@$npJF z4uDi${V&nqHfLw|TS;dZ1vj7ve<1Q(NoQS8M>vl*+}Xj^$sDfW23W%IJL8>QwBbL& z{Wnwr?7vmy_~%yU_jdhN{V!p3L0J3?Lr!Y`r5M};@h`+qYJP})_o*L4Ee@bAD(3`; zx;QxLIyl%${&3fy8!=!XcH*A2tm;s6g#F3xj7^;9SD~M-^HU#O9_j*@1Y`xd`1rZ_ z!Mc3BqP+Z~0wNr|e4@O(NJ@Ti{-Q%2m_{(D3-lja_(UNPQ2`-jEq~+vRgVJCqyuIyYT@8y2X&D|*g-AfJdXC3;ygbKkvPBmuc(T>vkTPT9IhfS2^4T65HL}G zusJWkg$SGrDk31j1?Ghcafv|qMYzo2B5*z-b0J<{IP^Ol>gvei|4=ROVD5S{g#gt* zq7BT!98mw4D~ZC)dCeib0v24nPzZ#Jk5357B@BTJa|y$R`32w>LSPZFz@H5vDf>r5 znobBH7(#84cux8P0~&)1f+4*8U^6a07!=F}DCFl7<~KLzGKUKCnwddi0&p1k&nAAO z<{wQdBb))x^F;DUz%RdtJ019~znA`5VT<@4wj7~O&hV32EkXb1wEip3{T0SeWWGlS zQK_pTadkLMoab+)Kh63}@((e@KZo<*gW!jLB=LXUhPxHq{=b?iKU96^`qxUF z9V}ejp-ylaOTa_^y;=N2@81P~Z9tsoWQnjv{2v$!XQdOYB?v?eF0h$75T*EG5H2_$1SZ69E-Vb;`)iW^?@glb>jWS8Z0vpw zfiSoe;y;;rK%j*JNiE#TS<>9W-VN>q#7|laCkH!PsH3AT!W?=MSb5y+VSnU@f2~Kf zE)KLvi{_8ngQV^MDS!R9*6{yMtobFc`Tru;{8&ByzLNi$9sG0UKgk_|6iEJue=44M z*vaB^`a%z^)j%39iWDdQO1=KgF!PJ}-#qgV?Le~iXZH1CW7+MmcH0gSY)i~lumMRnmGE^1KElNtY0`eXq*84g*f<=;#HtoTp!vY+jJZ%YFR zGLp2t{^C5ptN5E8Kaw;ggTE^{vHG_PzE}LJ;D0u;`JKr+2unL4um4@uug07}_zT~6 zqkpgBKj_boUjBW^kWD&K^^cK8GDXh8*1<{L0S1=@HV!{6{8{|7CrDDj<=xdEcXyJ` z+DfvtDhhIfd?JE^T>RX8$ZC-Iko25vZ$*K<1CYmlUs@yqnSYf1%5QM82e5Vhp5ycJ z3G?v*=OBE%!1ISJa{aHehNs9P*Z(T}Q&i6$;c|k~cT;~UMv^+&g^Pa2PX{=8f}eEm z*P>s!uOd9)wg&PDVDIOAvWF26hWsJ)Yt3&Z4S!1VfrXJJk!t>s{NXQ1Cq&<;`j1lJ z{0TVv;`!_B>mO0z$F%$(e*f&!|FB|Yb!Uwrqj4&I2G^+&Av2u8g^b3j^ch^ILWIn4 z1{X3Kr_yI|oeB{$!x>!2Xq-x)!F4J`$P8z2A)|3BeFoR55Fs<1!G(;*sq`6Kr$U6x za0VAL8mH1{aGeSfGQ$~M$Y`8OpTTu1M92(ha3P~{Dt!jmsSqJEoWX^R#;NofT&F^W z%y0%5G8(7SXK!2Xq-y_mvCYK{F(&Z9{7ZWJMgUtsPC5>z;`BS&6Ty) zK_D+C5Xjdb1ll_W{w{++uDl@7%1sbR^a%(==J3e0RS^WD=}?iE(e-%uG1*&}$~CtA zuHIqcdbS^!j)xWvQ#}Mrg+)=5l&CwU^`glvqqz&YdtbOegQ#emRnrSEi&n!~j?7zg zkqNLM>V-Ee%$Z+>6U(DRh$hw4X#JFBWm(WRFD!x7-N`0RChPA$J}LzFZO`fJ;Z5|K zk_0ao=NC^`t=GRiNJH@h0UvC8fJI?{4){=*3}7ufKzx5upaK0kd07zx#K|j~bm3_o zyBL|3`>o`{mo}sB(9!ADon7xk{d&K1JyC>C`zXdBHnykwS25EWnIzN|m$E_O z^!73PLD4|b*Znm_tA%(7W5lT?!(wL(g-F7QsYvnwGxiZ5274B6z-bc(aj z=v+#gm>aRIjW$HHcd)l#8k-fQ#)`Lz-gnofw$i59 zyp)^ly%Ik6G&)gj*WK^N-t6sWeuftoAi{QvdAu|-(d3sK7KhQFNe3|x>66Ej@-X}{ zL9<%^JuhxYB~e(rIz}HT^6;}BOYlG&G|u%>fm$A--|3s1a%tnk%8;6A?0Xwl9psuv z;qeh9?E{!{b;oeAC+wqc?c8qf@DcMk$cG&CI#0%p`|7O$pOR@Yh=Z3_*-FuQ#U(rx z(56+NLe+5&%g1QjV-9W7rI=&SWTFcpAZcs77011Gd+NH6Uz&?jv(k@kw9N43^Un3r zb~F@rcO2>z)Q_N|Y*Vwb@Arm9>|Onc?TfC`sKQe9Ok z22hiZzTm&JfmKWWQ62;i;hX|_rP<{>GzWL6uNwR;YPN8cP=zh0GdgH18K?#{!*w06ARb5BG=DukwIHePuVloJraWK%MRD--__}o}v0)m@90(=yUg6 zlXU6k2Zb-W)GkyYywC|8@69)a^C+#6JiaO7s5Imt3Sv43ms$;x^pC!{IY|vdH71Ui zeLL_v?v&y*A0}hnGw>>%9{MhjQG}MK}qGH;SdSCTiZVBv0Km1!FjAtPf^lj<{WK94s!J~=4(j8 zXE!dyoLAI~y%ArmZ?$y)9r4a+-&d{0D6dI^Y(7<}2N*CeA+D!Z9^&_bHLrqsC!%BJ zp8U{rm3|b6Z{H&H2*4`8eEMo-Xz>8-FC!x70EA zW^(xrvDW1PLNe(+(=Q_Q9jy9;x?6aIkD#&~ywxGD-N){|j^QZV<-|K=UD0<_vQkA+ z0ti=}oFXw*jl_={4RlpFot@Y?IL&*bj~-n(x;fVt@l}{K zAu;i~kgb)#Gb}xH zIv9UEDVI^pWpjT_Rc%+-idHkvFJqQW;o?smhn9`ucpMb~mP_W+1G-eMy7neF!HA_i zPU%7(N7i@gL6>(tQ+fu%pb~Yi6sxx2Xug2A7zDeCCxn3|@7 zea0H@9Nc@`4&#q`{1|3w*_$lpUg&(&u(F_lV=1Y&rl#pFZ7kfsM@Ae^)N4EU(@ITk zEtb#Mqr-#1ji?VFK4huJY23uyZ+qoRpj7!lu;=YJYoB{goem$9TphJEFRaphNp^G$ z!@cqu?X_8{?zL+{eZ=Kswt|VOSFXGpAKzV{OiWDV@B_KJyT9D(5o;3iG&CHWZ4GNS zRKRV1?_4=%i-(7&Uu=wZchA+;^}HuIA|m3Umos#;mCO`P`Qe0y;zu49$qn1c3bCK#W3 zzlV7A1repo=Mt)nk+ef7Sx6q+w*8H!8t`ZofXu~2^urh{1Wi}jv-&R-H^#L*$jy}Q zQBYD=*^V`Nvoc2`0OtYxsXx%kjFE+fMNBM_fISd1pq=I!8#gzzkDtM<(9qDY1@G}{ z?&r%CO7+1AR_$!l`cnei5_KT75>I&6_{jOY-FRL}s$&q)v@m-qC@9oT7SOVM-Wf5C zo4T{~@ncK7(1auEUB~&?M6;&PEqiH>ZtiU^2$yLvGQJ%heI1tFY6iW8ZW^eJo=i;f z7Pi=ga0OaO-HPbS&XaNHMWs2$a#y3Ssz3KuzIgI5__9w(J+EPSSRWs4I!NN+V|iKX z3@}Rm>y$EgFLnid;y2+&IlNsu%xT&{Mb|PeQzt`4d@Gfg!i6zhs5(K`bx#oov#-KH zMNMM=odz=_tbDA5C_rC7sj=)MfPWwgF~M@+D0aC~5lpD4VSk0==>3;*2NoZe)z#I# zITDU*v;m}?2DIRSh59Tl`)Aeo@eGMSdjrpG1+m^`_fzV@;sN=HsyF+A` zXu6?!oqdO_?_TSsjvvpGE6LfePOF%$C=1YuDZiMDj*BDHr+DtYYfpumUs(89%a;lt zt>MP5_?qh$Rtge3U4_p|dk+_U-KS}VF1g*FpDFJl#=A4PZ8fXIMp0vt{<*t6 za*?OgxC*jvdX`cPz~-&V`LtH?}??icr#q?827hMa`c{ zeR`=MbX<+NIW}?_mF!$UvOyirD}B9x{lz7_`}gn3#*77xly7fx_lMcPOip~Wr8?zv zU{|L*e@8V6ZJZ5lH^$K=FD=dY;}{UHjZ5fVs`eVMK0wj9geOX}$#lCwrkZVp_EGOn zKa<F}NLk&w?#afAiv7dKc3}i$3q& z{v!;rBx)Lzb20le>m^*-oQbL3gU9m2j@~1@eg_ZY<3+4(m2Z@6Ts1bP%A}MQlI@2#@!!rxDz;idi;ODRm+rE2bAKQRX&+G-Nh4&U7+<8cctG=d z5hqQ`O;k0Kz-NJ84hS-2?yq1cJ%Byx3jieBab1UoXZ4J$K zRg3~I*noF$+q{mT^fduchB4ONf)kU zr}fG4pU=S%=BLm#FbFBPSY26}=c1*sxu~QFEM|&_8>(^Ktp{UTV*Il00S73!e#Pd>sdCl~{`b2x3?@xi@KRaIJ@-MP~nM-!OK>BZGWzcTxxR88>C@Of3&bB<~ZxRgl%w} z1XwuAgy;IL`i5-QXi+0*x|zvIKcDZ@v6IU%k`||?c`5LLlM2pII@m5jQRDOQ`BX}m-qFRDlwg<-zN>9nEDK8Ge>5THrr5-a}?An;C0`@h_*@SmJ z=OTn=&8dfUGUJ@1G~vD`#|XHKdIPlew~lznAr(F23Q%HM|X zYx@WIg|G}i@1gYOFDjm1{6N$sa%fomB`m+w5! z&%bid(bdL=P=T2!p-=OF7$=-s@}`kAX!_CcP^z z6GQj~bI;qOm}ynRwQU|K2az@NE-WmFxv%4fUIjxSdcZ(myB3-+Kkzc3eW5dkgHEkH zmxHDRbpIh~fGgu0DvlYwa5d~GB|Ni+%h+F*Y`Ns@rAOIncZVD%t;`eZwR*XfS}J2C-22=QbYU>+>CXNhzWa!h|Cq_SnUc{2T(LjkynRX4i9Wcfa z2;^j#MXh#rb~MF52ZUEd4)`|D)76bAbI>?`&_Ey%A;bYof*5immI2NrO?Y)Mg7yec6XVV;E=uD5UcL?ZiV^z7F+hNQSFSm{ZM#5&K zcrnF0(lkjhu?R@|WAty(%|7NbdLH7QX~sr+**)MhkgAqU6G;x{XcFAgEb*d|@)3WI zj)mF=#lO;W>$78>IMqlLc;n%-@%P6YK%N$AgLg%5kO|A^p2JKOH$hh!xy4hq%+S#* zvBM;C0`rM+-Mnh*>fMplVxMfBfmF0#9S`?Nfmzw8FSluT_`1VqZtEppa{NnC1uc8Ff^dBXe{gczr(m|y!;jtxw54{(9^c#}XP zPwyG-v7xe0JzH8=$$PAAaEroBTVW|k(tI+4m#C6Hl!&eUvZ6T#S09JKPD^FRIC}kJ z-R3-6H&adcoq;dz8Y7)`7u$K?+}WKBZf24%bT{Vg;nDZJK9*@C)G9a9x9Lh**AVbT zmncIS;uf$akiqSSu(QJ(%zLckPfDRIc^DxWWvpqy+z-DU6RoQvFxwa+kk}1m4t3qK zEnZ~BTa}f9V-QwlXSw572@spFygDH&B*HhT`U&}xBxxv&ziDY}U)XW*^M}ug#yIVp zl|vZg)=l?eU8+f%x@uxiyFc{|a?C;DX6|n+%3B*W9r)uN4;hP1BQA~ zq8|zg3JHvsA4y4?f=PXO%mQin1{bB4Q(6ZM_$<4>&8iZYHg&58nHtq4vYBt;21#bnNp$Yp z5n_1s%8AL*pT`F~;xF)3W=DD~v%tW%f;f^PL2mIT} zx#bpJm2~(ma||py*O?wKP9Tqf+RR1>O+DaaCsxpee6+-xfN z@A8SC$-%+Co@us)<_%5{)0HDM3+7JRBS%Z_RLPPt(HIM-Wl)DlZ?$SDQx5HW8(OMi zh^0@HVLVY|zPBt6Y&<-eMn*=uAbt)L8GC&VjBf2EM8vKStwTJdj<7gMabC%QM)Qch zdqN8-S~|03+!@(o0BoC4A6Wxui=Qvxni-N7G`|?qe3&5HbYBh#gwagA0Tm4VLdV|b z0q!t^*aZRiCLZn9@B+4`ecZBU5yxP|()P=pc`|X6WpMOKwVubH&iBz^>C_KC-*yf1 zz(T*Xznr6`j|iT7Ydi6R=(xP5#;EP9OGq1aCN@R`rJbFfhY}}tJWbWS>=Q-hPNF#? z`>Ua%M^`oQYIr#lsbMPg*)r3(UOY|%ffYx53Kv<@)7sh;8?>qDbhWhT+JsAhBLwJY zcOa>-vn%4HTOo`PQ#>EFZEa;mTV^A9?Ev36mB{M>)aAV{gBD=tN>~RBqv$5Fvr9`p zxS>SF$VU5$OY9gBb24x$z~8s$bG$7pLj?*oQ=6Mk=@4Cg{oR&}n#$&ctpiw~oKav6 z{M?`&u8^Nuxj{H=89)QOv~YzeB-_K4vh1AY-mzIwlnSMBOS zN=T?KU%E3&09F7INm`&vxExipe9!A9rc`mUEd^lMhR;2FT)Qb1uTXY(z5ViCPU9iw z=)9*-8O6lB*C(nOrbmIaa=s(_T7lkZdYf6P?YrkH_tM|o-fGh~y+iWy%bv3$W=w4C zhxvJ!g+-9Eq{Yc8LjiC+lbMN1rY0{>9N!&A#zQ()umq-e(Ew@C;#!^nX#oP(x7`Wn zs7o&^Y$?DWaU7kL`-;%v6e3E6N1Y`w%i`?3%NMC_U-}$+P1U|)XD}9M3Ln=hXyjmL zzxM0~*O)P05EMw{hJjqgedET=9vb4xRVym@X7ZT6WOQ$DZ}Ye66I!fq=!dR4)Do?U z1?-TUZD)NTYO9W-61i|xl7^Kgr^w^lg2ZPK*Nmj|W+hqq#qVA>_7Z*2utDRaY zk_usEh50VH(x@m?&_^g<4cOsdWI&!u5RXgVfB9UED_#iPm_QflRhgEn zr1*8jB8)E{NV%!TvFRrjHuz`@t%9We=jr5{mcjSIJA=4pihUv&yA8S!Gh$gyiM4JR|GZNAQSV(|lZW;flMn)rur5!}eEE}0ggOE2< ze(z1Im65IOPfvi8*IBCvx_psj&UjD1%n`{tq`F^;HQ7@}7f_OGHV2N|?>CA2JEecc zu^r@GdjvDn+sp~B6Z2;3R0XdRM?IBe!~~ka!%p9kFKATKF4PxKQXh!(99!8|-D$2% zN=(amPXt)lGOy%(smQ9!HuBNCSckU{R^vS0kIzJ9QG|Q}?oHlcrdxr)$}ny4wz)us z*=;9&^&xI>4=lv49?$boGc_ic-#lHW`n+sE$m#)DoL4iofcZ8BI>jNI6kY1lcFNkd zMbBr%rB*@NZu5R*=)e_0;0={}YeB-o@h6#2zubtkq7%J6do|TtDN6p}Gh_>|`htbV zxklX)+JL@Cu$@E^bo!h!?VQnC#^oBuN2GmvRmYzN@DcW{hvgNkmR9ep(X#Q&hPtDk zrnj5i@_J3QR(Ua^FLvx*C}(}b(l@EQpu;lwC!R-YgdA$x^|h=-b-X}t1bakZSZa#l z3)4?|*Knxpc*IKT1f&bq_K2iDd@Ak=+|b0`lh3?B4B+>O zyaX{zM0pE&^^U@`gY{^+3&uN~c!!@KJvDrA0nZn`l_1Vw`e8we4*oi2+{X21$LwU% znCd#(Y*$1zbDliY;ndJwpwPqa{}?nVHw9^5^Y`iBh*DA5;8IQ^xCJ1M*=P*Cvl@w) zvK5*_oSggg3o+w|RXS>-CmFt`ylM1bK`kbX@5}q`R6lV-%lI#Qz|67hPsn7$(rH=Kl8Nj=Ma47rB#J0t#qJD ze)68;Xb=W))p1#P_5HdSbmLRV`n^f1>j#OpsEqU|Agext+e3>!C6myt`?U(M``!-k z1=XezqXLf4dj#4a^jsV?DIERME9>v1bOVJr^ScDFf9_49c*n8%n1&)9WAI6Lyl7dV zEpQD9F5N2|sn2Vsb5o@)*ZAyYkK!VacGCEz?S0cV$2vAA8!s;PLwcjCdIWT^~ljZXV%-=7MFMmT*(5qQElwjdGzeW%S~i&w|1iFFZH zl6$6cay{vnMgZJ#(p5=04lj#24Q<^;ozh&p5hZ+Jas``9DBnIlyMf8Dq7T#x{L)mo z%79Y;0uB}BY(u4Y;z#4e=vw8BgI+4Ul2IOG00uQR_ng-dsorWn zDuzx6Lhn8sncmnj-9bkemXfs!!TCy*L3HJ+_CN1wohw{YZeZ0zK!8Jmd{yvmmwvqk jKDj0Sc~ugyz4weh`X~`Zi(W^a5ky3t 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 R4SWYhQC3Z+7G@Ume*x<1&bI&n literal 0 HcmV?d00001 diff --git a/doc/logos/cuckoo.png b/doc/logos/cuckoo.png new file mode 100644 index 0000000000000000000000000000000000000000..57cf35a56fae11e85813de9f00b349615dcdeece GIT binary patch literal 11970 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 B){OuF literal 0 HcmV?d00001 diff --git a/doc/logos/domaintools.png b/doc/logos/domaintools.png new file mode 100644 index 0000000000000000000000000000000000000000..69965e1b79e38db30c6bd6f74a110565efbddaa2 GIT binary patch literal 4886 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 literal 0 HcmV?d00001 diff --git a/doc/logos/farsight.png b/doc/logos/farsight.png new file mode 100644 index 0000000000000000000000000000000000000000..31a73c101d62b1b19d2a48ae954282965fae9690 GIT binary patch literal 12300 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 literal 0 HcmV?d00001 diff --git a/doc/logos/goAML.jpg b/doc/logos/goAML.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4e938eed84847fd9b9b36defb82e9138ca2b070b GIT binary patch literal 31790 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 literal 0 HcmV?d00001 diff --git a/doc/logos/onyphe.jpg b/doc/logos/onyphe.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cd16f760a53bb9f618f2da373b8c00afb8c32191 GIT binary patch literal 10439 zcmbt(WmsHGv+iPWXMo@`cyI{rgy62h3GVI|2!Y`49^3*!g1ZGLXbA4^4ne{hvd_NX z{c+BZ``li$y5CjRR@GfyJ>5^UPpbfytfY)200RR6Fwh5hS_kFGym|9ZSyf3=Mqc7C z0~Bj(V&?+K1^{;UuFk4bVh}BD9SGtYfCRjNzFYvGk%^1rYh`75;C~I*GtadHz!dXy zUH@yg|CxbmYUW}B?I04A&1>T5>iCRfp%~Z1(#RBw3!s?E8QMW89)G4A z|ARN5vBf|5_8B{?tG)pM5E>LiEdIev&)E1M{45K^#M0Rgn#UH3sqJjtpndooo+Uvu zv)53ArX+t~7eEz|0>l6aU<|kcR)8(w3NS-cd#KL-=W?(9mQw^Apk+*;u_NFGI73TV z0G80QOi-#D-~yOIQ*$WY4EoqYb3nth?w?Zt_>WFq%sHOxgNlqM3jpvNPfr&N0DzbQ z0FNP0PxpCGPmlQk06Pl+or(V`?~n*p=P{HX`yU#0764!d0ziHHe`v;q0MG!{F~PE< zk+ad?c3`0~$lM$N&Wiy6T^j&!pt?rU{r~0vqi<;4&-DZOzW_kp2LR+p0U#|M0BE7@ zVd_0C0B-t8Qf{%ks^a2bfpdcV3 zB_$^($HAwhrX-^#AtNVyt^@{J6cGUt0|^O(3>S<`_WvB7x&bf}a0Z;g!cYPrFbpgh z=BXDtAz%OyEX=b-{TmRG;NTHpLC;)bDE*oHFAJK2;9y{(GCa)#Xs}Qw7AzKY9MePr zSb{-ueU?KHFkDAB7$d&hp}ep7j~@AC!LhAhY-Ed_1!_|8Kq>ay^3e+SzJd?WHU9qO zZc_E}$|pLL2WPp5?lap2blA|v<#_AY3+3Bx zLHdg9)XXo46A@iy<<+@fw3OT+m$`LZncr|o{S>eL&d>=upVuAAD8%DuLO!bPl)cvl z8W^oXomonUKNh5!VKf+%z*hM(p7OwpHCG+=6aYlbrERVYDq23AJiu2?B=WJZ9rlt7 zt4GL~5VI@lNjN7NfBjnZ?E@`gG=EyZ1$vd$obkc>!=R9lcHV6fis6Un~1Y!%%9W44VH=K#4D($5;rvlVJM>)iT0{Vzgb4_P5_jozZST z%@?L4PV0AH6tj1Xo$R~?VB+jck23RFa$I(>#Ahg5?!$BEW}q#>$L{m_?GanNr9Egd3bC9o=sWhTl~1{^0_(&Kfx}{N9-KkPyk3WkrU`%s3v0>AkpLdX?97$ zj^TT@Z`T}D(f5*+7XXdUC>BfFF6Fj&6&AU(72f)BfJ8&Y3aRhEU|BhKR7@JY`NB_9T<`lwv=2MrJ)jH zQ{Gn(ed86d<3lF|0QQ5eh=-+c&$Qg*0>R)BVBUIGdpSEtesOP@HKy4aaGLB_KyYgN zxqI%;AwXFhC@Bh6t7Sv^m&^B#b@){32~uEtK^Hn=cVJ1z@z>Y#u|F!}A!@J2gvLvi z68HOHp-nso*zSfDszHoI9&Y7a=EhvMXByPR7_k5tI2aHD)T;j5%-}#!1A_q|h*;P- zxNI-Ll%jYP)Eu1bP?JM~nj#E5=<3G;ImslG3#(O6l!xqFjdBa#VYB4r)X%9FVtQke z0rh<%MdYTzPXJuX9AT+VyjptVaLvfRYz3KB{!DJ#l*(#dK_UH&Q{>uspoh4oQT9Y) zwMPE_dG}t(5b|wC#K1TrijVLCe4^!%^Z-8O${jAutXC2lufI%^lMqf`o!mmcSqolN zXP!>D&H`B>eY}{X#E>LD?nUq?@&$Gb+(En83M>-pKJMSm9-7{%(lFF$eeO^7U2*;p0zpqEjsx+lE+LZE6lPEOVwxosld z=agyJIO6N5cfjO@vp|i3i(txv$tWQ=m{7jM_aohF+nFX@>%Ek2zYA6SAiAAfE&tDS z3MV=hT7i;TwWCPR(ys&LVw1V5*Bm!o^ z`(#M7d6Ta0U%%gu>w4OJUdfzqE)p`r+WjPzIG2044!loOB*mYhZH$ysY4LQd+;&Os zm#?h(;IUM5&Et&{FBN0>v+ejQ!~rvNH*?2WN_?jV_cMm3X@;=8Bc|u}zQX%C<~kl9 zZZZ$^7MDM>#Xe^lm<}}_V^-SxZzr;b;V86p_?*yVIu_J5HT=y2$z?_@$mNgAaJEF9 z8>2Q~`&ftVRoYhlxo8$5{np^PS=o>fDi@W_AH*|<^~;#mH2uEj9a@Es&fzT2+mTaq zua$s3|6f=?WnzD|e^tO>IgdT+?{)ZVK<0m8(IIhKMTB3;b85*Rn3nti31cO{sc zT3qk(J5bwWsMdyDTEAbJOfq6%i(7`mk!DJ;mVYG)8%S0b-DaznU3vnzv=VwV-_aB@ zO2pHsn0wQY&!JaDHwLFC&`U{cemte*b3zcwiMmRY|2{~grRpV()YU!G7f0}RzrrV9 zz+qDVLuvIN*M2^>1wrh7S5=P*bMJ(a(|%s70Z_ii4?)nFJ3^zN-v+YfYts>T@w-}! zK8eFNaW6CnJ8L_)uprrx8p%2W0}Xl2X_Q!|(&TibwqJaeyy2Wuv5E)2v?ie|Bqpo) zsI84nuxvFmPN((;xN=9;B1YcnBzkN$Bs9~i=*aa(ehFLA|4plZt{T*1E(#n+TV zVm3=`jUPM{0c##mk_^fIJUc6Ubf9dT#L9o0a z>$s{)^yUrc#lFjeivu%h34IeOrng%XxA&1%&3Y9*5)#1+c27laD*dE971cRH9TQsErdl(lx~a zcMfTf-H&72<86`ye9}B#MB@^TaM`+B-yS*ylB?b`?s$Agl$U2kJ-L3T>J7s_BdZE8EcU&-n zf(XSR>de|;3q4xCPN^&dACKAO{5Kh8<$)%`;@Pdt=!H}hGPkJ{7t944CA?AYeLYeZ zNuQ|+>HUmC8Yxix5Ft^DlCa+OGBT+I4_k}|&GpZV<~gzZa9vw!j~rP=33>@By6biC z3XW?L=(G{X0-%!sgoKQY0RN9q{X1=-vxb5aI@49)v2dtf89C*4iK(g?I|sy7ui;X2 zm`t3$;1qr1@-Z;J`>f`_T`htLXkt`O3e9)Q5Z|{+qGqNhY5=s5Szh9AEoPS~b5ysw zWN%+&daV~>{%h!c>q%DonG#yh7tRF)d)2fyBhT9i=UCbJqAQ*!K-{b($>eoiRHQF4 z2Klzkmqli_Y%XmI%Jb$>k>9;Pmn9+^uU@~k8|}Qmx7je%GKdXihAX}TXtmV8vMfB=OOKV ziWq+SLd34^_1#eg#c5oJ+_(=^foLt%=en1Zc*X$H;5SuAlOzQNwizGZ{6hIueX1t+ zIr>$3f^j{B^u3K{C0dC+RF`}VoQWhi^2uQZb@|#Bp8m_4QPhyA@B2Ms+M$3AZC`X$ zXdx->VgNa*N&_<>>it!`9EyMm_;w#56uY4iHvs3g0zX4+#-IGGBWK z-xq|imUzscC7L*6asA{#%Pu?V&5yZYYbW$&_tGg9Ll3m7)**5X97~|T)y=BNs8&Qe zuG?JptPIzTysL2QNZXR!lf<9tvN2-&EaY4fR3%$OF<(rWANPg|HI=l!3qRfKRqrz1 zA4281eZF}2sM#8IS$pYUNb|C#ZcPI`=(C?5NvIz}qN$VAT(RCQn3rT#(kaj8y9Xu} zro|pD`q)CUcdb2a?MEz%d_#12(q|S(PDMH+q}0=p2YOuWdSe1j_%F6j3F;i0fRo;j zpOm&m20V9Xg4=$Nstovk&HojdNr!zGe@nii5rxyhrShfpc5ZR8jFomuqvM9?Y?|Bn z*Avk21lY<(4LZjJCmhzH--HHt2sxP*rQ}mC+77tTeEI{YgHrfsec3yDnI4y9Z;V#Z zr}|6(?GtbmrsXg6$6I>eh=-r}dtO;Y-uj;(Am=8M2V z_ZgX-Dojg+^K-O5vyj9MyzlX7V;z>a^qfxx+@EOC71-|@bXgo^57v^)vm#)3>ZrXJ zy3Hr(T%7&7y#){K77-(HDeX%v_joH&B*S01(S&BunqK2a^XtSl=KhW$<)XNfKH;rf z*>U*%qMt13Wa!J_XLm(D={*V8z@^IOM=nUqAYJSF%oC8+?yJ_nJ748 zMCChqS2ac`$lq-kH|uECm&^Iv&DN=UO|hReB%Re6pZJPI>v?+(HnDd3^@NclK?Z)u zyB|JYS<&yc-r!%`eNT6JtYtpMi>CN0g?iSo7V$=>#fUU8zInJGQf zR3Lg-@Kq%Kn~##wIyZ#)>;mrVBdk9XL;GXgu8WAV&O?^2*0&NZL~jj_q+VZpp--r9 zUvrNrtvh~IiFbg_ll9a@UR-2;!Q>rlv+S|nC0vtpgTE<*2T(GxCBy(?X) zP*|K$O#Ff$E-pqet>*e<VRug;BvY69;t61?#tan}lV)0e9=QH6*yNeaOBj zsLyZObneet(hoG9I`~_j^>?#J&T&v**ZbYoqEeBWU6_&T9ui73IDh1wPtSFbJU(ck zVrx@+ea+Udvw!*H7m1XGb{{UdflpX-UyhM&J7>%vCXAW74WHl+V7e#m zjK1(GiNs~nVtAni|2B&MqB`0YliuUL7G?Llw!&&u@)$(D|2Cqd61O?G`2E@btu9Xx z{F2jqyH9FV(^*SkBNFy=fd8t8 zC3aC(mA=niIb8_F^j@7}(k*w`#g+Vq#TO&&?cOHS_uYeVjDbX`ANoCMMb8?W0_x?K z)5f^}Ac`{3FU;#l@@Of+Tu)hN5vw@b?Cg65|Hw6h1MW_$CGVNJcn7$$$~|@skjDk> zSs=5c7`V0B?$bx(DB%`ecKIgm3v8>3{7$6autM@*NKCB0si~~JQ7ZXN{{+0YB?v}) zQ%o_Ku%yk&J;m;vkRk zX2I~R%8Wmh4}-}Q?3^Iy2Y>fzW{aWFir8>g06y6dt2>x9?zi2Yw$VqfXs+pe1vh!^ z1!}!WCWDj{74LjOpK^@=I2%N=JMB45Ym&0}GtKppD?8%8=#@M7Y)pwA7ReM8U6OPT z*acfj2@L#)^(0n93VY=5T0AZ_HPom>ek`0-TQk&Tm*@St_h%l8IwSs5-|T4`dUxQ? zf@b-{<7N9?ttw=N*5Sv5##?m?_IfPoo9ib)`7v4e7r~A1VfynfmL3cG4;bdzvHyE2 z1p@{spkCc50K(=Jmy1>1HF3IjCi?H6Wf6+4D989xX+v!01L-!V=-%n?-+m{@C%LGp zQAf(Unsh*>wi5EU3q;#ry+0CE+swbtUK7V*;XQi-UK}ZfDq{tL^Ugh@lgP_tWhK() zF9U9nrWLBZ*Vld?izgYX1bpGDx~bO1Y{>qrrAzwuzVg5Ft{7 z%aq-tVbSQN-RifjusW+@<=gwRDT4ntO%oq=91JC>BF zt|S9q8DhRi@D<|~exUfRwH17bg)UEYqK?ck0rSfb9OV`8oX+WY^1Ny(29J>{vk@zm zJKCS(jPDhQB4iCSFzgsQlW-j5gh5CW$&(Ojy*)En}t-JCeUcp681EgY|<)e$e+vRLKgDJP}$ zz&Qe{D7Ad)i!uFFUjb!Fk=L$cZJ^i4IAo0LRl{e^ry`kfxe&r)j1|X-SQBYO@>x7G zD@jr4#u$`E&#F14$Gbe_TI=2S1XxSo-+kXnZ~2~pLA#t+fW8H3kTeUQzALCZPjc~F zNRXL5r7a^hB+$?X( zooieQpF(vU1hN-nvyR+k>%+$lCPBKKH)dCzg|U>{e+2o&A|ttY*vVJN$Qn*Vm*X%G zWI1WVY*Aa1-LHU10ieC8MxE@#Y+w-f`ZA2&b-E?awwGCC;`RAJ<;cECH@#eK-Ta+- zsu#OvRv8c~x{IE6Su@*4y8?3P#fwmp0!?kNP5O?`u$c4nsDeWj4&AvO6TJ?H*HeNJ zO?|UJ)gllzUZ1Z=_Z+_a1zLs(J;!G6}AW$ zw~e0mL;TpY?sKz{UQfVddul+8lc-o0!x>z?+CRBuG1?IBZs^f=?8(Fc>(%veilmfH zfEIz1$Dr-+tybY}z)JIzz95~DgIo)t&F`}XpH%{BfUjd(mi@IjuRb@!NM z-)66;cKRJMikp4jHNeg(86{tDWp;~7vt+fyc6-o2_7#Ou{FExFrg*%s@_Eg2dL;he3t`A7e% zO?`*qO7^*s-<_g9KT}tyC*eb^rb2WZX(@KE7Q&4nwGv+=uY8?!4S7~)3My*qdq6p^ z^{+H-nGOVb3WG2&efXV0)p(!qxrh)@zedM|*u#ObCC}rcCiLyy!`scz10qS@b4K5;oJ;^EW{36exY%i(VHQUis)*s zO6}v*p+pnrd7-D&3O|Ixr=)13$u@B%&Q6bPzT4G2HQy7J@n|G{A9G&8Q!jGkTK<;y zjzz1JlY&w?H)S;7peO&K$TN||f^5B*}SV-$O_%GA% zg7}ggKTWv{M}E`o)=XaQnoq^nw!HT59Sj%4d~GM36eMLarN;ajb>p%e$CUA+=PN1W zcP7PGRL6OX`H2z*CH+cwf{%X8LcSfwt#4*`{GtBg@BPK!yBn;FG2!l^E_?F&i!s5A zF)5tqI9nGQ&kkISy_y;mv~v*L&B;8+dB&x@HRwhW&h0^oa z^K&ue8wBN!{quErGvtp13B2(Cm;GxDga%1Gul(ox&VOJ84f4_D=U@K$MW^pB z`X}H~@pqrV>V`Md{f994C>aQWL6i&L_p z&;t@>4tng6DX9k79+Tgo){7=MnRnxbpu1b969H>2j=Abhl7x+(b@+h!2rA`Oh<$_N z^6L}UNQ0e76!RX1u9~ww#=NU==9>=k5vPzQ_4SptDqZE56|joY1IcPcQT{L~KVZM_ zsRjIr0~fz62~qi!w|Wz&m|gBJ05iruizBH~5};ibrlZ}ElW2{mQrI>$tr_rgrTR{S z5In#@(^(l469$#-B~&)x`9AI6Z2(j@EHFES0=g4`ZUedkswdV?+0On7`FtdVmC7jg zQY=VFLHY^6k`OuB7a3wNu->~O+|O^N%(Hlav`p&#f{|m(OhUe^^Fa7u z^&Q`B`duIC0AitPbWESB{xmv2tS6csnGO%QlZQu8DJzz^@x}jWv((qgcDuenzz%ysrTKd~IcDv7yr1`du&{8T|8pJr@4gS>6cC5SmRl{_ zbvp6SctdlFVBHO8nJ=mVND33&skFu1$enU?R)x61tK!yH))+dXNyG+0hN@y-1#eb+ z3|zU|*kW=AmlnCOuUvAgPQa~$4PX;dY!mZ3{jUO z?Ci>Vt*<0Ih*K4u(&XseuiH4$xo+H~t0E}%r&JX&)o)c33B8Myeq;G^5>Ub2c)d*V zM~?U)i2c$-yyo60$oXCc-@n_-h-F~A{6JiMP02I|l#AEkXpdqlO{m#EhW#inv$23la&Od@GwiAVshO8;{yuuS7xgHX-hIQ}^Z zCqt#erV3Of%_nx(Lc4}hNKic@-RHNJbYdh82c}l=YtVCs!7i+V-ww)8DHvh=Y&VHo zR$g9bXngJ>kZbH5Tx-iK46E_;)fYF7vBgC!b6e!cuGHYGIC?28+x@vu$i?_cNY$(q zUsJo+{kz!Sc1-dR5$wckMl4l#Dfw9OZAs-p#NCWB;iJDv!HuDdcaZtw`kIi(s45Z4Uf**z#Pp60BQT<~ z#MS{Re9)^K?Tj_GR!-^8I6|CVw|+<*3NfV)1BDiXrmk~lt^I^O)?91V>*h|*noVk7 zIcXBA(&_II)S9|ahbd+ZH8l?d3vx&$p0<0Qt;J!azgB;uq_vCxu!m&L7w>RZ-mz=9 zY%nhQp`^d!$lAg+;BQ29iFvJ%kgM4Srw8xEmY-qHcFe<{gM5~RWE&+dsW(F0?wBQ^ zqYP%N{HU;4^w>R~9rg3KqddZw4?o2c4QISaBSJB>oi9m)BG`bdU2pPmZJ_@pY;u~8 zAs;tC;#N}gBetQ$c$7kj^ZXkr-k_c=?ydIGi79du&P2@U-SmROFFH1+QJey8LXVzlU6%}e&O13Dj{o-K#Q+z~>0)rjQ zWC~v#&lhOypm75`pN#$;+^G*k*`3phB=l0J?-1pPAmE5V|J_}&c&QqWrQdgxNTCDi zs!2cEC4eP!^hus%estMpnlzK4fC1Hyt+%#Ti&S%Y@+6a(tNHys^AAxc+H$`+_vH^SFerh$c1L909+-Qpjj z8NB%86r{iD_TMk$(N}O&i!`XnFCvjUQzaOr&3!~Gxz{S${gwZDcja^N3kpH5-PS83 zi1UEOucqC%PH=(|Cm!>>fzy|*e7DpzNK(|~xWS8OHKb>!yPHjd_uTgPVx#u?r!(XA+<<4TBY$Cc96CAqZl%+C=yctMSCh8q3FS;A02?cYzYcU_Fg_F@ngRVVxkPubS>^#d4o zQQsaT#nvq(8qo6F2SfNzTT74R;K8jMk9@ZQSvVYtF6fRH!6h<12|)+W-yz)siG$+@ z#KOH0Zd#5O4I@&^i6ZxT#0@VyaIeHlBb@jch7uvCS^RfSHivv==AaEbt20 zoYhtK0DH?&5CBQmfTPEU8`Q{#4|_qP)bC(=od)=)H@xG;{va5QiP0E2(V(V41bdB& z1S8g^sLCiv#r;0QX1S71Ebj!RV?;y}w2~8cbvVUIUS4L6!(^u?H&*$E(~E`@cRZ3J ze2mN%EswtLSB|8qFo7vEL|1>w-RRAJ`Vnf?!a>Ld zNz$}n1j_;1>MHUoZnomh3=mcvcWD`K7*471767 z-BQt{4_~&I0qx|5QB)p088Y;o4#9*Tr|FT-AQ|jhu#{=@wTI?=xU1c9gp;o6S6&ow zwFH=MY^E;83Tc?tvy$^Adg-UNigNOna`V2#y@4$Oc~8K{SrMD0p|Y!L#MO+teiar- zN|78gvYRXxxoCp&!APNxkJI$rz~UDWn9(P>XqGPaE{OC7juLF)bQ!*sCvcq! z%mbyov5Yu5Rei{AZ!bF&#RIz@vfCviO>ri(Lck;O68D*UjmgHb{%+~DvbBAbov7qyDh>TjJ@qOU(juyn zb4S4q3v44!^AmtFZ;dTtDZXx;CT`JpJ${@ysejFCW2pxkGM$H+RAD6hfGWcm&gp<; gfRK!Qho+(hBewhF3FvXEdBq*S0Q*K6+;cqxT-Y%;*Lo(Yui#dhflD(S;C(L_`fiM2}v=AWEXQ=$+`F z=lTA;@BjC#z1CT0ue0}k?d#rapMBpaL0?ycn2??j0|SE?3Q>h&U|^vzFff^)U_WYJ zv0YLu$FHD|vW$D4p}3*lU?T>c6->8Teb7KkXPds@b@VxVyh~?*U%j zU9_(k`t}QY_41zG90UxA-P~Py{NRomRo}T-3LcaW8 zs9K0BoA;YN{L#FcQTZpVaK>fn0F!=^A2ms`?clvX3~nlI1Ok z8pnT5JGaJvLAx;(K6j8{fPuk^hN>zV`D5iT!fJ*D;JT*_X~XQzi}`qr)Wo5%<`44HKCF9k!S*5j_foO#^C z>%_-%6gq?L8!4`EUW-)LF%R&vHqna);Hf@`OXoOsj__2iGt{oUO8dp|BFl6Ulc-W8 z$FFnj(}04tI?B0KK0OkG$&Q@d=-U-o&aP)s z%Us!MOj8{so-R=8Trl`PEUDWZ-m) ziZE48)c= zDCW!rO23#`>R4x*|2i`axv2}QYA$%mvr{_h7SAcqPY_B`l z3L4ivutWE2E{uAmT;go6icr!BZ;3vx+X*Z73W;yDkm7?@hC$?gMuUxYZor`+;;hn| zm8B}3Uk@FI%;!&5?YLV(x-5`zcDh{FXG|^nGNg+Iv0{}0P6M-`%)mk)q14_YzKRz! z!fY;G!w__JY8{_Aq_8rnPW#z5QkO5;>AYX_QzLOOcB9a+G8(r&e(CSD-x2O}y?3aF z()j+<(3h^44`R@)>KOOc&qLjy{fYth&<`^hRwCKF+aui?MhfG?KfLcWpn+8@_@*2a z$ma;P?*yRW6gFdYeG=cbjWf2QyJRm{!DN4z{QQUJym};Z79bl9HbnD?eB8g0 zlwe%uaM{*NKb!ALn|P}Xa%EK_LjO=9R4YMo+5bI@PNnN zPp_Br1mwTmZzB(c9I_K@6DcW7B~YrA1l~IDP}tuMSFiG~70l7NB2u&iPKtbnO6%H6#;1AAF8t(Wyc2$_n_ty@9^&m8G+I#cW<~7XcluK(~)ux~q zB4a2@aFzpkgL#ZZV<+u+v^|jDT0^yX5=+@sVa@jBf8UZ&n3q9a%osT$Je6V>NVBIV z4S0liL;+gThMf(6OGDV5w;Ymzdm2N*!dS=p{=^9<2ACIkRY2qD!7r$ z2whVbVIWqNUx=Sjoi5hE!Ud-kUE@(JAOPQW=v#7c1(vQ3|Scu&ZGg&v#$<(Zdgd zFV59>f3=;j0o~1I3@@wlB1Dn!RDM-Nbi_D5h_+V)TsC&1_zrA2p43{ ziDXtUWr-szU-A_u&=A%5k1H*32-Jp^d)%E($pC@cAi49Pr`7s!T$~G&xyc2*ddu<% zN;n7;nfe3g`W5gmw=EF#j>G~JV|I;b>X@u#EXr12_NLp#08X9MJK^S0?Q3M)2G)V; z^ncDn306?BcD;lkIAtt{+cr_ff23)YHlnKsS%42vls*TcMBBa z4R{VDpr}R)>>jDg2{s^CYA;JjZ}`j+Zi-_m$)TvOEj45>f02KL#6SN0tQ&(m`Jq3sPB%_J)J6K z2+yjIde!;^YO5@3y*wqHj_2drCFu#M(I>M@rVh1ABRv=u?86B`i-xW}Wy@w;+>V0D znANCHG7B*#xXxmzP<41nph5g4fah4ib3~_(>;Ov?D->Vu=X7MAJ1+*t(9CR?R6*3$ zAN}>y(Rb8n5K}2Y1`8NNMDf~?LR&8!rAs1|oZ1_pF{;{b=^ zjcTNz0aBN2yAZ;;SaD?eUA&F-UKV>dIHRz{9%{m!Q1Jxn4tb+3I#3hA4ZV>w>(p8a z(5Q<7YEic5?VBA%iij2uWO;`p^&g2s4tqFd*dmqy0`REIP7Pv=VugO$u7f>m;Pt`1 zeh=@{&53MqMFM&@5z!EdbAA?it4PjS(SJ(Im)CS+&Pm8a7(Vwa9kkt>LLKUr&bv)q zHbKn>hG&cVQJ)L}``!nl#vM&G5xcJx#mR!pok9rTi$ScRMoJ_iAhFQGedut0^AQH=?H zx#!5qU+>H-8EAVG>9uciGo&k0PmUf-Ra_?4-Ln=PO7Qm1LUy0Bpz>B!B6)Mj;$r_- zBWMtJdI=fS$%TjD7Kb346^3SS3;UF0vq>3mFVXj8Dx=$E{1YVWj0gmyVFz1m6{0BC zgI0kK0@%|?;GgG|wk^8Ck#JJmI%0{KEN(b9NQ9bW8Y@9 z{Cfxbg#yz{Pa%zEnt|#GPop84KRKs$2$k-QYf%>Uqb~VkH&gQ@vC_Zo{YDv4nlIH7 zOe2AJ%8?ga{Uka{wW#vMjyFSOacNK0O)%+eVr=k4KOG(eP0*M_Kd3ptQ}Vh}evmhm zVDKwt8e4s%?~^;2ENUz&f6Z3Cs}jti{F#H=2-IjAc1RA1nVQzM+UwmzH6O%e=Feb! z;~<{j*Sy9sDhD(T2e=Kl;}njtA{PWV*YKA1_z5<*exqG2X(Ou>Fo6-5oq!-PnTFZW zuV*K&T}Y8Ok!t0lSy&`+)>Zu0H}LrM`knbBsafgVQ1QD`#jr>mA~HVkP0%>%!{{rB z`ckjct-KJo!0+$uCNR=67&!>spg?#=bT&a9337;oS#ik1wIT6s-A>7_BP+jD29XyJ zMo`Hy{D^z?GQV$$*_ljZ^Y&fPh##4^G-0XjL*;W}O(>_l0EI84?(f$`w~nl= z3By`Y79L3D^L@pzIGo)PU?JW6n3sc{gI-qcMDFC)@sTG5sZYzaWUeZ_VEJr!(LcuD zldg?w!kp!-W?@F;PCp$Q;ek1r$yxnPa(LUwu_Kx>x1hq2`<~sL#D!^p7n>p4sGax1 z*Waq09_kLl#4RMu$=Si$OLFf6l5onNf{gL@nD&-RzhB9bjIHpO)4Fe`w-G+CM!n4q zxZJE<6cM7D+WsV!1XpcM$ZUwmX}XNO#kA)J`{T)$i09Ts(m_8d_S``QJNS)X?tN1F zgd)7hE2-70#8CJlv)DjGdaRE^Y2sUnhPmU34H>~^h4=A@B;KHxc3g>DeLtyvy~?&w zW#pDd0Fq~f%^p6l{riGj(Il39E(hx;+V-s(zmG zhgNhlucFoniv7vj#>70BXxAR;#1)-@x*Ao&_4(xym-U7AlFV0XW zC|9mM`x`tuBu^EhRA1KwmTNk&dU?VDFS=RxtBEx?1sy1Bnlzw8Y5^la-4iYWpVkBS#iYCkTNmpK7Y&N6|t z=y{A@bw-G&BO~&xWu0C$puDe~-;Hdy1E5&}{&hKyI8+(YM(djNfu$;tept>=104sN zOLowmCdwjW(VJZb4XXbP%WfdR7^3T;eRYMbAD#6POEp)J zQgPj_;brVx_LW-_m%f1JWVZD|H;g+cGn%L-vVtOzJo4?1y)5gJy->|GqF+zlcy5gU zsmAe+GDpc{n1>|Waa+MQae2YX+Qar{vU~%|Fy}Y;*G7m#-fmzS7s`bI<15L%Z8l6{ zJJ=0Nak9%)##bJ=&v~0mll;CE^xNHhKou4kymx|kMLb{%bNBfCoW=6{`ky8{(s*i< z{6ef^#-oI1A`Pc(@Wbs)LPEoSXHA03Uw|*^fae7?3xEEyNg6_Ds^a;1O_<2zes1Rz z9oFZ?6}@<_Utq@DG+MqT%|9+8`w+U&@8-MsU z4eO5*HYa!di%6k*1pNfEollGeSAOMF+yAFdh7D1z%f8hH@mC1rH~o~63e?qO@=r57 zn}6fFT<9tQJV%Z-^U!t)Z%d3hIwGsni+>t!Dkx_ov-$0X9Y=y+eIwGbd+i^!$XxWs zZDqQyav(c1FT!jiO=gjc03W6?tPOLXh>&Mm56aUF3`{SW%@}WZg*^z~`4=sp{E{in zlMOBj=heB(h+O$Ap#B_5GN@Q&X=%)62(<|QQJY|Rjx6Cn&Rf1`%zy50%xMBM40$z`Ld&~0riwFGZ{peAO z2xJ+GL&RbSom9yOYV$N|t@d|1JMKy|onDnI*iuHaQJx#z)vmEdi`xi(~J$_yR%Pt^+-Xm;nJ_ROI>o$*UQS4!n@miplQC_c+Ea!WLnzCjhwjE7aYqQn|8Mb z>rWl-?$b83hAcZ4gd>tJ%w7ex#w=PIGYL-_BbDc1VjD1$C+j4%K8sZ&PXl z9!r4gt7j_{V4LAHKz;U<_YpQVq7lUqM?yn5P+}7P!;+R(*#coT$3uz~G5Go$o8-0l zRnpvLbaQSCs`kgF@Q^L2%xMDuo#Et`QEcL9z2-{u|Z2JB*XApaCDl7ZU(x5^6T}KqB3HoShoSSo>=RCp@ZL^lS2Z|wTPnyqQ zt?2h}SQ9{9am07YRnn>@?Ml;WA~L1aSBf!u!7Jb3TeZXmBEYuQRsaU-?hk`oV~LWO zL$dW^+>?fw)CtDs`F`Ikgm=o*#?TV_F}Atei3IA37M>5>|-8%@7b_V_K>t_Dp+@t9?P=$XP$A905J?6;N(sIM??i`Rtc z*=#^GxB5d>N*5I@0LF>p!S)#Sz^Y~0r;4#ChId@wZjR2Kc^z|K@Ej49p}QcPmg#n* zm0CHOi#8u>|O#%`vJGDiXAt3Oxq_Ek7@u>$U{IRFZpyY-D_fN&j;qO!C%xJ1M$U?uscWFn_ zIN{HJr>Li>YP38W?p>sadu%M>DWB!jU~{~YOF(85YirY>pqd|o3dip$Ha>N_U*qDJ z=3UTXx*uU>R0R#^WSTx)uk{^4DI|Zfv|35~#)ouy0%NE+d(o9PlE`Wdr5(R(ayunZRoDzp|EW7R@ahfcy+seAqZD3zqVbopKq z^TPStuZ>|KLkrT#!+^C<`zva^8~9Najud#uu2;Y54VJ7VX)k^sJ);_8Ei@wv@>F-p zZ9xKJxe<7foQ7yH*1V2@~pB>IIB0#*Yl7seC#Y9az zd#2@y)u22^>+X6lN>(ZRZaq{&C|~@sQRE0d(^=e8G>Lj;vH0n zeC&})NW6on3|>JZ^RsY9Ef>689$gksU`3o<0~0%WX0JIk{!}Qn=kze7Qg%&nmNq6LbwYcNdi9Scp@jz}>DP}DznqP_Orl>asFa*BjhB07{0{_gTi2shAnQIc7XpEOY+flWphaLmz zD4aF>!yrZvLbDdNB9j~~==%iln^-wb7qfrJ-+IWieCKU3Lyj#`oy!~R47yux==&6w zFD55kV2j}^mLR{vKi?=+ZGu_)%{lV&syrASckbLb+mpH~1lqLk$Lns)6px&keSR`g z!ai?+iY`l&WrO#E(dOpm7<4fIgwFo zij{XZTh!@@e_xBjUZPAJIZ{_?XtxWaPnT5^wa#CU#Npp?>@mUQ8aa}wX~6UpuZQCS zGt$gGZfAbC73r%LR5lIG5H1QALM+sEW7)pfdBQHZGfnJ10FI2ns9=B=6XL?|L>oNt zi(0kU{YrO2*?X@b_ZoLjLab_rb8CumQ{wB5K3iWV+T~9oS-{y|gBC&9c&HVvBhlw5 zsfS9T@BPi$+@u%Ykn_7nX?jg?iz7L3*yG`1t4N6{qI z_eYR`-8NinM7z-l4?&S1Oa`_{693`G#bni33?DyqN zDn;3znOsO6v8g!;uAdgu|0uo*1l@3E>by#ZDpUVJa>V;tDqL|G5AS|8M;-MYHnP4<&m>SUyb7 z48fG>0DP1W9>vZ?M1!kYOKwzMLNFx^ensKX&bzV+8rumW@H1N7d)ZFB9kz##vFTAH z!>A<^J$>HvAhyu?plgQDH_G}q69pOikO2Uy!5+J&347#`XQ{IY#J|N=Od9z8k9hu5 zO3Qub(O(3D5{Ztd(KGpcrE^#Bj$o(UbHMNDWk$8rV5Vk^(r)e*W4FnVo-&-@f4rv8 zDv#P@W@Op=C~0orWbK)&E;}=9qwp7xuP<#yI+XPBYV3cGfAFoxKNr0|tb3g+3wWvJ zH&%{|+8PzqD?DWnTd`q=d~J$rMw|G!v5pH!4o~>2P1D7RHkTJ9L~1b~MqvZCtd_VT zmHCt$F+H;@Z3W-@pAJ#U{9=P2(aMHQbDCuJY?xAn;C5?y)6~k3o8r)CYL569E$P?E zZ_qcI9B>KJBnCny)-8WYp@14Psdomt7AXQI*`-3j(PF!!=h-ICg<(1!h^IK-N{4oQ z*{_Qfy?aYTbZefgeby0P5NCvaUBiO3e6_LTuoGi~d7a99uZAm-P|=fA%=hc_{YqHh zS3{`F7#V5%F~?=T?;wFCa`cHn;Swk&zEhfd+s}-U>?A4FOBU?9b>%q1D?$i|4-ZfI z2Xe?@<104YORx>}h8i;3m;dhT9I&+B7XWJ`<)>C7m?L7fs4%Ovy*{zAkvcn=Pg8jr z_5^)qyyq#5Ft7d*hNHEH)i-c3R(KdPdpYogF2?8(JFN^M;BXL=5y?Ken0Z*y9k1?DXmxG-Pq3R7wY~~Mh>(Z=m<96XKCPIWJCUtQJBi~3>?*5UjNf$H z4IT)9mS*1=eUWB?UD*HhbN!Qln6$!T4QY8a%$IcH3g=os35IN(mDqn|{0R$*{jb$r zs@JoCSDV3*7tp^20`BfVK9?dqdwE$$*AUdJ^`~rE)lZ^5!|eayR*}k4bB#S|){V=| z`ZvaGx)eVbr*P1+iiUMLci_?gcgL?0NO9dUJ#wMbkAjyx~ z;SGj6nn?y5iMuf=Y0grpS=w%zt)Lkh=J{rB6scS`@VM`~z=}_Y$QF|`hh>_MRerA- z3w;6)m_b@II*DPT?V4IH^k{mo*AP4*%XI^kUB~h@u~QqukkOu0B39ynPE!(}))+Yk znDDcPkvOA$m9(-_`^F}hjwh(BH(R;#^97WG?JYYlM)dl0q-Ac7n~N^9|Bu5?#6e^v=ZSKW-~!3Dmsi65Io+V-#yKywc~ zLV}ZQ#O^FfBazWIkSzHl)9<--1T#wWsh0DiS^@4~ecZ=e+%E`r8|F#cC{b z88vk5^0T^`D3Ik1-*&jV3a~+H&*9QXPondov6xKbvimy^t$zGq zi=M~a!?0ViItl2Z{>uZ=LAj|dc<&7cZ!bMQ!Ig5nWP97U2c{CfM| z%v&B+UVsm7dd}K1myktwB0~=2{J7~9HsCi)E00_a39nm!^9*I4blY*$8_e;z7q_C> zy=1_lYQOoRF90EN41gmCFXjJSJZ%ZT#O$|aN{?QnhvA;96fS=dl9+az7z89NPzqK@zuG?zt-Zwuon%Kd#sTRp)^MR z3oxwpuZzFUe$_M<@(?#f=ST_qtyMh;e9V9SAC5Zh(w+5ne&?_J4Z9oKbiZ?{yRGUt bZ@S0Nikv9varV#uPnCzN>8e&MTSxvs@EVFR literal 0 HcmV?d00001 diff --git a/doc/logos/passivedns.png b/doc/logos/passivedns.png new file mode 100644 index 0000000000000000000000000000000000000000..4959a84779d952e542a23a998cb2c1d5d01dfd80 GIT binary patch literal 19024 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

J<$%*G|;$9^=6eE5g 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 BJ^26t literal 0 HcmV?d00001 diff --git a/doc/logos/passivetotal.png b/doc/logos/passivetotal.png new file mode 100644 index 0000000000000000000000000000000000000000..87cef69582a217576825927a774dc486151f4bd4 GIT binary patch literal 36361 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�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 Q>i6{j%l~N%pyaWC15)zZ#{d8T literal 0 HcmV?d00001 diff --git a/doc/logos/stix.png b/doc/logos/stix.png new file mode 100644 index 0000000000000000000000000000000000000000..e8b824149da6a3dbae0a814e0f92838ed32e7f1f GIT binary patch literal 3730 zcmV;D4sG#?P)gu{U zQu+D$^YioE+}y_iGP$|APEJn0zrXJ8?t_DaqobqbCdtXk7#J8v01*-r5+NZWRaI4CVPTE{BQ`cRRsa>R04!tx84&;kPyiH_ z0406^A3OjLEC35^02?14A0z+?8UO~Q04X#84Js-s;D331TAkHjY-|~-R+?2 zliK8VDdPTCotVC{9aiJ<+dA&MTG_NWse0`$j{Bjp+B&I~%2~`vH-3+`?f`eavG`-J zRL**Gzi0%+8@CMivjBJF>0VMlS}L>lz6O0`BOIqJAM0!QOXaS>fYcXu@Xvjt#Jy?V z`7OK=3{vcc*SzabP2bUOYZ*b_tNG|l+r3!@ePr#6_v+qj>h-Ut?`PMweDquOXYbXW z*U~4qrtfFx^}sMctw%$ZQX5lXM*K-;$$KmE4S3LDmb|y3lJ|C%k+ZW6Mleyix*gJo;luU91NO+ zajWeeeR-6D_deXNMGX#WTqi}MS{W=E0aXpXsY=3OE58J7D&(BklaE}YZJKkjZPwYE^+4B$KjtdBb!%qE4 z{H~ZasW=gD=fZNGRJ_NjS)2%0DgbZG4L80NEef{@K<8r(!u>ykXSS?}cdt30c2e-} z(Yr}m?e%LFfj74(a4SuoM(|&TWKCX%JFXZqpZD?Mc_#($c{UpE+PxQnH*AHU{}%4J zOl)`TH0gWdmfvBAUi-YAilxsR?e{6Vi4VpL!P|1f%^yU&@EtoL$)Yf8@*6m^net=3 zj;m*Tf9Nl66ru-d3CK6q;;gQFsT)y~yy7 zVVLj^n!GbkEcK~u?#fBLZRRk4rySr#)&w&4GYW16?^z{Jjf=(`F%oP7b7ij|D48BM zm;oFvdb|lA)tz-cwFl}WE0x0WCRmiIe^{US>pp?j1ac_q@K#POFNY&GmiOS)AB`}RdE=9vR>I|`=ox1{^>>m=c6#1@c7PPjd@Ef; zvL-BK35MUac-u&RK_&96ujv{+#UEXb&?8c5YNh8LP3gp7CbuX|k~QJMAbNfi#{0N} zD$zNEPe_B&Y;W96l-e`aPCk*LOvjrLl3eLMxIZBaAq{V*f+{hC3eDy@#rKBxfHz7SDkQ91jAxl?rUvXi)6~n>g`KtqmmR;4)&PCr(*(ass4F`b_;unBJ%c^z| zR|ei!x#u6Z(f3ovPSWy@EQ(;j;}%7k06|j*-jzY#X|Sev6Owr31PB($A0QW&mBD^{ z_Lw+{rOFiVXc!BoN5Hl{ahL`w!}!G4KnX!d#&}ySm?@1x!WoTZO%f6yNJ8g3lu+@_ z5uE9*D|5V6=@HV{oP{i3X?{eM_f^u3%NiW%^w5aR@kY`k{5d2%_tdi{3f^@0_i6j& zIQwwLAaCxwcrP^MgEyWv0evg#1PDPWZ@uW{ReRsb#@r-tJ{U`gC`i7;cj2+hbSgdD|>D{z+`jqO=kq1mV1K z_bQ?RvOW!#QQmNRglN&3-;j__B?;%9#dkVrX^&dW@{XcrqD5K9t(O2HNRfY%EkLi^ z$_?}8zRlMpYa->(Ql#LmoYE_F+STbk4fBT6BjU|lE1gP`Dqj;vKv9!U+%)ef8CXn@ z$jO?hd1GsKsp?Yu72~{lFjE`?%GQLM03k@3|DD5XlFAt&QP-Do-cg7Nuqe9eRFc%| zNm7pqCg#yoFut#^nUT}?$1VZBr?D;!AKj0ND$*XW2Ysz5xQs!6+=EXsPoi{wwB zCrXM&0eBlP(t7D%f4nw@T1^(DMl4l=Jf&)X`3C7-f<-R(uA`*F>#jqVj%TMqd>SDkyI-doXCsU_P?8MM2jW4_lDn z413%CQ}V`(hh-=@CE6tougtsEX09{d0(hZ|# zjyJfvfrTu$uwJSw-*jAH1IHaJSd7Q~dO)xOgk(|pdO$skg78+vbD(&;jr$I`Jfz>1EH91h;`Il0|{S78~Gk*_kO)jRa-NEA*as?}tcfXRi z@UGkdZ#V&hRe*3~Jt{mt&!ygDhNns;OOepFecrsDDP94BUm2`tN=?AWdFTD?L`4q4 z_Iaxx6}TFExZ!`&LjiRwY%2WU`4%57T)rk>9wqw!4r12C7h%SqM?GrwoJUKlWop~z z%@;eW{o9~Nizi&sEQ*SE)&<6KDeq|8yun9cB-E4P(PYu0=v4q$#?x{f6DnQvTjtF} zmSB7`_5>a{BEcWmlf&~eETtQqF7=|i#9n-jGbAb@RZs{=KoTf3O{}ca`}! z{wbW=_*b|CU)H0ak31h!0+lI?R3Gj`q6~3t@N{7)eH?CnCg^XJ_ZqGa`m1>Ys9%Cs zD)03qrErzJx2ux(cEx!=_`JWFzMY-d{lw$_;Jvu@U)-6#ot>B8tS7hLi$8qc3)8o= z>smf~bN|L?@70%DwKU)xmv6qSwJ+W)`)l2A;7ikYw9{HX*2C`7vcCD~C#417pzMc9 zm5)l6i@N{MT{%hIl$HT*ZbJgwQk^VH8@?Gmd-QQvYn<<00KDFKynD7+DqlVOz1r~Y zN40SqeFF~x@M?Y1sFliBE7-ohjyL(cTD@9obE?N%cVJ_xjgt7Lk07*qoM6N<$g1U!EyZ`_I literal 0 HcmV?d00001 diff --git a/doc/logos/threatconnect.png b/doc/logos/threatconnect.png new file mode 100644 index 0000000000000000000000000000000000000000..4c8a5b1561c839769446e5a9d65ea12aeb5f1a7b GIT binary patch literal 14988 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 ZX$3flx%ZIUZuqB*hKjCoy`oj*{|BS8c8dT2 literal 0 HcmV?d00001 diff --git a/doc/logos/threatcrowd.png b/doc/logos/threatcrowd.png new file mode 100644 index 0000000000000000000000000000000000000000..94eacfc5bda962d9bda7385f37d900bc598e3128 GIT binary patch literal 3117 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 literal 0 HcmV?d00001 diff --git a/doc/logos/threatstream.png b/doc/logos/threatstream.png new file mode 100644 index 0000000000000000000000000000000000000000..eb3837ef92261912fc2ca824550561adf9ec2dd8 GIT binary patch literal 3488 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 literal 0 HcmV?d00001 diff --git a/doc/logos/virustotal.png b/doc/logos/virustotal.png new file mode 100644 index 0000000000000000000000000000000000000000..935c5ccfa622e4edece541d0b968632d7abb7673 GIT binary patch literal 2776 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 b9|ZpgU7#~zcxju?00000NkvXXu0mjf%P)ys literal 0 HcmV?d00001 diff --git a/doc/logos/wikidata.png b/doc/logos/wikidata.png new file mode 100644 index 0000000000000000000000000000000000000000..0ffb4b155f81948503c5f3496f08c02e753e4774 GIT binary patch literal 4385 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 literal 0 HcmV?d00001 diff --git a/doc/logos/xforce.png b/doc/logos/xforce.png new file mode 100644 index 0000000000000000000000000000000000000000..96db659beef4fe6cf1322e15e7f60093f6684d03 GIT binary patch literal 8534 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 literal 0 HcmV?d00001 From 6acf9573e4b2ecfc947e6752a83037a6ee5a32fb Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 13 Sep 2018 18:18:05 +0200 Subject: [PATCH 141/153] add: Added documentation for expansion modules --- doc/expansion/asn_history.json | 4 ++++ doc/expansion/circl_passivedns.json | 4 ++++ doc/expansion/circl_passivessl.json | 4 ++++ doc/expansion/countrycode.json | 3 +++ doc/expansion/crowdstrike_falcon.json | 4 ++++ doc/expansion/cve.json | 3 +++ doc/expansion/dbl_spamhaus.json | 4 ++++ doc/expansion/dns.json | 3 +++ doc/expansion/domaintools.json | 4 ++++ doc/expansion/eupi.json | 4 ++++ doc/expansion/farsight_passivedns.json | 4 ++++ doc/expansion/geoip_country.json | 3 +++ doc/expansion/intelmq_eventdb.json | 3 +++ doc/expansion/ipasn.json | 3 +++ doc/expansion/iprep.json | 3 +++ doc/expansion/onyphe.json | 4 ++++ doc/expansion/onyphe_full.json | 4 ++++ doc/expansion/otx.json | 4 ++++ doc/expansion/passivetotal.json | 4 ++++ doc/expansion/rbl.json | 4 ++++ doc/expansion/reversedns.json | 3 +++ doc/expansion/shodan.json | 4 ++++ doc/expansion/sourcecache.json | 3 +++ doc/expansion/threatcrowd.json | 4 ++++ doc/expansion/threatminer.json | 4 ++++ doc/expansion/virustotal.json | 4 ++++ doc/expansion/vmray_submit.json | 4 ++++ doc/expansion/vulndb.json | 4 ++++ doc/expansion/whois.json | 4 ++++ doc/expansion/wiki.json | 4 ++++ doc/expansion/xforceexchange.json | 4 ++++ doc/expansion/yara_syntax_validator.json | 4 ++++ 32 files changed, 119 insertions(+) create mode 100644 doc/expansion/asn_history.json create mode 100644 doc/expansion/circl_passivedns.json create mode 100644 doc/expansion/circl_passivessl.json create mode 100644 doc/expansion/countrycode.json create mode 100644 doc/expansion/crowdstrike_falcon.json create mode 100644 doc/expansion/cve.json create mode 100644 doc/expansion/dbl_spamhaus.json create mode 100644 doc/expansion/dns.json create mode 100644 doc/expansion/domaintools.json create mode 100644 doc/expansion/eupi.json create mode 100644 doc/expansion/farsight_passivedns.json create mode 100644 doc/expansion/geoip_country.json create mode 100644 doc/expansion/intelmq_eventdb.json create mode 100644 doc/expansion/ipasn.json create mode 100644 doc/expansion/iprep.json create mode 100644 doc/expansion/onyphe.json create mode 100644 doc/expansion/onyphe_full.json create mode 100644 doc/expansion/otx.json create mode 100644 doc/expansion/passivetotal.json create mode 100644 doc/expansion/rbl.json create mode 100644 doc/expansion/reversedns.json create mode 100644 doc/expansion/shodan.json create mode 100644 doc/expansion/sourcecache.json create mode 100644 doc/expansion/threatcrowd.json create mode 100644 doc/expansion/threatminer.json create mode 100644 doc/expansion/virustotal.json create mode 100644 doc/expansion/vmray_submit.json create mode 100644 doc/expansion/vulndb.json create mode 100644 doc/expansion/whois.json create mode 100644 doc/expansion/wiki.json create mode 100644 doc/expansion/xforceexchange.json create mode 100644 doc/expansion/yara_syntax_validator.json diff --git a/doc/expansion/asn_history.json b/doc/expansion/asn_history.json new file mode 100644 index 0000000..936feba --- /dev/null +++ b/doc/expansion/asn_history.json @@ -0,0 +1,4 @@ +{ + "description": "Query an ASN description history service (https://github.com/CIRCL/ASN-Description-History.git).", + "requirements": ["asnhistory"] +} diff --git a/doc/expansion/circl_passivedns.json b/doc/expansion/circl_passivedns.json new file mode 100644 index 0000000..664ca77 --- /dev/null +++ b/doc/expansion/circl_passivedns.json @@ -0,0 +1,4 @@ +{ + "description": "Module to access CIRCL Passive DNS.", + "logo": "logos/passivedns.png" +} diff --git a/doc/expansion/circl_passivessl.json b/doc/expansion/circl_passivessl.json new file mode 100644 index 0000000..2015b59 --- /dev/null +++ b/doc/expansion/circl_passivessl.json @@ -0,0 +1,4 @@ +{ + "description": "Modules to access CIRCL Passive SSL.", + "logo": "logos/passivessl.png" +} diff --git a/doc/expansion/countrycode.json b/doc/expansion/countrycode.json new file mode 100644 index 0000000..367c14b --- /dev/null +++ b/doc/expansion/countrycode.json @@ -0,0 +1,3 @@ +{ + "description": "Module to expand country codes." +} diff --git a/doc/expansion/crowdstrike_falcon.json b/doc/expansion/crowdstrike_falcon.json new file mode 100644 index 0000000..0faa6c0 --- /dev/null +++ b/doc/expansion/crowdstrike_falcon.json @@ -0,0 +1,4 @@ +{ + "description": "Module to query Crowdstrike Falcon.", + "logo": "logos/crowdstrike.png" +} diff --git a/doc/expansion/cve.json b/doc/expansion/cve.json new file mode 100644 index 0000000..afc4c33 --- /dev/null +++ b/doc/expansion/cve.json @@ -0,0 +1,3 @@ +{ + "description": "An expansion hover module to expand information about CVE id." +} diff --git a/doc/expansion/dbl_spamhaus.json b/doc/expansion/dbl_spamhaus.json new file mode 100644 index 0000000..b691007 --- /dev/null +++ b/doc/expansion/dbl_spamhaus.json @@ -0,0 +1,4 @@ +{ + "description": "Module to check Spamhaus DBL for a domain name.", + "logo": "logos/spamhaus.jpg" +} diff --git a/doc/expansion/dns.json b/doc/expansion/dns.json new file mode 100644 index 0000000..2ca7e42 --- /dev/null +++ b/doc/expansion/dns.json @@ -0,0 +1,3 @@ +{ + "description": "A simple DNS expansion service to resolve IP address from MISP attributes." +} diff --git a/doc/expansion/domaintools.json b/doc/expansion/domaintools.json new file mode 100644 index 0000000..5ed0cb2 --- /dev/null +++ b/doc/expansion/domaintools.json @@ -0,0 +1,4 @@ +{ + "description": "DomainTools MISP expansion module.", + "logo": "logos/domaintools.png" +} diff --git a/doc/expansion/eupi.json b/doc/expansion/eupi.json new file mode 100644 index 0000000..42da8aa --- /dev/null +++ b/doc/expansion/eupi.json @@ -0,0 +1,4 @@ +{ + "description": "A module to query the Phishing Initiative service (https://phishing-initiative.lu).", + "logo": "logos/eupi.png" +} diff --git a/doc/expansion/farsight_passivedns.json b/doc/expansion/farsight_passivedns.json new file mode 100644 index 0000000..6fd038b --- /dev/null +++ b/doc/expansion/farsight_passivedns.json @@ -0,0 +1,4 @@ +{ + "description": "Module to access Farsight DNSDB Passive DNS.", + "logo": "logos/farsight.png" +} diff --git a/doc/expansion/geoip_country.json b/doc/expansion/geoip_country.json new file mode 100644 index 0000000..fb3bf33 --- /dev/null +++ b/doc/expansion/geoip_country.json @@ -0,0 +1,3 @@ +{ + "description": "Module to query a local copy of Maxminds Geolite database." +} diff --git a/doc/expansion/intelmq_eventdb.json b/doc/expansion/intelmq_eventdb.json new file mode 100644 index 0000000..7746551 --- /dev/null +++ b/doc/expansion/intelmq_eventdb.json @@ -0,0 +1,3 @@ +{ + "description": "Module to access intelmqs eventdb." +} diff --git a/doc/expansion/ipasn.json b/doc/expansion/ipasn.json new file mode 100644 index 0000000..1ab9cdd --- /dev/null +++ b/doc/expansion/ipasn.json @@ -0,0 +1,3 @@ +{ + "description": "Module to query an IP ASN history service (https://github.com/CIRCL/IP-ASN-history.git)." +} diff --git a/doc/expansion/iprep.json b/doc/expansion/iprep.json new file mode 100644 index 0000000..343ce4d --- /dev/null +++ b/doc/expansion/iprep.json @@ -0,0 +1,3 @@ +{ + "description": "Module to query IPRep data for IP addresses." +} diff --git a/doc/expansion/onyphe.json b/doc/expansion/onyphe.json new file mode 100644 index 0000000..4c00866 --- /dev/null +++ b/doc/expansion/onyphe.json @@ -0,0 +1,4 @@ +{ + "description": "Module to process a query on Onyphe.", + "logo": "logos/onyphe.jpg" +} diff --git a/doc/expansion/onyphe_full.json b/doc/expansion/onyphe_full.json new file mode 100644 index 0000000..15f07f1 --- /dev/null +++ b/doc/expansion/onyphe_full.json @@ -0,0 +1,4 @@ +{ + "description": "Module to process a full query on Onyphe.", + "logo": "logos/onyphe.jpg" +} diff --git a/doc/expansion/otx.json b/doc/expansion/otx.json new file mode 100644 index 0000000..16ee6d6 --- /dev/null +++ b/doc/expansion/otx.json @@ -0,0 +1,4 @@ +{ + "description": "Module to get information from AlienVault OTX.", + "logo": "logos/otx.png" +} diff --git a/doc/expansion/passivetotal.json b/doc/expansion/passivetotal.json new file mode 100644 index 0000000..5b09f56 --- /dev/null +++ b/doc/expansion/passivetotal.json @@ -0,0 +1,4 @@ +{ + "description": "The PassiveTotal MISP expansion module brings the datasets derived from Internet scanning directly into your MISP instance. This module supports passive DNS, historic SSL, WHOIS, and host attributes. In order to use the module, you must have a valid PassiveTotal account username and API key. Registration is free and can be done by visiting https://www.passivetotal.org/register", + "logo": "logos/passivetotal.png" +} diff --git a/doc/expansion/rbl.json b/doc/expansion/rbl.json new file mode 100644 index 0000000..0f67c2c --- /dev/null +++ b/doc/expansion/rbl.json @@ -0,0 +1,4 @@ +{ + "description": "Module to check an IPv4 address against known RBLs.", + "requirements": ["dnspython3"] +} diff --git a/doc/expansion/reversedns.json b/doc/expansion/reversedns.json new file mode 100644 index 0000000..96773ac --- /dev/null +++ b/doc/expansion/reversedns.json @@ -0,0 +1,3 @@ +{ + "description": "Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes." +} diff --git a/doc/expansion/shodan.json b/doc/expansion/shodan.json new file mode 100644 index 0000000..734d768 --- /dev/null +++ b/doc/expansion/shodan.json @@ -0,0 +1,4 @@ +{ + "description": "Module to query on Shodan.", + "logo": "logos/shodan.png" +} diff --git a/doc/expansion/sourcecache.json b/doc/expansion/sourcecache.json new file mode 100644 index 0000000..13c2a03 --- /dev/null +++ b/doc/expansion/sourcecache.json @@ -0,0 +1,3 @@ +{ + "description": "Module to cache web pages of analysis reports, OSINT sources. The module returns a link of the cached page." +} diff --git a/doc/expansion/threatcrowd.json b/doc/expansion/threatcrowd.json new file mode 100644 index 0000000..83af5fd --- /dev/null +++ b/doc/expansion/threatcrowd.json @@ -0,0 +1,4 @@ +{ + "description": "Module to get information from ThreatCrowd.", + "logo": "logos/threatcrowd.png" +} diff --git a/doc/expansion/threatminer.json b/doc/expansion/threatminer.json new file mode 100644 index 0000000..da75784 --- /dev/null +++ b/doc/expansion/threatminer.json @@ -0,0 +1,4 @@ +{ + "description": "Module to get information from ThreatMiner.", + "logo": "logos/threatminer.png" +} diff --git a/doc/expansion/virustotal.json b/doc/expansion/virustotal.json new file mode 100644 index 0000000..8c203eb --- /dev/null +++ b/doc/expansion/virustotal.json @@ -0,0 +1,4 @@ +{ + "description": "Module to get information from virustotal.", + "logo": "logos/virustotal.png" +} diff --git a/doc/expansion/vmray_submit.json b/doc/expansion/vmray_submit.json new file mode 100644 index 0000000..b977203 --- /dev/null +++ b/doc/expansion/vmray_submit.json @@ -0,0 +1,4 @@ +{ + "description": "Module to submit a sample to VMRay.", + "logo": "logos/vmray.png" +} diff --git a/doc/expansion/vulndb.json b/doc/expansion/vulndb.json new file mode 100644 index 0000000..a4fec3b --- /dev/null +++ b/doc/expansion/vulndb.json @@ -0,0 +1,4 @@ +{ + "description": "Module to query VulnDB (RiskBasedSecurity.com).", + "logo": "logos/vulndb.png" +} diff --git a/doc/expansion/whois.json b/doc/expansion/whois.json new file mode 100644 index 0000000..7c5c119 --- /dev/null +++ b/doc/expansion/whois.json @@ -0,0 +1,4 @@ +{ + "description": "Module to query a local instance of uwhois (https://github.com/rafiot/uwhoisd).", + "requirements": ["uwhois"] +} diff --git a/doc/expansion/wiki.json b/doc/expansion/wiki.json new file mode 100644 index 0000000..14c4451 --- /dev/null +++ b/doc/expansion/wiki.json @@ -0,0 +1,4 @@ +{ + "description": "An expansion hover module to extract information from Wikidata to have additional information about particular term for analysis.", + "logo": "logos/wikidata.png" +} diff --git a/doc/expansion/xforceexchange.json b/doc/expansion/xforceexchange.json new file mode 100644 index 0000000..13d3622 --- /dev/null +++ b/doc/expansion/xforceexchange.json @@ -0,0 +1,4 @@ +{ + "description": "An expansion module for IBM X-Force Exchange.", + "logo": "logos/xforce.png" +} diff --git a/doc/expansion/yara_syntax_validator.json b/doc/expansion/yara_syntax_validator.json new file mode 100644 index 0000000..891aa5a --- /dev/null +++ b/doc/expansion/yara_syntax_validator.json @@ -0,0 +1,4 @@ +{ + "description": "An expansion hover module to perform a syntax check on if yara rules are valid or not.", + "logo": "logos/yara.png" +} From b9fe46ef014e2e59bf4784472238aa8907f82c91 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 13 Sep 2018 18:18:59 +0200 Subject: [PATCH 142/153] Updated documentation result file --- doc/markdown.md | 596 +++++++++++++++++++++++++++--------------------- 1 file changed, 333 insertions(+), 263 deletions(-) diff --git a/doc/markdown.md b/doc/markdown.md index 4973db8..1212b5f 100644 --- a/doc/markdown.md +++ b/doc/markdown.md @@ -2,51 +2,7 @@ ## Expansion Modules -### geoip_country - -Module to query a local copy of Maxminds Geolite database. - ------ - -### iprep - -Module to query IPRep data for IP addresses. - ------ - -### crowdstrike_falcon - -Module to query Crowdstrike Falcon. - ------ - -### dns - -A simple DNS expansion service to resolve IP address from MISP attributes. - ------ - -### vulndb - -Module to query VulnDB (RiskBasedSecurity.com). - ------ - -### wiki - -An expansion hover module to extract information from Wikidata to have additional information about particular term for analysis. - ------ - -### rbl - -Module to check an IPv4 address against known RBLs. -- **requirements**: ->dnspython3 - ------ - -### asn_history +#### asn_history Query an ASN description history service (https://github.com/CIRCL/ASN-Description-History.git). - **requirements**: @@ -54,19 +10,205 @@ Query an ASN description history service (https://github.com/CIRCL/ASN-Descripti ----- -### circl_passivedns +#### circl_passivedns + + Module to access CIRCL Passive DNS. ----- -### farsight_passivedns +#### circl_passivessl + + + +Modules to access CIRCL Passive SSL. + +----- + +#### countrycode + +Module to expand country codes. + +----- + +#### crowdstrike_falcon + + + +Module to query Crowdstrike Falcon. + +----- + +#### cve + +An expansion hover module to expand information about CVE id. + +----- + +#### dbl_spamhaus + + + +Module to check Spamhaus DBL for a domain name. + +----- + +#### dns + +A simple DNS expansion service to resolve IP address from MISP attributes. + +----- + +#### domaintools + + + +DomainTools MISP expansion module. + +----- + +#### eupi + + + +A module to query the Phishing Initiative service (https://phishing-initiative.lu). + +----- + +#### farsight_passivedns + + Module to access Farsight DNSDB Passive DNS. ----- -### whois +#### geoip_country + +Module to query a local copy of Maxminds Geolite database. + +----- + +#### intelmq_eventdb + +Module to access intelmqs eventdb. + +----- + +#### ipasn + +Module to query an IP ASN history service (https://github.com/CIRCL/IP-ASN-history.git). + +----- + +#### iprep + +Module to query IPRep data for IP addresses. + +----- + +#### onyphe + + + + + +----- + +#### onyphe_full + + + + + +----- + +#### otx + + + +Module to get information from AlienVault OTX. + +----- + +#### passivetotal + + + +The PassiveTotal MISP expansion module brings the datasets derived from Internet scanning directly into your MISP instance. This module supports passive DNS, historic SSL, WHOIS, and host attributes. In order to use the module, you must have a valid PassiveTotal account username and API key. Registration is free and can be done by visiting https://www.passivetotal.org/register + +----- + +#### rbl + +Module to check an IPv4 address against known RBLs. +- **requirements**: +>dnspython3 + +----- + +#### reversedns + +Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes. + +----- + +#### shodan + + + +Module to query on Shodan. + +----- + +#### sourcecache + +Module to cache web pages of analysis reports, OSINT sources. The module returns a link of the cached page. + +----- + +#### threatcrowd + + + +Module to get information from ThreatCrowd. + +----- + +#### threatminer + + + +Module to get information from ThreatMiner. + +----- + +#### virustotal + + + +Module to get information from virustotal. + +----- + +#### vmray_submit + + + +Module to submit a sample to VMRay. + +----- + +#### vulndb + + + +Module to query VulnDB (RiskBasedSecurity.com). + +----- + +#### whois Module to query a local instance of uwhois (https://github.com/rafiot/uwhoisd). - **requirements**: @@ -74,109 +216,25 @@ Module to query a local instance of uwhois (https://github.com/rafiot/uwhoisd). ----- -### threatcrowd +#### wiki -Module to get information from ThreatCrowd. + + +An expansion hover module to extract information from Wikidata to have additional information about particular term for analysis. ----- -### domaintools +#### xforceexchange -DomainTools MISP expansion module. - ------ - -### eupi - -A module to query the Phishing Initiative service (https://phishing-initiative.lu). - ------ - -### shodan - -Module to query on Shodan. - ------ - -### xforceexchange + An expansion module for IBM X-Force Exchange. ----- -### circl_passivessl +#### yara_syntax_validator -Modules to access CIRCL Passive SSL. - ------ - -### virustotal - -Module to get information from virustotal. - ------ - -### otx - -Module to get information from AlienVault OTX. - ------ - -### cve - -An expansion hover module to expand information about CVE id. - ------ - -### reversedns - -Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes. - ------ - -### threatminer - -Module to get information from ThreatMiner. - ------ - -### sourcecache - -Module to cache web pages of analysis reports, OSINT sources. The module returns a link of the cached page. - ------ - -### ipasn - -Module to query an IP ASN history service (https://github.com/CIRCL/IP-ASN-history.git). - ------ - -### intelmq_eventdb - -Module to access intelmqs eventdb. - ------ - -### vmray_submit - -Module to submit a sample to VMRay. - ------ - -### countrycode - -Module to expand country codes. - ------ - -### passivetotal - -The PassiveTotal MISP expansion module brings the datasets derived from Internet scanning directly into your MISP instance. This module supports passive DNS, historic SSL, WHOIS, and host attributes. In order to use the module, you must have a valid PassiveTotal account username and API key. Registration is free and can be done by visiting https://www.passivetotal.org/register - ------ - -### yara_syntax_validator + An expansion hover module to perform a syntax check on if yara rules are valid or not. @@ -184,13 +242,24 @@ An expansion hover module to perform a syntax check on if yara rules are valid o ## Export Modules -### testexport +#### cef_export -Skeleton export module. +Module to export a MISP event in CEF format. +- **features**: +>The module takes a MISP event in input, to look every attribute. Each attribute matching with some predefined types is then exported in Common Event Format. +>Thus, there is no particular feature concerning MISP Events since any event can be exported. However, 4 configuration parameters recognized by CEF format are required and should be provided by users before exporting data: the device vendor, product and version, as well as the default severity of data. +- **references**: +>https://community.softwaregrp.com/t5/ArcSight-Connectors/ArcSight-Common-Event-Format-CEF-Guide/ta-p/1589306?attachment-id=65537 +- **input**: +>MISP Event attributes +- **output**: +>Common Event Format file ----- -### goamlexport +#### goamlexport + + This module is used to export MISP events containing transaction objects into GoAML format. - **requirements**: @@ -223,7 +292,43 @@ This module is used to export MISP events containing transaction objects into Go ----- -### threatStream_misp_export +#### liteexport + +Lite export of a MISP event. +- **features**: +>This module is simply producing a json MISP event format file, but exporting only Attributes from the Event. Thus, MISP Events exported with this module should have attributes that are not internal references, otherwise the resulting event would be empty. +- **input**: +>MISP Event attributes +- **output**: +>Lite MISP Event + +----- + +#### pdfexport + +Simple export of a MISP event to PDF. +- **requirements**: +>PyMISP, asciidoctor +- **features**: +>The module takes care of the PDF file building, and work with any MISP Event. Except the requirement of asciidoctor, used to create the file, there is no special feature concerning the Event. +- **references**: +>https://acrobat.adobe.com/us/en/acrobat/about-adobe-pdf.html +- **input**: +>MISP Event +- **output**: +>MISP Event in a PDF file. + +----- + +#### testexport + +Skeleton export module. + +----- + +#### threatStream_misp_export + + Module to export a structured CSV file for uploading to threatStream. - **requirements**: @@ -239,7 +344,9 @@ Module to export a structured CSV file for uploading to threatStream. ----- -### threat_connect_export +#### threat_connect_export + + Module to export a structured CSV file for uploading to ThreatConnect. - **requirements**: @@ -256,96 +363,9 @@ Module to export a structured CSV file for uploading to ThreatConnect. ----- -### pdfexport - -Simple export of a MISP event to PDF. -- **requirements**: ->PyMISP, asciidoctor -- **features**: ->The module takes care of the PDF file building, and work with any MISP Event. Except the requirement of asciidoctor, used to create the file, there is no special feature concerning the Event. -- **references**: ->https://acrobat.adobe.com/us/en/acrobat/about-adobe-pdf.html -- **input**: ->MISP Event -- **output**: ->MISP Event in a PDF file. - ------ - -### cef_export - -Module to export a MISP event in CEF format. -- **features**: ->The module takes a MISP event in input, to look every attribute. Each attribute matching with some predefined types is then exported in Common Event Format. ->Thus, there is no particular feature concerning MISP Events since any event can be exported. However, 4 configuration parameters recognized by CEF format are required and should be provided by users before exporting data: the device vendor, product and version, as well as the default severity of data. -- **references**: ->https://community.softwaregrp.com/t5/ArcSight-Connectors/ArcSight-Common-Event-Format-CEF-Guide/ta-p/1589306?attachment-id=65537 -- **input**: ->MISP Event attributes -- **output**: ->Common Event Format file - ------ - -### liteexport - -Lite export of a MISP event. -- **features**: ->This module is simply producing a json MISP event format file, but exporting only Attributes from the Event. Thus, MISP Events exported with this module should have attributes that are not internal references, otherwise the resulting event would be empty. -- **input**: ->MISP Event attributes -- **output**: ->Lite MISP Event - ------ - ## Import Modules -### vmray_import - -Module to import VMRay (VTI) results. -- **requirements**: ->vmray_rest_api -- **features**: ->The module imports MISP Attributes from VMRay format, using the VMRay api. ->Users should then provide as the module configuration the API Key as well as the server url in order to fetch their data to import. -- **references**: ->https://www.vmray.com/ -- **input**: ->VMRay format -- **output**: ->MISP Event attributes - ------ - -### threatanalyzer_import - -Module to import ThreatAnalyzer archive.zip / analysis.json files. -- **features**: ->The module imports MISP Attributes from a ThreatAnalyzer format file. This file can be either ZIP, or JSON format. ->There is by the way no special feature for users to make the module work. -- **references**: ->https://www.threattrack.com/malware-analysis.aspx -- **input**: ->ThreatAnalyzer format file -- **output**: ->MISP Event attributes - ------ - -### ocr - -Optical Character Recognition (OCR) module for MISP. -- **features**: ->The module tries to recognize some text from an image and import the result as a freetext attribute, there is then no special feature asked to users to make it work. -- **input**: ->Image -- **output**: ->freetext MISP attribute - ------ - -### csvimport +#### csvimport Module to import MISP attributes from a csv file. - **requirements**: @@ -366,7 +386,38 @@ Module to import MISP attributes from a csv file. ----- -### goamlimport +#### cuckooimport + + + +Module to import Cuckoo JSON. +- **features**: +>The module simply imports MISP Attributes from a Cuckoo JSON format file. There is thus no special feature to make it work. +- **references**: +>https://cuckoosandbox.org/, https://github.com/cuckoosandbox/cuckoo +- **input**: +>Cuckoo JSON file +- **output**: +>MISP Event attributes + +----- + +#### email_import + +Module to import emails in MISP. +- **features**: +>This module can be used to import e-mail text as well as attachments and urls. +>3 configuration parameters are then used to unzip attachments, guess zip attachment passwords, and extract urls: set each one of them to True or False to process or not the respective corresponding actions. +- **input**: +>E-mail file +- **output**: +>MISP Event attributes + +----- + +#### goamlimport + + Module to import MISP objects about financial transactions from GoAML files. - **requirements**: @@ -382,34 +433,7 @@ Module to import MISP objects about financial transactions from GoAML files. ----- -### cuckooimport - -Module to import Cuckoo JSON. -- **features**: ->The module simply imports MISP Attributes from a Cuckoo JSON format file. There is thus no special feature to make it work. -- **references**: ->https://cuckoosandbox.org/, https://github.com/cuckoosandbox/cuckoo -- **input**: ->Cuckoo JSON file -- **output**: ->MISP Event attributes - ------ - -### email_import - -Module to import emails in MISP. -- **features**: ->This module can be used to import e-mail text as well as attachments and urls. ->3 configuration parameters are then used to unzip attachments, guess zip attachment passwords, and extract urls: set each one of them to True or False to process or not the respective corresponding actions. -- **input**: ->E-mail file -- **output**: ->MISP Event attributes - ------ - -### mispjson +#### mispjson Module to import MISP JSON format for merging MISP events. - **features**: @@ -421,7 +445,19 @@ Module to import MISP JSON format for merging MISP events. ----- -### openiocimport +#### ocr + +Optical Character Recognition (OCR) module for MISP. +- **features**: +>The module tries to recognize some text from an image and import the result as a freetext attribute, there is then no special feature asked to users to make it work. +- **input**: +>Image +- **output**: +>freetext MISP attribute + +----- + +#### openiocimport Module to import OpenIOC packages. - **requirements**: @@ -436,3 +472,37 @@ Module to import OpenIOC packages. >MISP Event attributes ----- + +#### threatanalyzer_import + +Module to import ThreatAnalyzer archive.zip / analysis.json files. +- **features**: +>The module imports MISP Attributes from a ThreatAnalyzer format file. This file can be either ZIP, or JSON format. +>There is by the way no special feature for users to make the module work. +- **references**: +>https://www.threattrack.com/malware-analysis.aspx +- **input**: +>ThreatAnalyzer format file +- **output**: +>MISP Event attributes + +----- + +#### vmray_import + + + +Module to import VMRay (VTI) results. +- **requirements**: +>vmray_rest_api +- **features**: +>The module imports MISP Attributes from VMRay format, using the VMRay api. +>Users should then provide as the module configuration the API Key as well as the server url in order to fetch their data to import. +- **references**: +>https://www.vmray.com/ +- **input**: +>VMRay format +- **output**: +>MISP Event attributes + +----- From ba2b2652a9f094450bd7ef7b9b3a46724cac6a11 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 13 Sep 2018 18:23:12 +0200 Subject: [PATCH 143/153] chg: Changed documentation markdown file name --- doc/{markdown.md => documentation.md} | 0 doc/generate_documentation.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename doc/{markdown.md => documentation.md} (100%) diff --git a/doc/markdown.md b/doc/documentation.md similarity index 100% rename from doc/markdown.md rename to doc/documentation.md diff --git a/doc/generate_documentation.py b/doc/generate_documentation.py index af66b8a..ce4d60f 100644 --- a/doc/generate_documentation.py +++ b/doc/generate_documentation.py @@ -24,5 +24,5 @@ for _path, title in zip(module_types, titles): value = ', '.join(value) if isinstance(value, list) else '{}'.format(value.replace('\n', '\n>')) markdown.append('- **{}**:\n>{}\n'.format(field, value)) markdown.append('\n-----\n') -with open('markdown.md', 'w') as w: +with open('documentation.md', 'w') as w: w.write(''.join(markdown)) From 5dc05bfafcd6d1a6028472791d14d87f5eaf8b6a Mon Sep 17 00:00:00 2001 From: Igor Ivanov Date: Tue, 18 Sep 2018 11:18:55 +0200 Subject: [PATCH 144/153] initial Vulners module PoC --- REQUIREMENTS | 1 + misp_modules/modules/expansion/vulners.py | 39 +++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 misp_modules/modules/expansion/vulners.py diff --git a/REQUIREMENTS b/REQUIREMENTS index c004afe..6ab46cc 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -25,3 +25,4 @@ yara sigmatools stix2-patterns maclookup +vulners \ No newline at end of file diff --git a/misp_modules/modules/expansion/vulners.py b/misp_modules/modules/expansion/vulners.py new file mode 100644 index 0000000..70b5d12 --- /dev/null +++ b/misp_modules/modules/expansion/vulners.py @@ -0,0 +1,39 @@ +import json +import requests +import vulners + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['vulnerability'], 'output': ['text']} +moduleinfo = {'version': '0.1', 'author': 'Igor Ivanov', 'description': 'An expansion hover module to expand information about CVE id using Vulners API.', 'module-type': ['hover']} + +# Get API key from https://vulners.com/userinfo +moduleconfig = ["apikey"] + + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + if not request.get('vulnerability'): + misperrors['error'] = 'Vulnerability id missing' + return misperrors + + key = q["config"]["apikey"] + vulners_api = vulners.Vulners(api_key=key) + vulners_document = vulners_api.document("CVE-2017-14174") + if vulners_document: + summary = vulners_document.get('description') + else: + summary = 'Non existing CVE' + + r = {'results': [{'types': mispattributes['output'], 'values': summary}]} + return r + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 8d7d37746450d653e23398ab77a802789e225324 Mon Sep 17 00:00:00 2001 From: Igor Ivanov Date: Tue, 18 Sep 2018 12:11:47 +0200 Subject: [PATCH 145/153] added exploit information --- misp_modules/modules/expansion/__init__.py | 2 +- misp_modules/modules/expansion/vulners.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index c6e81a7..fce9343 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,3 +1,3 @@ from . import _vmray -__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus'] +__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', 'threatminer', 'otx', 'threatcrowd', 'vulndb', 'crowdstrike_falcon', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus', 'vulners'] diff --git a/misp_modules/modules/expansion/vulners.py b/misp_modules/modules/expansion/vulners.py index 70b5d12..7d1b54b 100644 --- a/misp_modules/modules/expansion/vulners.py +++ b/misp_modules/modules/expansion/vulners.py @@ -18,14 +18,21 @@ def handler(q=False): misperrors['error'] = 'Vulnerability id missing' return misperrors - key = q["config"]["apikey"] + key = request['config'].get('apikey') vulners_api = vulners.Vulners(api_key=key) - vulners_document = vulners_api.document("CVE-2017-14174") + vulners_document = vulners_api.document(request.get('vulnerability')) + vulners_exploits = vulners_api.searchExploit(request.get('vulnerability')) if vulners_document: summary = vulners_document.get('description') else: summary = 'Non existing CVE' + if vulners_exploits: + for exploit in vulners_exploits[0]: + exploit_summary += exploit['title'] + " " + exploit['href'] + "\n" + summary += vulners_exploits[1] + " Public exploits available:\n " + exploit_summary + + r = {'results': [{'types': mispattributes['output'], 'values': summary}]} return r From 3e9589d0f44d8ef500e91b05e9568ee7c71cac44 Mon Sep 17 00:00:00 2001 From: Igor Ivanov Date: Tue, 18 Sep 2018 14:38:49 +0200 Subject: [PATCH 146/153] code cleanup and formatting --- misp_modules/modules/expansion/vulners.py | 28 ++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/misp_modules/modules/expansion/vulners.py b/misp_modules/modules/expansion/vulners.py index 7d1b54b..c7d48de 100644 --- a/misp_modules/modules/expansion/vulners.py +++ b/misp_modules/modules/expansion/vulners.py @@ -18,21 +18,33 @@ def handler(q=False): misperrors['error'] = 'Vulnerability id missing' return misperrors + ai_summary = '' + exploit_summary = '' + vuln_summary = '' + key = request['config'].get('apikey') vulners_api = vulners.Vulners(api_key=key) - vulners_document = vulners_api.document(request.get('vulnerability')) - vulners_exploits = vulners_api.searchExploit(request.get('vulnerability')) + vulnerability = request.get('vulnerability') + vulners_document = vulners_api.document(vulnerability) + vulners_exploits = vulners_api.searchExploit(vulnerability) + vulners_ai_score = vulners_api.aiScore(vulnerability) + if vulners_document: - summary = vulners_document.get('description') + vuln_summary += vulners_document.get('description') else: - summary = 'Non existing CVE' + vuln_summary += 'Non existing CVE' + + if vulners_ai_score: + ai_summary += 'Vulners AI Score is ' + str(vulners_ai_score[0]) + " " if vulners_exploits: + exploit_summary += " || " + str(len(vulners_exploits[0])) + " Public exploits available:\n " for exploit in vulners_exploits[0]: - exploit_summary += exploit['title'] + " " + exploit['href'] + "\n" - summary += vulners_exploits[1] + " Public exploits available:\n " + exploit_summary - - + exploit_summary += exploit['title'] + " " + exploit['href'] + "\n " + exploit_summary += "|| Vulnerability Description: " + vuln_summary + + summary = ai_summary + exploit_summary + vuln_summary + r = {'results': [{'types': mispattributes['output'], 'values': summary}]} return r From 007723109d559b46fdcfc395a191e5888651a3a3 Mon Sep 17 00:00:00 2001 From: Igor Ivanov Date: Tue, 18 Sep 2018 15:56:15 +0200 Subject: [PATCH 147/153] HotFix: Vulners AI score --- misp_modules/modules/expansion/vulners.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/vulners.py b/misp_modules/modules/expansion/vulners.py index c7d48de..2be0a9f 100644 --- a/misp_modules/modules/expansion/vulners.py +++ b/misp_modules/modules/expansion/vulners.py @@ -27,7 +27,7 @@ def handler(q=False): vulnerability = request.get('vulnerability') vulners_document = vulners_api.document(vulnerability) vulners_exploits = vulners_api.searchExploit(vulnerability) - vulners_ai_score = vulners_api.aiScore(vulnerability) + vulners_ai_score = vulners_api.aiScore(vulners_document.get('description')) if vulners_document: vuln_summary += vulners_document.get('description') From f1325f431629f222451bc1ac521f9339853b263d Mon Sep 17 00:00:00 2001 From: isox Date: Tue, 18 Sep 2018 18:36:12 +0300 Subject: [PATCH 148/153] Fixed getting of the Vulners AI score. --- misp_modules/modules/expansion/vulners.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/vulners.py b/misp_modules/modules/expansion/vulners.py index c7d48de..49cb9aa 100644 --- a/misp_modules/modules/expansion/vulners.py +++ b/misp_modules/modules/expansion/vulners.py @@ -26,8 +26,15 @@ def handler(q=False): vulners_api = vulners.Vulners(api_key=key) vulnerability = request.get('vulnerability') vulners_document = vulners_api.document(vulnerability) + + # Get AI scoring from the document if it's already calculated + # There is no need to call AI Scoring method + if 'score' in vulners_document.get('enchantments', {}): + vulners_ai_score = vulners_document['enchantments']['score']['value'] + else: + vulners_ai_score = None + vulners_exploits = vulners_api.searchExploit(vulnerability) - vulners_ai_score = vulners_api.aiScore(vulnerability) if vulners_document: vuln_summary += vulners_document.get('description') From c19989e217627d7a7b05dde17a6f735ad7bc3126 Mon Sep 17 00:00:00 2001 From: Codelinefi-admin Date: Wed, 19 Sep 2018 21:50:56 +0300 Subject: [PATCH 149/153] Fixed a bug with wrong dates conversion --- misp_modules/modules/expansion/macaddress_io.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/misp_modules/modules/expansion/macaddress_io.py b/misp_modules/modules/expansion/macaddress_io.py index 14e1134..e735f39 100644 --- a/misp_modules/modules/expansion/macaddress_io.py +++ b/misp_modules/modules/expansion/macaddress_io.py @@ -79,29 +79,36 @@ def handler(q=False): misperrors['error'] = 'Unknown error' return misperrors + date_created = \ + response.block_details.date_created.strftime('%d %B %Y') if response.block_details.date_created else None + + date_updated = \ + response.block_details.date_updated.strftime('%d %B %Y') if response.block_details.date_updated else None + results = { 'results': [ {'types': ['text'], 'values': { + # Mac address details 'Valid MAC address': "True" if response.mac_address_details.is_valid else "False", - 'Transmission type': response.mac_address_details.transmission_type, 'Administration type': response.mac_address_details.administration_type, + # Vendor details 'OUI': response.vendor_details.oui, 'Vendor details are hidden': "True" if response.vendor_details.is_private else "False", - 'Company name': response.vendor_details.company_name, 'Company\'s address': response.vendor_details.company_address, 'County code': response.vendor_details.country_code, + # Block details 'Block found': "True" if response.block_details.block_found else "False", 'The left border of the range': response.block_details.border_left, 'The right border of the range': response.block_details.border_right, 'The total number of MAC addresses in this range': response.block_details.block_size, 'Assignment block size': response.block_details.assignment_block_size, - 'Date when the range was allocated': response.block_details.date_created, - 'Date when the range was last updated': response.block_details.date_updated + 'Date when the range was allocated': date_created, + 'Date when the range was last updated': date_updated } } ] From 4ad60ca948ba7d4aa6d3a8178bf0ded291dd0b76 Mon Sep 17 00:00:00 2001 From: Codelinefi-admin Date: Wed, 19 Sep 2018 21:51:23 +0300 Subject: [PATCH 150/153] Updated README. Added a link to the integration tutorial --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index af32bc0..8a63403 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [hashdd](misp_modules/modules/expansion/hashdd.py) - a hover module to check file hashes against [hashdd.com](http://www.hashdd.com) including NSLR dataset. * [IPASN](misp_modules/modules/expansion/ipasn.py) - a hover and expansion to get the BGP ASN of an IP address. * [iprep](misp_modules/modules/expansion/iprep.py) - an expansion module to get IP reputation from packetmail.net. -* [macaddress.io](misp_modules/modules/expansion/macaddress_io.py) - a hover module to retrieve vendor details and other information regarding a given MAC address or an OUI. [MAC address Vendor Lookup](https://macaddress.io) +* [macaddress.io](misp_modules/modules/expansion/macaddress_io.py) - a hover module to retrieve vendor details and other information regarding a given MAC address or an OUI from [MAC address Vendor Lookup](https://macaddress.io). See [integration tutorial here](https://macaddress.io/integrations/MISP-module). * [onyphe](misp_modules/modules/expansion/onyphe.py) - a modules to process queries on Onyphe. * [onyphe_full](misp_modules/modules/expansion/onyphe_full.py) - a modules to process full queries on Onyphe. * [OTX](misp_modules/modules/expansion/otx.py) - an expansion module for [OTX](https://otx.alienvault.com/). From b2c34fba06b745b0e4f3334658bb28349f32e970 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 20 Sep 2018 10:46:25 +0200 Subject: [PATCH 151/153] fix: Reduced logos size --- doc/documentation.md | 56 +++++++++++++++++------------------ doc/generate_documentation.py | 47 ++++++++++++++++------------- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/doc/documentation.md b/doc/documentation.md index 1212b5f..9c8709d 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -12,7 +12,7 @@ Query an ASN description history service (https://github.com/CIRCL/ASN-Descripti #### circl_passivedns - + Module to access CIRCL Passive DNS. @@ -20,7 +20,7 @@ Module to access CIRCL Passive DNS. #### circl_passivessl - + Modules to access CIRCL Passive SSL. @@ -34,7 +34,7 @@ Module to expand country codes. #### crowdstrike_falcon - + Module to query Crowdstrike Falcon. @@ -48,7 +48,7 @@ An expansion hover module to expand information about CVE id. #### dbl_spamhaus - + Module to check Spamhaus DBL for a domain name. @@ -62,7 +62,7 @@ A simple DNS expansion service to resolve IP address from MISP attributes. #### domaintools - + DomainTools MISP expansion module. @@ -70,7 +70,7 @@ DomainTools MISP expansion module. #### eupi - + A module to query the Phishing Initiative service (https://phishing-initiative.lu). @@ -78,7 +78,7 @@ A module to query the Phishing Initiative service (https://phishing-initiative.l #### farsight_passivedns - + Module to access Farsight DNSDB Passive DNS. @@ -110,23 +110,23 @@ Module to query IPRep data for IP addresses. #### onyphe - - + +Module to process a query on Onyphe. ----- #### onyphe_full - - + +Module to process a full query on Onyphe. ----- #### otx - + Module to get information from AlienVault OTX. @@ -134,7 +134,7 @@ Module to get information from AlienVault OTX. #### passivetotal - + The PassiveTotal MISP expansion module brings the datasets derived from Internet scanning directly into your MISP instance. This module supports passive DNS, historic SSL, WHOIS, and host attributes. In order to use the module, you must have a valid PassiveTotal account username and API key. Registration is free and can be done by visiting https://www.passivetotal.org/register @@ -156,7 +156,7 @@ Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes #### shodan - + Module to query on Shodan. @@ -170,7 +170,7 @@ Module to cache web pages of analysis reports, OSINT sources. The module returns #### threatcrowd - + Module to get information from ThreatCrowd. @@ -178,7 +178,7 @@ Module to get information from ThreatCrowd. #### threatminer - + Module to get information from ThreatMiner. @@ -186,7 +186,7 @@ Module to get information from ThreatMiner. #### virustotal - + Module to get information from virustotal. @@ -194,7 +194,7 @@ Module to get information from virustotal. #### vmray_submit - + Module to submit a sample to VMRay. @@ -202,7 +202,7 @@ Module to submit a sample to VMRay. #### vulndb - + Module to query VulnDB (RiskBasedSecurity.com). @@ -218,7 +218,7 @@ Module to query a local instance of uwhois (https://github.com/rafiot/uwhoisd). #### wiki - + An expansion hover module to extract information from Wikidata to have additional information about particular term for analysis. @@ -226,7 +226,7 @@ An expansion hover module to extract information from Wikidata to have additiona #### xforceexchange - + An expansion module for IBM X-Force Exchange. @@ -234,7 +234,7 @@ An expansion module for IBM X-Force Exchange. #### yara_syntax_validator - + An expansion hover module to perform a syntax check on if yara rules are valid or not. @@ -259,7 +259,7 @@ Module to export a MISP event in CEF format. #### goamlexport - + This module is used to export MISP events containing transaction objects into GoAML format. - **requirements**: @@ -328,7 +328,7 @@ Skeleton export module. #### threatStream_misp_export - + Module to export a structured CSV file for uploading to threatStream. - **requirements**: @@ -346,7 +346,7 @@ Module to export a structured CSV file for uploading to threatStream. #### threat_connect_export - + Module to export a structured CSV file for uploading to ThreatConnect. - **requirements**: @@ -388,7 +388,7 @@ Module to import MISP attributes from a csv file. #### cuckooimport - + Module to import Cuckoo JSON. - **features**: @@ -417,7 +417,7 @@ Module to import emails in MISP. #### goamlimport - + Module to import MISP objects about financial transactions from GoAML files. - **requirements**: @@ -490,7 +490,7 @@ Module to import ThreatAnalyzer archive.zip / analysis.json files. #### vmray_import - + Module to import VMRay (VTI) results. - **requirements**: diff --git a/doc/generate_documentation.py b/doc/generate_documentation.py index ce4d60f..283f4bc 100644 --- a/doc/generate_documentation.py +++ b/doc/generate_documentation.py @@ -2,27 +2,32 @@ import os import json -root_path = os.path.dirname(os.path.realpath(__file__)) module_types = ['expansion', 'export_mod', 'import_mod'] titles = ['Expansion Modules', 'Export Modules', 'Import Modules'] markdown= ["# MISP modules documentation\n"] -for _path, title in zip(module_types, titles): - markdown.append('\n## {}\n'.format(title)) - current_path = os.path.join(root_path, _path) - files = sorted(os.listdir(current_path)) - for _file in files: - markdown.append('\n#### {}\n'.format(_file.split('.json')[0])) - filename = os.path.join(current_path, _file) - with open(filename, 'rt', encoding='utf-8') as f: - definition = json.loads(f.read()) - if 'logo' in definition: - markdown.append('\n\n'.format(definition.pop('logo'))) - if 'description' in definition: - markdown.append('\n{}\n'.format(definition.pop('description'))) - for field, value in definition.items(): - if value: - value = ', '.join(value) if isinstance(value, list) else '{}'.format(value.replace('\n', '\n>')) - markdown.append('- **{}**:\n>{}\n'.format(field, value)) - markdown.append('\n-----\n') -with open('documentation.md', 'w') as w: - w.write(''.join(markdown)) + +def generate_doc(root_path): + for _path, title in zip(module_types, titles): + markdown.append('\n## {}\n'.format(title)) + current_path = os.path.join(root_path, _path) + files = sorted(os.listdir(current_path)) + for _file in files: + markdown.append('\n#### {}\n'.format(_file.split('.json')[0])) + filename = os.path.join(current_path, _file) + with open(filename, 'rt', encoding='utf-8') as f: + definition = json.loads(f.read()) + if 'logo' in definition: + markdown.append('\n\n'.format(definition.pop('logo'))) + if 'description' in definition: + markdown.append('\n{}\n'.format(definition.pop('description'))) + for field, value in definition.items(): + if value: + value = ', '.join(value) if isinstance(value, list) else '{}'.format(value.replace('\n', '\n>')) + markdown.append('- **{}**:\n>{}\n'.format(field, value)) + markdown.append('\n-----\n') + with open('documentation.md', 'w') as w: + w.write(''.join(markdown)) + +if __name__ == '__main__': + root_path = os.path.dirname(os.path.realpath(__file__)) + generate_doc(root_path) From e2cebd6c3e1e11b9a86a0965f673f96365ab8881 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 25 Sep 2018 17:10:19 +0200 Subject: [PATCH 152/153] fix: Catching errors while parsing additional info in requests --- misp_modules/modules/expansion/virustotal.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/virustotal.py b/misp_modules/modules/expansion/virustotal.py index 524bc49..d92ede1 100644 --- a/misp_modules/modules/expansion/virustotal.py +++ b/misp_modules/modules/expansion/virustotal.py @@ -134,7 +134,10 @@ class VirusTotalRequest(object): # Go through euch key and check if it exists for VT_type, MISP_type in self.output_types_mapping.items(): if VT_type in data: - self.results[((MISP_type,), comment.format(h))].add(data[VT_type]) + try: + self.results[((MISP_type,), comment.format(h))].add(data[VT_type]) + except TypeError: + self.results[((MISP_type,), comment.format(h))].update(data[VT_type]) # Get the malware sample sample = requests.get(self.base_url[:-6].format('file/download'), params={'hash': h, 'apikey': self.apikey}) malsample = sample.content From f9788c8fd339e28788341651d19eddfef8f47cbb Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Sat, 29 Sep 2018 08:09:27 +0200 Subject: [PATCH 153/153] chg: [doc] documentation generator updated to include links to source code --- doc/documentation.md | 96 +++++++++++++++++------------------ doc/generate_documentation.py | 6 ++- 2 files changed, 53 insertions(+), 49 deletions(-) diff --git a/doc/documentation.md b/doc/documentation.md index 9c8709d..7be5f29 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -2,7 +2,7 @@ ## Expansion Modules -#### asn_history +#### [asn_history](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/asn_history.py) Query an ASN description history service (https://github.com/CIRCL/ASN-Description-History.git). - **requirements**: @@ -10,7 +10,7 @@ Query an ASN description history service (https://github.com/CIRCL/ASN-Descripti ----- -#### circl_passivedns +#### [circl_passivedns](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/circl_passivedns.py) @@ -18,7 +18,7 @@ Module to access CIRCL Passive DNS. ----- -#### circl_passivessl +#### [circl_passivessl](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/circl_passivessl.py) @@ -26,13 +26,13 @@ Modules to access CIRCL Passive SSL. ----- -#### countrycode +#### [countrycode](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/countrycode.py) Module to expand country codes. ----- -#### crowdstrike_falcon +#### [crowdstrike_falcon](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/crowdstrike_falcon.py) @@ -40,13 +40,13 @@ Module to query Crowdstrike Falcon. ----- -#### cve +#### [cve](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/cve.py) An expansion hover module to expand information about CVE id. ----- -#### dbl_spamhaus +#### [dbl_spamhaus](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/dbl_spamhaus.py) @@ -54,13 +54,13 @@ Module to check Spamhaus DBL for a domain name. ----- -#### dns +#### [dns](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/dns.py) A simple DNS expansion service to resolve IP address from MISP attributes. ----- -#### domaintools +#### [domaintools](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/domaintools.py) @@ -68,7 +68,7 @@ DomainTools MISP expansion module. ----- -#### eupi +#### [eupi](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/eupi.py) @@ -76,7 +76,7 @@ A module to query the Phishing Initiative service (https://phishing-initiative.l ----- -#### farsight_passivedns +#### [farsight_passivedns](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/farsight_passivedns.py) @@ -84,31 +84,31 @@ Module to access Farsight DNSDB Passive DNS. ----- -#### geoip_country +#### [geoip_country](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/geoip_country.py) Module to query a local copy of Maxminds Geolite database. ----- -#### intelmq_eventdb +#### [intelmq_eventdb](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/intelmq_eventdb.py) Module to access intelmqs eventdb. ----- -#### ipasn +#### [ipasn](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/ipasn.py) Module to query an IP ASN history service (https://github.com/CIRCL/IP-ASN-history.git). ----- -#### iprep +#### [iprep](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/iprep.py) Module to query IPRep data for IP addresses. ----- -#### onyphe +#### [onyphe](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/onyphe.py) @@ -116,7 +116,7 @@ Module to process a query on Onyphe. ----- -#### onyphe_full +#### [onyphe_full](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/onyphe_full.py) @@ -124,7 +124,7 @@ Module to process a full query on Onyphe. ----- -#### otx +#### [otx](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/otx.py) @@ -132,7 +132,7 @@ Module to get information from AlienVault OTX. ----- -#### passivetotal +#### [passivetotal](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/passivetotal.py) @@ -140,7 +140,7 @@ The PassiveTotal MISP expansion module brings the datasets derived from Internet ----- -#### rbl +#### [rbl](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/rbl.py) Module to check an IPv4 address against known RBLs. - **requirements**: @@ -148,13 +148,13 @@ Module to check an IPv4 address against known RBLs. ----- -#### reversedns +#### [reversedns](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/reversedns.py) Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes. ----- -#### shodan +#### [shodan](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/shodan.py) @@ -162,13 +162,13 @@ Module to query on Shodan. ----- -#### sourcecache +#### [sourcecache](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/sourcecache.py) Module to cache web pages of analysis reports, OSINT sources. The module returns a link of the cached page. ----- -#### threatcrowd +#### [threatcrowd](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/threatcrowd.py) @@ -176,7 +176,7 @@ Module to get information from ThreatCrowd. ----- -#### threatminer +#### [threatminer](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/threatminer.py) @@ -184,7 +184,7 @@ Module to get information from ThreatMiner. ----- -#### virustotal +#### [virustotal](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/virustotal.py) @@ -192,7 +192,7 @@ Module to get information from virustotal. ----- -#### vmray_submit +#### [vmray_submit](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/vmray_submit.py) @@ -200,7 +200,7 @@ Module to submit a sample to VMRay. ----- -#### vulndb +#### [vulndb](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/vulndb.py) @@ -208,7 +208,7 @@ Module to query VulnDB (RiskBasedSecurity.com). ----- -#### whois +#### [whois](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/whois.py) Module to query a local instance of uwhois (https://github.com/rafiot/uwhoisd). - **requirements**: @@ -216,7 +216,7 @@ Module to query a local instance of uwhois (https://github.com/rafiot/uwhoisd). ----- -#### wiki +#### [wiki](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/wiki.py) @@ -224,7 +224,7 @@ An expansion hover module to extract information from Wikidata to have additiona ----- -#### xforceexchange +#### [xforceexchange](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/xforceexchange.py) @@ -232,7 +232,7 @@ An expansion module for IBM X-Force Exchange. ----- -#### yara_syntax_validator +#### [yara_syntax_validator](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/yara_syntax_validator.py) @@ -242,7 +242,7 @@ An expansion hover module to perform a syntax check on if yara rules are valid o ## Export Modules -#### cef_export +#### [cef_export](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/cef_export.py) Module to export a MISP event in CEF format. - **features**: @@ -257,7 +257,7 @@ Module to export a MISP event in CEF format. ----- -#### goamlexport +#### [goamlexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/goamlexport.py) @@ -292,7 +292,7 @@ This module is used to export MISP events containing transaction objects into Go ----- -#### liteexport +#### [liteexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/liteexport.py) Lite export of a MISP event. - **features**: @@ -304,7 +304,7 @@ Lite export of a MISP event. ----- -#### pdfexport +#### [pdfexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/pdfexport.py) Simple export of a MISP event to PDF. - **requirements**: @@ -320,13 +320,13 @@ Simple export of a MISP event to PDF. ----- -#### testexport +#### [testexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/testexport.py) Skeleton export module. ----- -#### threatStream_misp_export +#### [threatStream_misp_export](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/threatStream_misp_export.py) @@ -344,7 +344,7 @@ Module to export a structured CSV file for uploading to threatStream. ----- -#### threat_connect_export +#### [threat_connect_export](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/threat_connect_export.py) @@ -365,7 +365,7 @@ Module to export a structured CSV file for uploading to ThreatConnect. ## Import Modules -#### csvimport +#### [csvimport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/csvimport.py) Module to import MISP attributes from a csv file. - **requirements**: @@ -386,7 +386,7 @@ Module to import MISP attributes from a csv file. ----- -#### cuckooimport +#### [cuckooimport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/cuckooimport.py) @@ -402,7 +402,7 @@ Module to import Cuckoo JSON. ----- -#### email_import +#### [email_import](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/email_import.py) Module to import emails in MISP. - **features**: @@ -415,7 +415,7 @@ Module to import emails in MISP. ----- -#### goamlimport +#### [goamlimport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/goamlimport.py) @@ -433,7 +433,7 @@ Module to import MISP objects about financial transactions from GoAML files. ----- -#### mispjson +#### [mispjson](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/mispjson.py) Module to import MISP JSON format for merging MISP events. - **features**: @@ -445,7 +445,7 @@ Module to import MISP JSON format for merging MISP events. ----- -#### ocr +#### [ocr](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/ocr.py) Optical Character Recognition (OCR) module for MISP. - **features**: @@ -457,7 +457,7 @@ Optical Character Recognition (OCR) module for MISP. ----- -#### openiocimport +#### [openiocimport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/openiocimport.py) Module to import OpenIOC packages. - **requirements**: @@ -473,7 +473,7 @@ Module to import OpenIOC packages. ----- -#### threatanalyzer_import +#### [threatanalyzer_import](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/threatanalyzer_import.py) Module to import ThreatAnalyzer archive.zip / analysis.json files. - **features**: @@ -488,7 +488,7 @@ Module to import ThreatAnalyzer archive.zip / analysis.json files. ----- -#### vmray_import +#### [vmray_import](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/vmray_import.py) diff --git a/doc/generate_documentation.py b/doc/generate_documentation.py index 283f4bc..5a59814 100644 --- a/doc/generate_documentation.py +++ b/doc/generate_documentation.py @@ -5,14 +5,18 @@ import json module_types = ['expansion', 'export_mod', 'import_mod'] titles = ['Expansion Modules', 'Export Modules', 'Import Modules'] markdown= ["# MISP modules documentation\n"] +githublink = 'https://github.com/MISP/misp-modules/tree/master/misp_modules/modules' def generate_doc(root_path): for _path, title in zip(module_types, titles): markdown.append('\n## {}\n'.format(title)) current_path = os.path.join(root_path, _path) files = sorted(os.listdir(current_path)) + githubpath = '{}/{}'.format(githublink, _path) for _file in files: - markdown.append('\n#### {}\n'.format(_file.split('.json')[0])) + modulename = _file.split('.json')[0] + githubref = '{}/{}.py'.format(githubpath, modulename) + markdown.append('\n#### [{}]({})\n'.format(modulename, githubref)) filename = os.path.join(current_path, _file) with open(filename, 'rt', encoding='utf-8') as f: definition = json.loads(f.read())